前言:
为什么会有结构体,结构体可以用来面熟一个复杂对象,我们知道C语言中有哪些数据类型,有整型,有浮点型,有字符型,但是在生活中,我们需要描述一些比较复杂的东西,比如说一本书,有书名,有价格,有出版社等等,所以我们可以用结构体去描述。
结构体的声明
一般结构体声明时,形式如图所示:
struct book
{int price;char name[20];
}p1,p2;
解析:
struct为关键字,在声明结构体的时候,必须有关键字struct。
book表示类型,就类似于int、char、float,只不过book是我们自定义的类型而已。
{}里面的是描述book这个类型里面的成员,称为成员变量。
{}外边的p1,p2是两个定义的变量名,这里可写也可以不用写,在主函数中也可以定义。
类似于:
struct book {int price;char name[20]; }; int main() {struct book p1;struct book p2;return 0; }
放在主函数中定义!
特殊的声明:
在声明结构的时候,可以不完全的声明;
//匿名结构体类型 struct //省略了结构体类型 {int a;char b;float c; }x;
这样声明结构体是可以的
但是,我如果一个源文件中有两个匿名结构体可不可以呢?
答案是不行的,不然初始化的时候不知带是对哪一个结构体进行初始化的。
类似于这样是不可取的:
//匿名结构体类型 struct {int a;char b;float c; }x; struct {int a;char b;float c; }a[20], *p;
这里声明了两个匿名结构体。
注:这里两个匿名结构体是相互独立的,谁有谁的地址,当我定义*p后,我用*p = &x是不行滴!!
初始化结构体
声明好结构体后,接下来就是初始化结构体
struct book
{int price;char name[20];
};
int main()
{struct book a1 = { 3,"lisi" };//一般初始化struct book a2[2] = {{4,"zhangsan"},{"10","wangwu"}};//结构体数组初始化return 0;
}
结构体也可以嵌套初始化:
struct Stu //类型声明
{char name[15];//名字int age; //年龄
};
struct Node
{int data;struct Stu p;//嵌套声明struct Node* next;
}n1 = { 10, {"lisi",5}, &n1};//嵌套初始化
结构体成员访问:
结构体成员的访问有两种方式,第一种:
#include<stdio.h>
struct book
{int price;char name[20];
};
int main()
{struct book p1 = {10,"lisi"};printf("%d\n", p1.price);printf("%s\n", p1.name);return 0;
}
访问结构体中的成员变量用.去访问。
第二种用指针访问:
#include<stdio.h>
struct book
{int price;char name[20];
};
int main()
{struct book p1 = {10,"lisi"};struct book *p = &p1;printf("%d\n", p->price);printf("%s\n", p->name);return 0;
}
访问结构体中的成员变量用->去访问。
结构体传参:
代码如下:
struct S
{int data[1000];int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{printf("%d\n", ps->num);
}
int main()
{print1(s); //传结构体print2(&s); //传地址return 0;
}
有两种方式,在这里通过代码去分析。
可以传地址进去,用指针接收。
也可以传结构体进去,用结构体接收。
结构体大小(内存对齐):
由于结构体是由问我们自己定义的,那么结构体大小该如如何计算?
计算如下结构体大小:
struct arr
{int a;char b;char c;
};
int main()
{printf("%d\n", sizeof(struct arr));return 0;
}
如果直接想,答案应该是4+1+1 = 6;
但是:
答案是8!!
再来看一组:
struct arr
{char b;int a;char c;
};
int main()
{printf("%d\n", sizeof(struct arr));return 0;
}
和第一组相比,我交换了一下int a和char b的位置,答案是多少?
是6?还是8?
答案是:竟然是12!
为什么呢?
按照结构体的对齐规则:
首先得掌握结构体的对齐规则:
1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
了解了对齐规则,我们再来看第一个案例,答案为什么是8?
struct arr
{int a;char b;char c;
};
int main()
{printf("%d\n", sizeof(struct arr));return 0;
}
通过画图讲解:
所以答案是8,可以自己画图分析。
继续分析第二个,交换顺序后大小为什么会变?
可以自己练习一个:
struct S3 {double d;char c;int i; }; printf("%d\n", sizeof(struct S3));
答案应该是多少呢?
答案是16!!
嵌套结构体大小:
struct arr
{char b;char c;int a;
};
struct S3
{double d;char c;int i;struct arr a;
};
int main()
{printf("%d\n", sizeof(struct S3));return 0;
}
它的大小是多少呢?
根据内存对齐规则第4条:
如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
画图解释:
最后的大小是最大对齐数的整数倍,最大对齐数是8,所以总大小是24。
为什么会存在内存对齐
1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特 定类型的数据,否则抛出硬件异常。
2、性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说: 结构体的内存对齐是拿空间来换取时间的做法。
修改默认对齐数
在vs中,默认对齐数是8,当然我们也可以进行修改,用到#pragma预处理指令。
#pragma pack(1)//设置默认对齐数是1
struct arr
{int a;char b;char c;
};
#pragma pack();//取消默认对齐数
可以设置默认对齐数为1,然后计算结构体大小。
#pragma pack(1)
struct arr
{char a;char b;int c;
};
int main()
{printf("%d\n", sizeof(struct arr));
}
答案是多少呢?
答案应该是6;
位段:
2.1 什么是位段
位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是 int、unsigned int 或signed int 和char(整型家族)。
2.位段的成员名后边有一个冒号和一个数字,单位是比特位。
代码如下:
struct arr
{char a : 3;char b : 2;char c : 3;
};
此时,arr就是一个位段类型。
那么位段类型的大小是多少呢?
此时只占用了1个字节,答案是1;
如果是这样呢
struct arr
{char a : 3;char b : 6;char c : 4;
};
int main()
{printf("%d\n", sizeof(struct arr));
}
错误示例:
答案应该是3,不是2!!
枚举
枚举顾名思义就是一一列举。
把可能的取值一一列举。
枚举的定义:
关键字是enum!
enum Day//星期
{Mon,Tues,Wed,Thur,Fri,Sat,Sun
};
enum Sex//性别
{MALE,FEMALE,SECRET
};
enum Color//颜色
{RED,GREEN,BLUE
};
以上定义的 enum Day , enum Sex , enum Color 都是枚举类型。 {}中的内容是枚举类型的可能取值,也叫 枚举常量 。
我们用%d打印一下,看看每个枚举类型中的变量的值。
enum SEX
{MALE,FEMALE,SECRET
};
int main()
{printf("%d\n", MALE);printf("%d\n", FEMALE);printf("%d\n", SECRET);
}
那么这个对应的值可不可以修改呢?
答案是不可以的,因为是枚举常量,常量的值是不可以修改的。
但是在枚举{}内是可以修改的:
enum SEX
{MALE = 10,FEMALE = 9,SECRET = 2
};
int main()
{printf("%d\n", MALE);printf("%d\n", FEMALE);printf("%d\n", SECRET);}
枚举的使用:
enum Color//颜色
{RED = 1,GREEN = 2,BLUE = 4
};
int main()
{enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。}
只能拿枚举常量给枚举常量赋值,才不会出现差异!
枚举的优点:
我们可以使用 #define 定义常量,为什么非要使用枚举?
枚举的优点:
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名污染(封装)
4. 便于调试
5. 使用方便,一次可以定义多个常量
联合(共用体)
联合体用关键字:union
联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。 比如:
//联合体的声明
union arr
{int a;char b;
};
int main()
{union arr a1;//联合体定义return 0;
}
联合体共用一块空间,可以看看联合体内这两个成员的地址是否相同?
union arr
{int a;char b;
};
int main()
{union arr a1;//联合体定义printf("%p\n", &a1.a);printf("%p\n", &a1.b);return 0;
}
地址是一样的。
联合体的特点:
联合的成员是共用同一块内存空间的,
这样一个联合变量的大小,至少是最大成员的大小(因为联 合至少得有能力保存最大的那个成员)。
联合体大小的计算
联合的大小至少是最大成员的大小。
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
例如:
union Un1
{char c[5];int i;
};
union Un2
{short c[7];int i;
};
int main()
{printf("%d\n", sizeof(union Un1));printf("%d\n", sizeof(union Un2));
}
这两个的打印结果是多少呢?
联合体的应用
利用联合体判断大小端。
之前判断大小端的方法:
int Print(int *a)
{return *(char*)a;
}
int main()
{int a = 1;int b = Print(&a);if (b == 1){printf("小端存储");}elseprintf("大端存储\n");return 0;
}
利用联合体判断:
union arr
{int a;char b;
};
int main()
{union arr a1 ;a1.a = 1;printf("%d\n", a1.b);if (a1.b == 1){printf("小端存储");}elseprintf("大端存储\n");return 0;
}
将1存入int型变量中,之后用char类型变量将其取出。