文章目录
- 前言
- 一 自定义的分类
- 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.1struct
- 2.2 union
- 2.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={.age=18,.name="zhangsan"};//按照自己定义来初始化}1.1.3 结构体的特殊声明
在声明结构体时,可以不完全声明。
//匿名结构体struct{intage;charname[20];}s;//结构体变量匿名结构体只能使用一次
struct{charname[20];intage;}s1,s2;//这样是可以的,且只能在这里创建变量intmain(){//这里就不能创建结构体变量了return0;}这里我用两个一样成员的匿名结构体,它给了我一个警告说=两边的类型不兼容,我们没有类型名,不知道它是上面类型,编译器会把它们看作两个不同的类型。
匿名结构体初始化
struct{charname[20];intage;}s1={"lisi",100};//按照顺序struct{charname[20];intage;}s1={.age=20,.name="wangwu"};//按照自己定义struct{intnum;intage;charname[20];}s1;intmain(){s1.num=100;s1.age=20;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){inti=0;for(i=0;i<5;i++){printf("%d ",s.data[i]);}printf("\n%d\n",s.num);}voidprint2(structz*p){inti=0;for(i=0;i<5;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;};位段的大小:2+5+10+30=47个比特位,4个字节为32个比特位,不够,再分配4个字节。所以是8个字节
位段的内存分配:
- 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
- 位段在空间是按照需要int(4字节)或者char(1字节)来开辟的
- 位段的成员必须是 int、unsigned int 、signed int 或者char等类型。
位段的内存是没有标准的,不同的编译器处理可能存在差异
位段的跨平台问题
1.int 位段被当成有符号数还是无符号数是不确定的
2.位段中最⼤位的数⽬不能确定。(16位机器最大16,32位机器最大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.z=0x55后,变成了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中默认对齐数为8
- Linux中 gcc 没有默认对齐数,对齐数就是成员自身的大小
- 结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
普通的
嵌套结构体的
编译器的对齐数是可以修改的。
#pragmapack(1)//设置默认对齐数为1structS{//....};#pragmapack()//取消设置的默认对齐数,恢复原来对齐数将编译器对齐数修改为1后这个结构体的大小
2.2 union
- 联合体的大小至少是最大成员的大小。
- 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
满足最大成员大小是它的对齐数的整数倍
不满足最大成员的对齐数是整数倍时
2.3 enum
通过整型家族的取值范围来确定的。(一般为4字节)。不同的编译器可能会有不同的结过,这个是在vs上。