#前言
在C语言中存在两种类似的结构,它们分别是结构体和联合体。今天我们就来认识一下这两个结构的相关内容。
#结构体
1.结构体的声明
结构体的声明是使用struct关键字进行声明,具体代码如下:
typedef struct Stu{ char name[20]; int age; int grade; char Id_card[20]; }Stu;
其中typedef是重命名的关键字,其将结构体类型struct Stu重命名为了Stu,这样能便于我们后续的使用。但是如下的声明却是错误的,代码如下:
typedef struct Stu{ char name[20]; int age; int grade; char Id_card[20]; Stu* stu;//因还没将struct Stu重命名为Stu故不可将其提前使用 }Stu;//到此处才算完成重命名
正确的写法应是继续使用struct Stu* stu。
2.结构体的初始化
结构体有两种初始化的方法,我们就以上文的学生结构体为例来说明其声明方法。
第一种:
typedef struct Stu{ char name[20]; int age; int grade; char Id_card[20]; }Stu; Stu stu = { "zhangsan", 20,558,"350000000000"};
此时我们直接根据其对应类型在大括号内初始化即可。
第二种:
typedef struct Stu{ char name[20]; int age; int grade; char Id_card[20]; }Stu; Stu stu; stu.name[20] = "zhangsan"; stu.age = 20; stu.grade = 550; stu.Id_card[20] = "35000000000";
此时我们则将对应成员分别使用"."操作符进行赋值。
3.结构体大小的计算
结构体的内存大小计算与数组不同,并不是类型内所有元素的类型大小的总和,这是因为结构体会进行一种内存对齐,使其内存大小变的并不是那么容易计算,至于内存对齐的好处我们后面在谈。
现在我们先来说内存对齐的规则,具体规则如下:
同样的我们还是以上面的学生类型作为例子,计算其大小。在开始前我们还是要先了解一个概念没那就是对齐数 ,对齐数包括类型本身的大小(如果是数组就是其数组类型的大小)和编译器的默认对齐数,在vs2019中其默认对齐数为8,我们之后的计算就以其为标准。
typedef struct Stu{ char name[20];//0-19,其自身类型为char,对齐数为1,默认对齐数为8,故取对齐数为1,下列同理 int age;//20-23,对齐数为4 int grade;//24-27,对齐数为4 char Id_card[20];//28-47,对齐数为1 //此时计算处的总字节大小为48,又总字节要为最大对齐数的整数倍,故可为48. }Stu;
接下来,我们再来看一个例子:
struct A { char a;//对齐数为1,开始是位于偏移量为0的位置 int b;//对齐数为4,由于1不是4的倍数所以,此时从4开始偏移,4-7 double c;//对齐数为8,接下来的8恰好是8对齐数8的倍数,所以从8开始偏移,8-15 //此时总字节大小为16,恰好为此时最大对齐数8的倍数,故就为16. };
接下来我们来看一种特殊的情况,那就是结构体中嵌套一个结构体那么其大小又是多少呢?
struct A { char a; int b; double c; }; typedef struct Stu{ char name[20];//对齐数为1,0-19 int age;//对齐数为4,20-23 int grade;//对齐数为4,24-27 char Id_card[20];//对齐数为1,28-47 struct A a;//其大小为16,对齐数为内部元素的最大对齐数为8,48-63 //其总大小此时为64,为最大对齐数8的倍数,故为64可行。 }Stu;
4.有关对齐数
虽然编译器有自己的默认对齐数,但是我们是可以进行自行修改的,代码如下:
#pragma pack(4) struct A { char a; int b; double c; }; typedef struct Stu{ char name[20]; int age; int grade; char Id_card[20]; struct A e; }Stu; #pragma pack()
此时就将对齐数由8改为4。最后我们来说为什么存在对齐数呢?具体原因如下:
1.平台原因
不是所有硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定的数据类型,否则抛出硬件异常。
2.性能原因
数据结构(尤其是栈)应尽可能的在自然边界上对齐。原因在于,为了访问未对齐内存,处理器需要进行两此次内存访问操作,而对齐的内存访问仅需要一次操作,其本质上是一种以空间换取时间的做法。
#联合体
1.联合体的声明
联合体的声明与结构体的声明类似,我们在这里就不在多说了,只是将struct关键字改为union关键字,具体代码如下:
typedef union S { int a; char c; }S;
联合体的初始化与结构体不同,只有一直初始化方法,具体代码如下:
S s; s.a = 100; s.c = 'a';
2.联合体的大小
我们来看下面一段代码:
#include<stdio.h> typedef union S { char c; int a; }S; int main() { S s; s.a = 100; s.c = 'a'; printf("%c\n", s.c); printf("%d", s.a); return 0; }
这段代码将打印出怎样的结果呢?也许大多数人会认为就是打印出100与字符a,但其实打印出的结果如下:
那么这是为什么呢?这就和我们接下来讲到的联合体的大小有关了,其实联合体之所以叫联合体就是因为它内部的变量是共用一块空间的,当然所占的空间至少是其内部最大类型的大小并且需要根据最大对齐数进行调整(应为最大对齐数的整数倍),在此其大小就为4字节。所以我们在将是s.a赋值为100后又将字符a赋值给了s.c又字符a的ASCII码为97,故其将100所覆盖。具体可在调试中查看,如下:
#位段
1.位段的作用
位段的作用是通过对定义类型所需字节大小的确定,从而减少内存的消耗。
2.位段的声明
struct s {
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};
具体声明如上,后跟的数字单位为比特,表示其对应变量所需的内存空间大小。
3.位段声明后结构体的大小
如上图,开始时创建a变量,其大小为1字节==8bit,故其可供变量a与b使用,4+3<8。那么剩下的一个字节不够接下来的变量c使用,所以我们将这1字节舍弃不要,重新创建空间(其实在C语言中并没有明确规定是否将多余的这1字节舍弃,这里舍弃时因为在vs2019中确实时没有使用这1字节的空间,这要根据编译器而定)。所以接下来又创建了1个字节的空间,c使用了5个字节,剩余3个字节不够变量d使用,所以舍弃不要,我们在重新创建一块空间1字节空间来存放变量d。所以其总大小为3字节。