一:结构体
1.结构体的定义方法
(1).先定义结构体类型,再去定义结构体变量
struct 结构体类型名
{
成员列表
};
例子:
struct stu
{
char name[20];
int id;
char sex;
};
struct stu lucy, boo, lilei;
讲解:在这里我们自定义了一个结构体,结构体类型名为stu,结构体成员共有3个分别为数组name,整形int,字符sex。
//有了结构体类型后,就可以用类型定义变量了
//struct stu lucy, bob, lilei;//定义了三个 struct stu 类型的变量
//每个变量都有三个成员,分别是 num ,name, sex
(2).在定义结构体类型的时候顺便定义结构体变量,以后还可以定义结构体变量
struct 结构体类型名
{
成员列表
}结构体变量 1,变量 2;
struct 结构体类型名 变量 3,变量4……;
例子:
struct stu
{
char name[20];
int id;
char sex;
}zhangsan,lisi;
struct stu lucy, boo, lilei;
(3).在定义结构体类型的时候,没有结构体类型名,顺便定义结构体变量, 因为没有类型名,所以以后不能再定义相关类型的数据了(该类型的定义方式也被称为匿名结构体类型)
struct
{
成员列表
}变量 1,变量 2;
注意:该种结构体类型只能使用一次,由于该结构体没有结构体类型名,如果想再次重新这个结构体可以使用typedef重新定义结构体的类型名并使用
例子:
struct
{
char name[20];
int id;
char sex;
}s;
typedef struct
{
char name[20];
int id;
char sex;
}STU;
2.typedef 的使用
typedef struct stu
{
char name[20];
int id;
char sex;
}STU;
解析:用typedef重新定义了结构体的类型名,重新定义后结构体类型名为STU,struct stu 相当于STU。
3.结构体变量的创建和初始化
结构体变量的创建
struct stu
{
char name[20];
int id;
char sex;
}zhangsan, lisi;//zhangsan,lisi为全局变量
struct stu s1;//全部变量
int main()
{
struct stu s2;//局部变量
}
讲解:结构体分别创建了结构体变量zhangsan,lisi,s1,s2;其中zhangsan,lisi为全局变量,s1也是全局变量,s2为局部变量
struct Book
{
char name[20];//书名
char author[20];//作者
float price;//价格
};
int main()
{
struct Book b1 = { "活着","yuhua",28.8 };
struct Book b2 = { .author = "yuhua", .name = "活着", .price = 28.8 };//乱序初始化
}
讲解:这里我们定义了一个关于书信息的结构体,其包括name,author,price三个结构体成员,然后我们分别使用两种初始化方式,第一种是对结构体的正序初始化,第二种通过“ .结构体成员名 ”的解引用方式实现了对结构体的乱序初始化。
4.结构体的内存对齐(结构体的大小)
(1).对齐规则
首先得掌握结构体的对齐规则:
1.结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处
2.其他成员变量要对齐到其对应结构体的对齐数的整数倍的地址处。
对齐数=编译器默认的⼀个对齐数与该成员变量大小的较小值。
Linux中gcc没有默认对齐数,对齐数就是成员自身的大小
VS 中默认的值为 8
3.结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对齐数中最大的对齐数的整数倍)。
4.如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构
体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
例子1:
struct S
{ // 编译器默认对齐数 对齐数
char c1;//1 8 1
int i;// 4 8 4
char c2;//1 8 1
};
int main()
{
struct S s = { 0 };
printf("%zd\n", sizeof(s));
return 0;
}
代码分析: 一个空格代表一个字节,从红色线处开始存放变量占据内存的大小,从红色线向下数第一个空格的偏移量为0,第二个空格的偏移量为1,第三个空格的偏移量为2等等类似。
由规则1(1.结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处)可知c1变量存放在第一个空格处(c1为char类型,大小为1个字节),
对于整形i其本身大小为4个字节,vs默认对齐数为8,那么i的对齐数为4(对齐数=编译器默认的⼀个对齐数与该成员变量大小的较小值,4和8较小的为4);由于从第二个成员开始变量的存放必须存放在其对齐数的整数被的地址处,所以第1,2,3个偏移量处的空格不能存放i,i会跳过第1,2,3个偏移量处的空格在第4个偏移量处的空格存放整形i的内容,(第4个偏移量为4的倍数),共占用4个字节(i为int类型,大小为4个字节);
c2的大小为1个字节,vs默认对齐数为8,1和8两者中较小值为1,所以c2的对齐数为1,偏移量为8处的空格为1的倍数可以存放c2的内容,共占用1个字节大小(c2为char类型)。偏移量从0~8共占用9个空格,那struct的大小不应该是9吗?
我们看规则3(3.结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对齐数中最大的对齐数整数倍)在三个变量成员中最大对齐数为4,9不是4的倍数,所以继续向下访问并占用空间内存,偏移量为9,10,11处的空间也会继续被浪费,所以结构体共占用12个字节。
如下图:
例子2:
struct S2
{
// 对齐数
char c1;// 1 8 1
char c2;//1 8 1
int i; //4 8 4
};
int main()
{
struct S2 s2 = { 0 };
printf("%zd\n", sizeof(s2));
return 0;
}
解析:我们仍然从红色线处开始存放变量内容,一个空格代表1个字节,由规则1(1.结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处)可知c1变量存放在第一个空格处(c1为char类型,大小为1个字节),所以第一个空格用来存放c1;
c2本身所占1个字节大小,vs默认对齐数为8,两者中较小值为1,所以c2的对齐数为1。由于偏移量为1处的空格为1的倍数,所以c2就存放在第二个空格;
i本身所占内存为4个字节大小,vs默认对齐数为8,两者中较小值为4,所以i的对齐数为4,第2,3个空格会被浪费,从第五个空格开始占用存放内容,共占用4个字节。
由于struct从第0个偏移量位置开始存放内存,到第7个偏移量位置共占用8个字节,struct三个成员中对齐数分别为1,1,4,最大对齐数为4,8为4的倍数,所以结构体的大小为8。
分析如下图:
例子3:
struct S3
{
// 对齐数
double d;//8 8 8
char c; //1 8 1
int i; //4 8 4
};
int main()
{
struct S3 s3 = { 0 };
printf("%d\n", sizeof(struct S3));
return 0;
}
解析:d从第一个空格开始存放内存,由于d为double类型,所以共占用8个字节;
由于c为char类型,只占1个字节内存,vs默认对齐数为8,所以c的对齐数为1,9为1的倍数,所以c存放在第9个空格;
i本身大小为4个字节,vs默认对齐数为8,所以i的对齐数为4,所以第9,10个空格会被浪费,i从第11个空格开始存放内存,由于i为int类型,共占用4个字节空间大小。此时struct的大小为15,但由于struct的最大对齐数为8(c为1,i为4),struct的大小必须为8的倍数,所以存好i后会继续浪费1个字节大小,此时struct共占用16个字节内存,大小为16。
如下图: