⭐️前面的话⭐️
大家好!在C语言中,有个叫“自定义类型”玩意,它究竟是什么呢?其实,就是字面意思,可以自己定义的类型就是自定义类型。具体说就是我们熟知的结构体,枚举,位段,联合体(共用体)。
📒博客主页:未见花闻的博客主页
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
📌本文由未见花闻原创,CSDN首发!
📆首发时间:🌴9月23日🌴
✉️坚持和努力一定能换来诗与远方!
💭参考书籍:📚《明解C语言》,📚《C语言程序设计现代方法》,📚《C primer plus》
💬参考在线编程网站:🌐牛客网🌐力扣
🙏作者水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!
博主的码云gitee,平常博主写的程序代码都在里面。
📌导航小助手📌
🍇1.结构体🍉 1.1结构体概述🍓1.1.1结构体概念🍓1.1.2 结构体的声明与使用 🍉 1.2结构体对齐及其大小计算🍓1.2.1偏移量🍓1.2.2结构体大小计算 🍉 1.3结构体与位段🍓1.3.1位段🍓1.3.2位段实现结构体 🍇2.枚举🍉 2.1枚举概述🍓2.1.1枚举概念🍓2.1.2枚举的声明与使用 🍉 2.2枚举大小计算🍉 2.3枚举与宏的区别 🍇3.联合体🍉 3.1联合体概述🍓3.1.1联合体概念🍓3.1.2联合体的声明与使用 🍉 3.2联合体大小计算划重点:
结构体,枚举,位段,联合体的定义。结构体实现位段。结构体,枚举,联合体内存对齐及其大小计算。
🍇1.结构体
🍉 1.1结构体概述
🍓1.1.1结构体概念
C 数组允许定义可存储相同类型数据项的变量,结构是 C 编程中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。
🍓1.1.2 结构体的声明与使用
结构体的声明与定义
//结构体struct Stu{char name[20];//姓名char sex[5];//性别char id[20];//学号int age;//年龄double grade;//成绩}student;
其中struct Stu
是自定义结构体类型,花括号内的一系列数据类型称为结构体成员
,student
为定义的结构体类型变量。
结构体的不完全声明
//匿名结构体类型struct{int a;char b;float c; }x;struct{int a;char b;float c;}a[20], *p;
上面的两个结构体声明时省略了结构体标签,但是只能跟在结构体声明后创建结构体变量,其他情况下是无法创建该结构体体变量的,这也就说明无法创建该结构体指针,就算再声明一个一模一样的结构体指针p
,将x
的地址赋值给p
编译器会发生报错,编译器会当做两个完全不同的结构体处理。
结构体的自引用
struct Node{int data;struct Node next;};
直接将结构体嵌套在相同结构体是不可取的,你可以想象一下,如果可以这么做,那这个结构体的大小是多少,他会无限制地嵌套下去,导致栈溢出,如果硬是需要嵌套自身,应该嵌套相同结构体类型的指针。
struct Node{int data;struct Node* next;};
在使用typedef
定义结构体自引用时,不能直接在结构体中使用typedef
替换的结构体类型名直接定义结构体指针,而是应该使用原类型名定义在结构体中定义结构体指针.
//错误示例:typedef struct NODE{int data;Node* next; }Node;//正确示例:typedef struct NODE{int data;struct NODE* next; }Node;
结构体的使用与传参
#include <stdio.h>int main(){struct Stu s = {"张三","男","0618",20,99 };//结构体声明与初始化printf("姓名:%s\n", s.name);//结构体成员访问方式1:结构体变量名.结构体成员名printf("性别:%s\n", s.sex);struct Stu* ps = &s;printf("学号:%s\n", ps->id);//结构体访问成员方式2:结构体指针->结构体成员名printf("年龄:%d\n", ps->age);printf("C语言考试成绩:%.2lf\n", ps->grade);return 0;}
运行结果:
姓名:张三性别:男学号:06龄:20C语言考试成绩:99.00D:\gtee\C-learning-code-and-project\test_922\Debug\test_922.exe (进程 26272)已退出,代码为 0。按任意键关闭此窗口. . .
对于结构体传参,尽量使用结构体指针传参,因为直接传结构体,当结构体很大时,会占用大量栈区的空间,甚至导致栈溢出。
🍉 1.2结构体对齐及其大小计算
🍓1.2.1偏移量
计算机汇编语言中的偏移量定义为:把存储单元的实际地址与其所在段的段地址之间的距离称为段内偏移,也称为"有效地址或偏移量"。
🍓1.2.2结构体大小计算
结构体的大小不是将所有成员变量的大小加起来这么简单,实际上在内存中各成员是需要对齐的,在vs编译器中默认对齐值为8
,在其他的编译环境中可能没有默认值,也可以默认值是其他的值。
当然,这个默认对齐值是可以修改的,可以使用"pragma pack()"修改。
如果对齐数为1
,相当于结构体没有内存对齐。
#pragma pack(4)//设置默认对齐数为4#pragma pack()//无参数表示默认值为8(vs编译器)
那为什么需要对内存对齐呢?
平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
结构体内存对齐规则:
结构体的首成员与结构体的偏移量为0
。结构体成员需要对齐到对齐数的整数倍地址(偏移量)处。每个结构体成员都有一个对齐数,对齐数为编译器默认值与结构体成员大小中的较小值。结构体的总大小为最大对齐数(结构体各成员对齐数中最大的一个对齐数)的整数倍。如果结构体中存在嵌套结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
//练习1struct S1{char c1;int i;char c2;};//练习2struct S2{char c1;char c2;int i;};//练习3struct S3{double d;char c;int i;};//练习4-结构体嵌套问题struct S4{char c1;struct S3 s3;double d;};
练习1:char c1
为首个结构体成员,大小为1
,偏移量为0
,对齐数为1
,存入内存位置如下:
第二个结构体成员int i
,大小为4
,对齐数为4
,所以需存入偏移量为4
处的地址处。
第三个结构体成员char c2
,大小为1
,对齐数为1
,偏移量8
为1
的整数倍,所以c2
存入偏移量为8
的位置。
存入c2
后结构体一共使用了9
个字节,但是结构体大小需为最大对齐数的整数倍,该结构体中最大对齐数为4
,所以结构体大小应该为4
的整数倍,大小为12
。
练习2:这个较练习1中的结构体,将两个char
类型变量提前了,也就是将较小的成员变量放到了一起。我们来看看这个结构体相比于练习1的结构体大小会不会有变化吧!
第一个结构体成员c1
与练习1一样还是老位置,存入第二个结构体成员c2
时,大小为1
,偏移量为1
,对齐数为1
满足整数倍关系,因此c2
存入c1
的后。第三个成员大小为4
,偏移量为2
,对齐数为4
,不满足对齐数为偏移量整数倍关系,所以要从偏移量为4
的位置开始存,存完之后结构体一共用了8
个字节,满足为最大对齐数整数关系,所以这个结构体大小为8
。
我们发现当把空间小的结构体成员尽量放在一起是,结构体的大小变小了,所以我们在定义结构体时尽量将空间小的结构体成员集中在一起。
练习3:double
为8
字节,存入后偏移量变为了8
,然后存入char
大小为1
,存入后偏移量为9
,最后存入int
大小为4
,对齐数为4
,从偏移量为12
开始存入,结构体一共使用了16
字节,满足为最大对齐的整数倍,所以结构体大小为16
。
练习4:这个结构体里面嵌套了一个结构体S3
,这个结构体的最大对齐数为8
,大小为16
。首先存一个char
,大小为1
,存完后偏移量为1
,然后存S3
,大小为16
,最大对齐数为8
,所以S3
的对齐数为8
,所以从偏移量为8
的地址开始存,存完后,偏移量为24
,最后存double
,大小为8
,对齐数为8
,从偏移量为24
的地址开始存,存完后,结构体一共使用了32
字节,满足最大结构体成员的整数倍,所以该结构体大小为32
。
int main(){printf("%d\n", sizeof(struct S1));printf("%d\n", sizeof(struct S2));printf("%d\n", sizeof(struct S3));printf("%d\n", sizeof(struct S4));return 0;}
运行结果:
1281632D:\gtee\C-learning-code-and-project\test_922\Debug\test_922.exe (进程 34308)已退出,代码为 0。按任意键关闭此窗口. . .
🍉 1.3结构体与位段
🍓1.3.1位段
位段,也称位域,C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为"位段"或称"位域"( bit field) 。利用位段能够用较少的位数存储数据。
🍓1.3.2位段实现结构体
位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是 int、unsigned int 或signed int 。
2.位段的成员名后边有一个冒号和一个数字。
struct A {int _a:2;int _b:5;int _c:10;int _d:30;};
这个使用位段实现的结构体大小多大?如果按前面所说的方法计算结构体大小,得出的结果是16
,但是真是如此吗?
我们使用程序来计算一下这个结构体大小,运行结果:
8D:\gtee\C-learning-code-and-project\test_922\Debug\test_922.exe (进程 22660)已退出,代码为 0。按任意键关闭此窗口. . .
大小为8
,并不是16
,其实结构体成员名冒号后面那个数,其实是占用的空间大小,单位为bit,在使用位段实现的结构体中,内存是一个一个对应数据类型开辟的,比如上面这个结构体A,先开辟4
字节大小空间也就是32
bit大小,然后存_a
,占2
bit,还剩30
bit,再存_b
,占5
bit,还剩25
bit,再存_c
,占10
bit,还剩15
bit,最后存_d
占30
bit,但是第一次开的那一块内存不够用了,所以需要再开辟一块大小为4
字节(32
bit)的空间,来存入_d
,在整个结构体中一共开辟了8
字节的空间,所以结构体大小为8
。
位段在内存中的分配
位段的成员可以是int unsigned int
signed int
或者是char
(属于整形家族)类型位段的空间上是按照需要以4个字节(int
)或者1个字节(char
)的方式来开辟的。位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
其实在C语言标准中并没有给出位段具体开辟内存空间的细节,我们来尝试探究一下位段内存分配细节。
位段跨平台问题:
int
位段被当成有符号数还是无符号数是不确定的。位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
位段的应用:
🍇2.枚举
🍉 2.1枚举概述
🍓2.1.1枚举概念
enum,枚举在C/C++/c#,还有Objective-C中,是一个被命名的整型常数的集合,枚举在日常生活中很常见。例如表示星期的SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY,SATURDAY, 就是一个枚举。枚举的说明与结构和联合相似。
🍓2.1.2枚举的声明与使用
关键字:enum
enum Day//星期{Mon,Tues,Wed,Thur,Fri,Sat,Sun};enum Sex//性别{MALE,FEMALE,SECRET};enum Color//颜色{RED,GREEN,BLUE};
以上定义的 enum Day , enum Sex , enum Color 都是枚举类型。
{}中的内容是枚举类型的可能取值,也叫枚举常量
。
这些枚举常量都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。
enum Color//颜色{RED=1,GREEN=2,BLUE=4};
枚举的使用
enum Color//颜色{RED=1,GREEN=2,BLUE=4};enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。clr = 5;//错误,枚举常量不可被赋值
🍉 2.2枚举大小计算
枚举变量的大小,即枚举类型所占内存的大小,枚举类型变量占4
字节。
enum A{QSW,BSW,CWS}a;int main(){printf("%d\n", sizeof(a));return 0;}
4D:\gtee\C-learning-code-and-project\test_922\Debug\test_922.exe (进程 21156)已退出,代码为 0。按任意键关闭此窗口. . .
🍉 2.3枚举与宏的区别
使用枚举定义的枚举常量是有类型的,为枚举类型,而使用#define
宏是替换,并没有枚举类型这种性质。
我们可以使用#define
定义常量,为什么非要使用枚举?
枚举的优点:
增加代码的可读性和可维护性和#define
定义的标识符比较枚举有类型检查,更加严谨。防止了命名污染(封装)。便于调试。使用方便,一次可以定义多个常量。
🍇3.联合体
🍉 3.1联合体概述
🍓3.1.1联合体概念
在进行某些算法的C语言编程的时候,需要使几种不同类型的变量存放到同一段内存单元中。也就是使用覆盖技术,几个变量互相覆盖。这种几个不同的变量共同占用一段内存的结构,在C语言中,被称作"共用体"类型结构,简称共用体,也叫联合体。
🍓3.1.2联合体的声明与使用
关键字:union
联合也是一种特殊的自定义类型.
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。
//联合类型的声明union Un{char c;int i;};//联合变量的定义union Un un;
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。
使用联合体判断大小端:
int is_bl(){union BL{char a;int b;}un;un.b = 1;//返回1为小端,返回0位大端if (un.a == 1){return 1;}else{return 0;}}int main(){int ret = is_bl();if (ret == 1){printf("小端\n");}else{printf("大端\n");}return 0;}
VS编译器使用的是小端:
小端D:\gtee\C-learning-code-and-project\test_922\Debug\test_922.exe (进程 23328)已退出,代码为 0。按任意键关闭此窗口. . .
🍉 3.2联合体大小计算
联合的大小至少是最大成员的大小。当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。union Un1{char c[5];int i;};union Un2{short c[7];int i;};//下面输出的结果是什么?printf("%d\n", sizeof(union Un1));printf("%d\n", sizeof(union Un2));
联合体Un1
最大成员大小为5
,char
类型对齐数为1
,int
类型对齐数为4
,所以最大对齐数为4
,联合体大小应为最大对齐数的整数倍,大小为8
。
联合体Un2
最大成员大小为14
,short
类型对齐数为2
,int
类型对齐数为4
,所以最大对齐数为4
,联合体大小应为最大对齐数的整数倍,大小为16
。
运行结果:
816D:\gtee\C-learning-code-and-project\test_922\Debug\test_922.exe (进程 12864)已退出,代码为 0。按任意键关闭此窗口. . .
觉得文章写得不错的老铁们,点赞评论关注走一波!谢谢啦!