C语言中有许多的自定义类型包括结构体、位段、枚举、联合,今天我们会来进行一个全面的解析。
首先是结构体,要认识结构体首先要知道怎么创建结构体,用创建一个学生stu的结构体举例,我们对结构体的声明可以参考下面的形式:
struct stu
{
};
在大括号里是结构体成员的标识,在分号前面可以创建结构体变量并对其进行初始化。比如说我们想让学生这个结构体里面包括字符数组姓名,整型变量年纪,并创建一个学生的结构体变量s1,并初始化名字张三,年纪18岁。我们就可以参考下面这种写法:
struct stu
{
char name[20];
int age;
}s1 = {“张三” , 18};
如果这个声明写在全局的话,那么这个s1也成为了全局变量。如果这个函数写在程序内部,那么s1就是局部变量。结构体的变量可以省略struct后面的名字这样的格式叫做匿名结构体,匿名结构体的使用通常是一次性的,因为结构体是匿名的,两次创建变量即便内容一样类型也是不一样的。
结构体的难点在于怎么结构体的自引用和计算空间大小。结构体自引用就是在结构体的成员变量中引用自己,引用时包括自己的下一个元素next,next的类型不可使用结构体类型,要使用结构体指针类型,因为使用结构体类型的话,next里面又会包括next+1,next+1里面又会有next+2,死循环。而使用指针就不会有这种困扰。
另一个难点是关于结构体的空间大小计算,这里要引入对齐的概念,结构体的各个成员是需要对齐的,所以结构体的空间大小并不能是无脑的各个成员内存大小加和,这样是为了方便计算机读取操作,用空间换时间。具体的实现的话要遵循下列的几条规则:
1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8, Linux中没有默认对齐数,对齐数就是成员自身的大小
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
我们用例子来解释上面的规则:
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
这里我们创建了两个结构体变量,我们依次分析,先看s1,第一条规则c1在偏移量为0的地址处,如果没有对齐,i应该在偏移量为1的地方,但是这里需要对齐,按照规则二和规则三,i要发生偏移,i的大小是4个字节,默认对齐数是8个字节取较小值就是4,所以i在偏移量为4的地址处对齐,占有的字节偏移量分别为4,5,6,7。c2的大小为1个字节,默认为8,所以对齐数为1,所以就是在偏移量为8处对齐,这样算下来一共是9个字节,按照规则四,对齐数最大值是4,所以最后结构体的大小应该大于9并且是4的倍数,那就是12个字节。
那么我们学会了s1的分析,s2就很容易了,c1对齐数1,c2对齐数1,i对齐数4,那么偏移量c1为0,c2为1,i为4,这样一共8个字节,最大对齐数4,所以8满足倍数关系,所以结构体大小就是8。在C语言中我们可以用一个预处理指令来指定默认对齐数,#progma pack()。
结构体传参也是需要注意的点,因为结构体大小普遍比较大,比如结构体成员放一个int [1000]这样的成员,如果直接创建结构体形参,会导致系统开销很大,所以我们一般都传结构体指针。
接下来讲一下位段,位段也属于是结构体的一种功能,位段的成员类型必须是整型家族,写法和结构体类似,如下所示:
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};
结构体成员后面会用冒号标识这个变量需要多少字节的空间,但是C语言并没有规定各个变量在内存里排布顺序和当一个字节剩下的空间不够存放下一个变量时,是选择追加一个字节,两个字节各存一部分,还是直接放弃这个字节。所以位段不跨平台,一般只有在网络中的数据打包时会用来压缩空间。了解这些即可。
接下来要讲的是枚举,枚举其实很简单。先了解一下写法:
enum Day
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
这样就完成了一个枚举类型的创建。在使用中我们对于枚举类型的数据只能使用枚举出的内容对其进行赋值,比如:
enum Day c = Mon;
枚举常量的编号默认从0递增,如果某个值被赋值的话,之前的常量不受影响,之后的按照被修改的数递增。
要注意的就只有这些,那么枚举有什么有点呢?
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
3. 便于调试
4. 使用方便,一次可以定义多个常量
除了上面的内容,自定义操作类型还有一个联合类型,联合内容也很简单,我们还是举例子:
union Un
{
char c;
int i;
};
在联合类型中成员是公用内存的,比如上面的c和i,c一个字节,i四个字节,c就存在i的第一个字节里面,对c改动会影响i的值,对i改动会影响c的值。联合类型变量也是存在对齐的,但是这里的对齐比较简单,对齐规则是
1、联合的大小至少是最大成员的大小。
2、当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
比如
union Un1
{
char c[5];
int i;
};
这里的数组5个字节,整型4个字节,共用空间只需要5个字节就够了,但是最大对齐数为4,联合体大小需要是4的倍数,也就是8。
终于讲完了......