文章目录前言一 自定义的分类1.1 struct的使用1.1.1 结构体的声明1.1.2 结构体的初始化1.1.3 结构体的特殊声明1.1.4 结构体传参1.1.5 结构体实现位段1.2 union的使用1.2.1 union的声明1.3 enum的使用1.3.1 enum的声明1.3.1 enum和#define的区别二、 怎么计算它们的大小2.1struct2.2 union2.3 enum前言本章主要讲解c语言中自定义类型的使用计算它们的大小一 自定义的分类名称类型结构体struct联合体union枚举enum数组-1.1 struct的使用之前我写过它的使用现在回忆一下结构体是一些成员变量的集合它的成员可以是不同的类型。1.1.1 结构体的声明它的成员不允许没有。structStu{charname[20];intage;}s1,s2;//这里的s1,s2为全局变量1.1.2 结构体的初始化structStu{charname[20];intage;}s1,s2;intmain(){structStus{0};//初始化为0后面再进行赋值structStus1{zhangsan,18};//按照顺序来初始化structStus1{.age18,.namezhangsan};//按照自己定义来初始化}1.1.3 结构体的特殊声明在声明结构体时可以不完全声明。//匿名结构体struct{intage;charname[20];}s;//结构体变量匿名结构体只能使用一次struct{charname[20];intage;}s1s2;//这样是可以的且只能在这里创建变量intmain(){//这里就不能创建结构体变量了return0;}这里我用两个一样成员的匿名结构体它给了我一个警告说两边的类型不兼容我们没有类型名不知道它是上面类型编译器会把它们看作两个不同的类型。匿名结构体初始化struct{charname[20];intage;}s1{lisi,100};//按照顺序struct{charname[20];intage;}s1{.age20,.namewangwu};//按照自己定义struct{intnum;intage;charname[20];}s1;intmain(){s1.num100;s1.age20;strcpy(s1.name,zhangsan);//只能使用字符串函数赋值不能像上面的数字这样赋值return0;}当有类型重命名时typedefstruct{intnum;intage;charname[20];}s1;intmain(){s1 s;//可以创建return0;}下面这种情况是错误的typedef对匿名结构体重命名为Node时成员就是用了Node类型来创建变量这样是不行的。解决方法使用有名字的结构体。typedefstruct{intdata;Node*next;}Node;解决方法typedefstructNode{intdata;structNode*next;}Node;1.1.4 结构体传参2会更好它更节约时间和空间。1是使用形参的方式形参也会临时拷贝一个4000字节的空间。2是传的指针指针大小是4或者8。structz{intdata[1000];intnum;};voidprint1(structzs){inti0;for(i0;i5;i){printf(%d ,s.data[i]);}printf(\n%d\n,s.num);}voidprint2(structz*p){inti0;for(i0;i5;i){printf(%d ,p-data[i]);}printf(\n%d,p-num);}intmain(){structzs{{1,2,3,4,5},100};print1(s);print2(s);return0;}1.1.5 结构体实现位段1.什么是位段位段和结构体类型是类似的有两个不同位段的成员必须是 int、unsigned int 、signed int 或者char等类型。位段的成员名后边有一个冒号和一个数字。位段的声明structS{inta:2;//代表a只占两个比特位intb:5;intc:10;intd:30;};位段的大小25103047个比特位4个字节为32个比特位不够再分配4个字节。所以是8个字节位段的内存分配位段涉及很多不确定因素位段是不跨平台的注重可移植的程序应该避免使用位段。位段在空间是按照需要int4字节或者char1字节来开辟的位段的成员必须是 int、unsigned int 、signed int 或者char等类型。位段的内存是没有标准的不同的编译器处理可能存在差异位段的跨平台问题1.int 位段被当成有符号数还是无符号数是不确定的2.位段中最⼤位的数⽬不能确定。16位机器最大1632位机器最大32如果我们上面的位段d为30时在16位机器会出问题。因为16位最多能有16个比特位3.位段中的成员在内存中从左向右分配还是从右向左分配标准尚未定义。4.当一个结构包含两个位段第二个位段成员比较大无法容纳于第一个位段剩余的位时是舍弃剩余的位还是利用这是不确定的。位段的注意事项1.位段的几个成员共有同一个字节这样有些成员的起始位置并不是某个字节的起始位置那么这些位置处是没有地址的。内存中每个字节分配一个地址一个字节内部的bit位是没有地址的。2.不能对位段的成员使用操作符这样就不能使用scanf直接给位段的成员输入值只能是先输入放在一个变量中然后赋值给位段的成员。1.2 union的使用1.2.1 union的声明特点所有成员共用同一块内存空间。unionUn{charc;inti;};我们可以从图中看出它们的地址是一样的所以它们确实共用一块空间我们从图中看到我的i原来是0x11223344但是在当执行完k.z0x55后变成了0x11223355。说明z的数据可以覆盖掉了我原来i中最后一个字节的数据。上面红色框里面的是k.i的地址下面是k.z的地址它们数据也相同再次说明它们是共用一块空间的。1.3 enum的使用1.3.1 enum的声明枚举 可能取值可以一一列举比如说一周的天数、三原色、一年的月数等enumDay{mon,//枚举常量tues,wed,thur,fri,sat,sun};enumSex{male,female,secret};这些可能取值都是有值的默认从0开始依次递增1当然在声明枚举类型的时候也可以赋初值。但是我们不能在主函数中这样写MALE 7常量是不能被修改的。1.3.1 enum和#define的区别#defineMALE0#defineFEMALE1#defineSECRET2既然#define也可以定义常量我们为什么要使用enum呢枚举的优点1.使用方便一次可以定义多个常量2.便于调试预处理阶段会删除 #define 定义的符号它会在预处理时将#define都替换成常量。3.和#define定义的标识符比较枚举有类型检查更加严谨。4.增加代码的可读性和可维护性5.枚举常量是遵循作用域规则的枚举声明在函数内只能在函数内使用我们可以从图中看到当我们想实现计算机的加减乘除时枚举常量更有可读性如果是#define的话它会在预处理时将加减乘除替换成数字。我们从图中看到我们想把0复制给左边的Calc但是编译器会报错误编译器认为你要把整数0赋值给Calc。说明enum确实有类型检查。二、 怎么计算它们的大小2.1struct首先要计算结构体大小我们需要了解它的对齐规则对齐规则结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处其他成员变量要对齐到某个数字对齐数的整数倍的地址处。对齐数 编译器默认的一个对齐数 与 该成员变量大小的较小值。在vs中默认对齐数为8Linux中 gcc 没有默认对齐数对齐数就是成员自身的大小结构体总大小为最大对齐数结构体中每个成员变量都有一个对齐数所有对齐数中最大的的整数倍。如果嵌套了结构体的情况嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处结构体的整体大小就是所有最大对齐数含嵌套结构体中成员的对齐数的整数倍。普通的嵌套结构体的编译器的对齐数是可以修改的。#pragmapack(1)//设置默认对齐数为1structS{//....};#pragmapack()//取消设置的默认对齐数恢复原来对齐数将编译器对齐数修改为1后这个结构体的大小2.2 union联合体的大小至少是最大成员的大小。当最大成员大小不是最大对齐数的整数倍的时候就要对齐到最大对齐数的整数倍。满足最大成员大小是它的对齐数的整数倍不满足最大成员的对齐数是整数倍时2.3 enum通过整型家族的取值范围来确定的。(一般为4字节)。不同的编译器可能会有不同的结过这个是在vs上。