目录
一、结构体类型
1.认识结构体
2.如何使用结构体类型
2.1按顺序初始化结构体,并通过.符号访问
2.2无序初始化结构体,并用.符号访问
2.3通过地址的方式访问结构体变量成员
2.4结构体的自引用
2.4.1错误的自引用
2.4.2正确的自引用
3.结构体内存对齐
3.1结构体对齐的规则
3.2练习
4.拓展:结构体实现的位段操作
4.1什么是位段
4.2位段有什么用?
4.3.位段的跨平台问题
二、枚举类型
1.枚举类型的定义
2.枚举的优点
三、联合体类型
1.联合体类型的定义
2.联合体类型的特点
一、结构体类型
1.认识结构体
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
如何创建一个结构体呢,如下代码便创建了一个结构体
struct abc//abc是这个结构体的名字
{int a;//这个a,b,c是结构体的成员列表char b;float c;
}abc1;
//abc1为创建的一个该结构体类型的变量,你也可以在别的地方定义这个变量,因为你已经创建了一个新的类型,一个新的结构体类型
2.如何使用结构体类型
创建完了一个结构体类型,那我们应该这样去使用这个结构体类型呢?
2.1按顺序初始化结构体,并通过.符号访问
#include<stdio.h>
struct abc//abc是这个结构体的名字
{int a;//这个a,b,c是结构体的成员列表char b;float c;
}abc1;
//abc1为创建的一个该结构体类型的变量,你也可以在别的地方定义这个变量,因为你已经创建了一个新的类型,一个新的结构体类型
int main()
{struct abc a1 = { 20,'b',5.2 };//按顺序初始化结构体//struct abc 此时就是声明类型可类比成intprintf("%d %c %f\n", a1.a, a1.b, a1.c);//将内容打印出来,用.这个符号直接访问结构体变量的成员
}
2.2无序初始化结构体,并用.符号访问
#include<stdio.h>
struct abc//abc是这个结构体的名字
{int a;//这个a,b,c是结构体的成员列表char b;float c;
}abc1;
//abc1为创建的一个该结构体类型的变量,你也可以在别的地方定义这个变量,因为你已经创建了一个新的类型,一个新的结构体类型
int main()
{struct abc a2 = {.b='c',.a=10,.c=3.14};//通过.符号来无序的初始化结构体变量printf("%d %c %f\n", a2.a, a2.b, a2.c);//将内容打印出来
}
2.3通过地址的方式访问结构体变量成员
#include<stdio.h>
struct abc//abc是这个结构体的名字
{int a;//这个a,b,c是结构体的成员列表char b;float c;
}abc1;
//abc1为创建的一个该结构体类型的变量,你也可以在别的地方定义这个变量,因为你已经创建了一个新的类型,一个新的结构体类型
int main()
{struct abc a3 = { .a = 60,.c = 9.9,.b = 'a' };//初始化结构体变量struct abc* address = &a3;//创建一个名字为address的struct abc类型的结构体指针来储存a3这个结构体变量的地址printf("%d %c %f\n", address->a,address->b,address->c);//通过->访问地址的方式将内容打印出来
}
2.4结构体的自引用
2.4.1错误的自引用
#include<stdio.h>
struct abc
{int a;struct abc next;//结构体里存放一个结构体
};
小明觉得在结构体里存放一个结构体,这样子创建出多个结构体后,就能将多个结构体存放到一个结构体中,一环扣一环,通过一个结构体访问所有的结构体,这听上去好像还有几分道理,但你要明白,这个结构体里面,还有一个这个类型的结构体,而这个被包含的结构体里面也有,被包含的结构体里面的那个结构体也有......子子孙孙无穷尽也,可以类比成死递归,没有人知道什么时候穷尽,所以计算机该给它分配多少个字节的空间,计算机根本不知道,所以这很显然是错误的。
2.4.2正确的自引用
#include<stdio.h>
struct abc
{int a;struct abc* a1;
//结构体里存放一个结构体的地址,而被存放的结构体也能存放一个结构体的地址
//那么有一个结构体,便可链接所有这个结构体类型的变量
//实现一个访问所有
};
只要你是个地址你所占字节的大小不是4(32位)就是8(64位),因此计算机可以分配给它空间,这个结构体和上面那个错误的结构体最大的区别就是一个存放的是地址,一个存放的是内容
3.结构体内存对齐
话不多说,先上题目
#include<stdio.h>
struct abc
{char a;int b;
};
int main()
{printf("%d", sizeof(struct abc));
}
你不妨猜测一下打印出来的结果是多少,之前没遇到过这种题型的宝子们,答案应该就是5吧,char占一个字节,int占4个字节,瞧瞧多合理啊,但结果是
是8,没想到吧,你的心里现在一定有个大大的疑惑,为什么呢?
这就要说到结构体对齐的问题了,首先我们要知道结构体对齐后长什么样才能够计算出结构体的正确大小
3.1结构体对齐的规则
(1) 第一个成员在与结构体变量偏移量为0的地址处。
(2) 其他成员变量要对齐到某个对齐数的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。
VS中默认的值为8
(3) 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
(4) 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
有的小伙伴恐怕不知道什么是偏移量,针对结构体的情况,偏移量就是相对于你所创建的结构体首地址的距离,比方说第一天规则,第一个成员要在偏移量为0的地址处,偏移量为0,那么很显然,就是没偏移嘛,就是在首地址存放。所以第一条规则我们可以直接立即理解为第一个成员放首地址,非常的符合常识。
后面的三条讲解起来太抽象了,这里画个图给大家
假设这是我存放结构体的内存空间,图中的数字为偏移量
按照第一条规则先把char a放进去,占一个字节的大小,那么我们接下来放int,根据第二条规则,int类型的大小为4,相比vs默认的8较小,所以它的对齐数应为4,故我们得找到偏移量为4的倍数的位置,并在那开始存储int b,因此4,5,6,7偏移量的位置就被int b所占据了,1,2,3则是被用来被浪费掉了,故打印出来的结果是8
3.2练习
#include<stdio.h>
struct abc
{char a;int b;char c;
};
int main()
{printf("%d", sizeof(struct abc));
}
第一步,将char a放进首地址即偏移量为0所在的空间,占了1,下一个空间的偏移量为1,因int型的对齐数为4,故对齐到偏移量为4时存储,存放4个字节,4,5,6,7的空间被占据,故最后char c会存放在偏移量为8的位置,空间大小好像是9个字节,但别忽略了第三条,结构体总大小为最大对齐数的整数倍,这里的最大对齐数,显然是4,故结构体大小应为12
最后来一道复杂点的
#include<stdio.h>
struct abc
{char a;int b;char c;
};
struct xyz
{char x;float y;struct abc xyz;
};
int main()
{printf("%d", sizeof(struct xyz));
}
首先将char x放进偏移量为0所在的空间,y的对齐数为4,故从偏移量为4的位置存储,偏移量为4,5,6,7的空间被float y使用,接着是结构体abc类型的存储,根据第四条,结构体在存储之前也得先对齐,观察abc,发现abc的最大对齐数为4,刚好我们此时的位置就是偏移量为8的位置,是4的倍数,故直接存储即可。先将char a存放进去,偏移量为8的空间被占据,再存放int b,此时,到了偏移量为9的空间,先对齐到偏移量为4的倍数的位置,即12,故12,13,14,15的空间被int b占据,偏移量为16的位置被char c占据,结构体xyz的空间似乎已经确实,为17,但根据第四条规则,结构体的大小,为所有最 大对齐数的整数倍,即4的倍数,为20
4.拓展:结构体实现的位段操作
4.1什么是位段
位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是 int、unsigned int 或signed int(其实char也可以,因为字符型在内存中的存储使用的是ASCII码值的形式,可以这样理解,一个一个的字符,为一个一个的数)
2.位段的成员名后边有一个冒号和一个数字。
#include<stdio.h>
struct abc
{int a : 3;
//冒号后面的数字代表的二进制位的数量,这里可看作用3个二进制位存储整型aint b : 20;int c : 20;
};
int main()
{printf("%d", sizeof(struct abc));
}
试想一下,这里打印出来的结果是多少,存储int a我们用了3个二进制位,存储int b用了20个二进制位,存储int c也用了20个二进制位,那么一共用了43个二进制位(bit位),而一个字节为8个bit位,那么是不是就用了6个字节来存储这个结构体呢?
在vs上,a用了3个二进制存储,b用了20个二进制位存储,此时还没达到32bit位,但是当把c用20个二进制位存储的时候就会发现,超出一个整型的大小了,vs采用的方式便是再开辟一个整型的空间给你存放这个c,之前剩下的就不要了。但由于c语言对于位段粗糙的定义,导致在不同的编译器有不同的实现,有的编译器秉承着不浪费的原则,先用完之前剩下的空间再开辟,所以位段的使用尽量不要跨平台。要注意的一点:先位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟。在vs上只有char时按1个字节开辟,当既有char又有int或者只有int的时候按4个字节开辟。
4.2位段有什么用?
聪明的小伙伴恐怕早就想到了,3这个数字,我用2个bit位就能够存放,但是我创建了一个整型,我就用了32个bit位去存放,因此,位段可以很好的节省空间。
4.3.位段的跨平台问题
(1)int 位段被当成有符号数还是无符号数是不确定的。
(2) 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机
器会出问题。
(3) 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
(4) 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是
舍弃剩余的位还是利用,这是不确定的。
二、枚举类型
1.枚举类型的定义
#include<stdio.h>
enum sex
{male,female,no
};//这里就定义了一个关于性别的枚举类型,它会按照从0开始的顺序给male,female,no赋值
int main()
{int arr1[no] = {9,7};//值得再次强调的一点,它定义出来的是常量(而非常变量),可以用在数组中printf("%d %d %d", male, female, no);
}
当然你也可以自己给它们赋值,不用根据默认的来
#include<stdio.h>
enum sex
{male=3,female,no=99
};//这里就定义了一个关于性别的枚举类型,它会按照从0开始的顺序给male,female,no赋值
int main()
{int arr1[no] = {9,7};//值得再次强调的一点,它定义出来的是常量(而非常变量),可以用在数组中printf("%d %d %d", male, female, no);
}
2.枚举的优点
1. 增加代码的可读性和可维护性2. 和 #define 定义的标识符比较枚举有类型检查,更加严谨。3. 防止了命名污染(封装)4. 便于调试5. 使用方便,一次可以定义多个常量
三、联合体类型
1.联合体类型的定义
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。
union abc
{int a;int b;char c;
};
int main()
{union abc x;x.a = 20;x.b = 10;x.c = 'a';printf("%d %d %c\n",x.a,x.b,x.c);printf("%d\n", x.a);printf("%d\n", x.b);
}
由此可知,改变联合体上的一个数就会导致牵一发而动全身的效果
2.联合体类型的特点
(1)牵一发而动全身
(2)所有的数据都从同一个地址开始存储
union abc
{int a;int b;char c;
};
int main()
{union abc x;x.a = 20;x.b = 10;x.c = 'a';printf("%p %p %p\n", &(x.a),&(x.b),&(x.c));
}
今天的分享到这里就结束了,感谢各位友友的来访,祝各位友友前程似锦O(∩_∩)O