💛 前情提要💛
本章节就进入C语言的核心:深度剖析C语言自定义类型(结构体类型+枚举类型+联合体类型)
接下来我们即将进入一个全新的空间,对代码有一个全新的视角~
以下的内容一定会让你对C语言有一个颠覆性的认识哦!!!
以下内容干货满满,跟上步伐吧~
作者介绍:
🎓作者:热爱编程不起眼的小人物🐐
🔎作者的Gitee:代码仓库
📌系列文章推荐:
实现Strcpy函数 - 通过函数发现 “程序之美” | 不断优化、优化、再优化~
《刷题特辑》—实现由小白至入门者的学习记录🥰
【C语言】数据在内存中的存储_ [进阶篇_复习专用]
【C语言】字符函数&字符串函数&内存函数(上)[进阶篇_复习专用]
【C语言】字符函数&字符串函数&内存函数(下)[进阶篇_复习专用]
📒我和大家一样都是初次踏入这个美妙的“元”宇宙🌏 希望在输出知识的同时,也能与大家共同进步、无限进步🌟
📌导航小助手📌
💡本章重点🍞一.结构体类型🥐Ⅰ.结构体类型的声明🥐Ⅱ.结构体的特殊声明🥐Ⅲ.结构体的自引用🥐Ⅳ.结构体的空间大小🥯Ⅴ.总结🍞二.位段🥐Ⅰ.什么是位段🥐Ⅱ.位段的内存分配🥐Ⅲ.位段的应用🥯Ⅳ.总结🍞三.枚举类型🥐Ⅰ.枚举类型的定义🥐Ⅱ.枚举类型的优点🥯Ⅲ.总结🍞四.联合体(共用体)🥐Ⅰ.联合类型的定义🥐Ⅱ.联合的大小&特点🥐Ⅲ.联合体的使用🥯Ⅴ.总结🫓总结
💡本章重点
结构体类型
枚举类型
联合体(共用体)类型
🍞一.结构体类型
🥐Ⅰ.结构体类型的声明
💡在深入了解结构体之前呀,让我们先来了解什么是结构体:
➡️简单来说:是C语言提供给程序员去创造一个创造属于自己类型的关键字
结构体
:就是不同类型的集合,这些结构体里面的类型称为成员变量
👉结构体的创建:
struct tag{member-list;} variable-list;
❗由上,我们可得知三点:
1️⃣tag
为结构体的标签名
,即给结构体创建一个名字【与struct
合起来一起创建了一个结构体类型
】
2️⃣member-list
为成员变量列表
3️⃣variable-list
为创建的结构体变量
列表
💫Eg:描述一个人【人有很多属性】
名字
年龄
性别
身高
struct People{char name[20];//名字int age;//年龄char sex[5];//性别int height;//身高};
💥特别注意:
1️⃣结构体声明的同时,在结构体变量列表内定义的结构体类型变量为全局变量
2️⃣在主函数定义的结构体类型变量为局部变量
✨所以:
结构体可以用来描述一个多元物体的信息~
🥐Ⅱ.结构体的特殊声明
💡结构体特殊声明:
匿名结构体类型
➡️简单来说:匿名结构体类型就是没有了tag【标签】
👉特殊情况:
struct{char c;int i;char ch;double d;}s;struct{char c;int i;char ch;double d;}* ps;
❓同学们结合上述代码,觉得下列代码可以正常执行吗
int main(){ps = &s; // ?return 0;}
答案是
不行
哦➡️因为编译器会把上面的两个声明当成完全不同的两个类型。 即使在编译器看来这两个结构体的成员变量是相同的,但编译器仍认为它们是不同的类型
所以是
非法
的
❗特别注意:匿名结构体类型没有标签
所以声明结构体的时候,匿名结构体的变量也要跟在后面声明
1️⃣以防在后续的程序中,找不到此结构体类型
2️⃣即匿名结构体类型一旦没有一次性定义完所需的结构体类型变量,在后续再想定义就找不到此结构体类型了
💥综上:匿名结构体类型具有局限性
,不建议使用呀~
🥐Ⅲ.结构体的自引用
💡结构体的自引用
同学们觉得sizeof(struct Node)
是多少呢?
struct Node{int data;struct Node next;};
对于下列代码,才是结构体的自引用🔅答案是:它在里面其实是
无限套娃
的始终在创建结构体变量,没停下来,所以无法计算大小
struct N{int date;struct N* next;};
通过创建包含同类型的结构体指针,从而找到下一个同类型的节点【即自己可以找到同自己类型的元素】
❗特别注意:
匿名结构体类型
不可以自引用
👉Eg:
typedef struct //这里typedef的意思是以后只要写 Node 就表示 这个匿名结构体类型】{int data;Node* next;}Node;
➡️上述的写法也是错误
的:
这个结构体类型在创建得时候,成员变量里就已经拥有了Node
了,而此时Node
的创建还在后面,即改名为Node的结构体还没创建好,就被调用了,所以是错误
的
✨综上:结构体自引用
不是包含创建同类型的结构体变量
,而是包含同类型的结构体的指针
【一般多用于实现链表
结构】
🥐Ⅳ.结构体的空间大小
经过上述结构体类型的了解
现在我们深入讨论一个问题:计算结构体的大小
💡其中,里面涉及了一个非常重要的知识点:结构体内存对齐
👉示例:
同学们觉得下面的结构体空间大小是多少呢?
➡️有的同学可能认为:结构体的空间大小就是成员变量大小的总和
上述的想法只能对一半❗
但经过我的讲解你就能完全掌握啦~
👉让我们先来了解内存对齐
的对齐规则:
1️⃣第一个成员变量
存放在在与结构体变量偏移量为0
的地址处
2️⃣其他成员变量
要对齐到某个数字(对齐数)的整数倍
的地址处
3️⃣结构体总大小
:成员变量中最大对齐数(每个成员变量都有一个对齐数)的整数倍
4️⃣如果嵌套了结构体的情况:
嵌套的结构体
以自己结构体中成员变量中的最大对齐数为嵌套结构体的对齐数,然后再对齐到外面结构体对应的嵌套结构体最大对齐数的整数倍处外面结构体的整体大小
就是所有最大对齐数(含嵌套结构体自己的最大对齐数)的整数倍。
❗特别注意:
1️⃣VS
中默认对齐数的值为8
Linux
没有默认对齐数的概念 2️⃣对齐数
:编译器默认的一个对齐数与该成员大小的较小值
➡️有了以上了解,我们再看回题目:
所以上面一共浪费了
2字节
的空间,结构体的空间大小为:8字节
同学们再来看看此题的答案是多少呢?
本题目就运用到
结构体嵌套
的其情况了~💫解题方法:
先求嵌套结构体
S3
的结构体总的空间大小&最大对齐数再回到外面的结构体S4
,进行上一题的做题步骤即可有了以上了解,
内存对齐
这一类的问题往后就不是问题啦~
👉补充:为什么存在内存对齐
?
平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常
性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于:
为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问
Eg:假设计算机每次读4
个字节,从地址偏移量为0的处开始读这样 即使读了
4
个字节,浪费了3
个字节的空间,但还是依然能读取到c
,再读4
个字节,也顺便把i
也拿出来了但是,如果是下面的情况去存放,每一次读
4
个字节,就不方便了为了读取到
i
【仅仅对读取i
来说,而不是整体字节需要读取几次】,要对它进行读取两次才拿到i
的全部字节 第一次读了i
的3个字节,第二次才读取到i
的最后一个字节,相当于要完全读取到i的内容,需要读取2次而按照上面的
内存对齐
的放法,就可以直接读取一次就读到i
了
❗在设计结构体的时候,我们既要满足对齐
,又要节省空间
,我们可以:
让占用空间小的成员尽量集中在一起
💥结构在对齐方式不合适的时候,我们也可以自己更改
默认对齐数:
利用#pragma pack()
:
1️⃣#pragma pack(?)
= 设置默认对齐数为?
2️⃣#pragma pack()
= 取消设置的默认对齐数,还原为默认
✨总的来说:结构体的内存对齐
是拿空间
来换取时间
的做法
🥯Ⅴ.总结
✨综上:就是结构体类型啦~
➡️同学们需要好好理解哦,有利于我们后续的学习~
🍞二.位段
🥐Ⅰ.什么是位段
💡位段
的声明和结构体是类似的,有两个不同:
1️⃣位段的成员必须是int
、unsigned int
或signed int
2️⃣位段的成员名后边有一个冒号
和一个数字
👉示例:
struct S{int a:2;int b:5;int c:10;int d:30;};
❗特别注意:
位段
的意思就是:将这个成员变量存储于自己设定的空间大小内(单位:比特
)
➡️就如上述的代码:
变量a
存储于2
个比特位里
变量b
存储于5
个比特位里
……
🥐Ⅱ.位段的内存分配
💡分配规则:
1️⃣位段的成员可以是int
、unsigned int
、signed int
或者是char
(属于整形家族
)类型
2️⃣位段的空间上是按照需要,以4
个字节(int
)或者1
个字节(char
)的方式来按需开辟的
👉例子:
struct S{int a:3;int b:4;int c:5;int d:4;};struct S s = {0};s.a = 10;s.b = 12;s.c = 3;s.d = 4;
➡️位段的空间是如何存储的呢?
💥注意:
1️⃣比特位的放置
与高低地址
无关:比特位在一个字节内由低位
向高位
放置
2️⃣大小端
讨论的是字节
被机器读取、存放的顺序
,与字节内比特位
的放置无关
❗特别注意:
位段
涉及很多不确定因素,位段
是不跨平台
的,注重可移植的程序应该避免
使用位段,因为在跨平台中可能出现如下问题:
1️⃣int
位段被当成有符号数
还是无符号数
是不确定
的
2️⃣ 位段中最大位的数目
不能确定(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题)
3️⃣位段中的成员在内存中从左向右
分配,还是从右向左
分配标准尚未定义
4️⃣当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余
的位还是利用
,这是不确定的
🥐Ⅲ.位段的应用
💡位段可用于通信传输
中,以节省一个信息传输的空间大小
🥯Ⅳ.总结
✨综上:就是位段啦~
➡️简单来说:跟结构体相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在
🍞三.枚举类型
🥐Ⅰ.枚举类型的定义
💡枚举类型:
即一一列举
👉Eg:
enum Color{RED , GREEN,BLUE = 5,YELLOW};
❗特别注意:
BULE = 5;
仅仅是给常量赋初值,并不是修改常量的值【所以赋值
可以正常运行,但修改常量的值
机器是不允许
,如下图:】
🔅我们可得知:
➡️上述定义的enum Color
为枚举类型
1️⃣{ }
中的内容都是枚举类型的可能取值,也叫枚举常量
2️⃣枚举常量
的取值默认从0
开始,往下逐个递增1
【在定义的时候赋初值
,则往下的枚举常量按初值
递增1
】
🥐Ⅱ.枚举类型的优点
💡#define
也可以定义常量,我们为什么使用枚举类型
:
利用好枚举类型可以增加代码的可读性
和可维护性
和#define
定义的标识符比较,枚举有类型检查
,更加严谨
防止了命名污染
(封装)
便于调试
使用方便
,一次可以定义多个常量
👉Eg:
enum Option{EXIT,//0ADD,//1SUB,//2MUL,//3DIV //4};
int main(){int input = 0;do{menu();printf("请选择>:");scanf("%d", &input);switch (input){//case 1:case ADD:break;//case 2:case SUB:break;//case 3:case MUL:break;//case 4:case DIV:break;//case 0:case EXIT:break;default:break;} } while (input);return 0;}
➡️通过以上例子,我们不乏可以更直观地去书写代码:
1️⃣使得常量更具由象征意义
、实际意义
,更容易联想
2️⃣而且调试起来更加容易,不然使用#define
会在预处理阶段直接常量替换内容,使得无法很好的去调试
🥯Ⅲ.总结
✨综上:就是枚举类型啦
➡️简单来说:枚举类型可以使常量更加具体化
🍞四.联合体(共用体)
🥐Ⅰ.联合类型的定义
💡联合类型的定义 :
1️⃣联合也是一种特殊的自定义类型
2️⃣这种类型定义的变量也包含一系列的成员,特征
是这些成员公用
同一块空间
➡️这也就为什么:联合体
也叫共用体
啦~
🥐Ⅱ.联合的大小&特点
💡特点:
1️⃣联合的成员是共用同一块内存空间的
2️⃣这样一个联合变量的总大小
:至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)
联合的大小
至少是最大成员
的大小。
当最大成员大小
不是最大对齐数
的整数倍
的时候,就要对齐
到最大对齐数的整数倍
🔅大家可能对联合体
不太具象化,接下来紧跟我步伐~
👉Eg:
union Un1{char c[5];int i;};
大家觉得下面输出的结果是什么?
printf("%d\n", sizeof(union Un1));
❗特别注意:
计算结构类型的大小时,若遇到像上面一样的数组类型
的时候:
计算数组
类型的对齐数
只看数组类型
所对应的对齐数
又因为数组
是连续存放
的,所以数组只要首元素对齐后,剩下的其它元素就不需要再对齐,只需要排下去
即可
👉再看回上题:
💫所以答案为:8
字节
✨综上:
对于联合体(共用体)
可以理解为:成员变量
都在同一块空间
的同一起始位置
开始存储的【即在同一块空间上堆叠存储
】
❗正因为这样:
在同一时间
里,只能调用共用体的一个
成员变量
➡️因为当改变一个共用体的成员变量,对于其它成员变量来说共用的这部分空间的值
也会被改变
🥐Ⅲ.联合体的使用
💡知道其用法,那我们便可以用联合体
的内容去实现机器大小端的判断
啦~
❤️不记得的同学可以乘坐下列快车快速回顾哟~
【C语言】数据在内存中的存储_ [进阶篇_复习专用]
❗思路:
在内存中存储整型1
【即对于二进制序列来说:000000……0001】
使用char
类型的指针对其进行读取【读取1字节
(8
个比特位)的内容】,看读取的内容为1
还是0
判断:若为1
则是小端
,若为0
则是大端
👉实现:
有了以上思路,我们便可以用联合体
将以前的前两个步骤整合在一起
int check_sys(){union U{char c;int i;}u;u.i = 1;return u.c;}
💛这样子的设计就很巧妙啦~💛
int main(){int ret = check_sys();if (ret == 1){printf("小端\n");}else{printf("大端\n");}return 0;}
🥯Ⅴ.总结
✨综上:就是联合体(共用体)的内容啦
➡️相信大家对联合体
有不一样的看法了吧🧡
🫓总结
综上,我们基本了解了C语言中的“自定义类型(结构体类型+枚举类型+联合体类型)”🍭 的知识啦~~
恭喜你的内功又双叒叕得到了提高!!!
感谢你们的阅读😆
后续还会继续更新💓,欢迎持续关注📌哟~
💫如果有错误❌,欢迎指正呀💫
✨如果觉得收获满满,可以点点赞👍支持一下哟~✨