> 文档中心 > 【C语言】自定义类型(结构体类型+枚举类型+联合体类型)[进阶篇_ 复习专用]

【C语言】自定义类型(结构体类型+枚举类型+联合体类型)[进阶篇_ 复习专用]

💛 前情提要💛

本章节就进入C语言的核心:深度剖析C语言自定义类型结构体类型+枚举类型+联合体类型)

接下来我们即将进入一个全新的空间,对代码有一个全新的视角~

以下的内容一定会让你对C语言有一个颠覆性的认识哦!!!

以下内容干货满满,跟上步伐吧~


作者介绍:

🎓 作者: 热爱编程不起眼的小人物🐐
🔎作者的Gitee:代码仓库
📌系列文章推荐:

  1. 实现Strcpy函数 - 通过函数发现 “程序之美” | 不断优化、优化、再优化~

  2. 《刷题特辑》—实现由小白至入门者的学习记录🥰

  3. 【C语言】数据在内存中的存储_ [进阶篇_复习专用]

  4. 【C语言】字符函数&字符串函数&内存函数(上)[进阶篇_复习专用]

  5. 【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️⃣即匿名结构体类型一旦没有一次性定义完所需的结构体类型变量,在后续再想定义就找不到此结构体类型了

💥综上: 匿名结构体类型具有局限性,不建议使用呀~

🥐Ⅲ.结构体的自引用

💡结构体的自引用

  1. 同学们觉得sizeof(struct Node)是多少呢?
struct Node{int data;struct Node next;};

🔅答案是:它在里面其实是无限套娃

  • 始终在创建结构体变量,没停下来,所以无法计算大小
  1. 对于下列代码,才是结构体的自引用
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字节
    在这里插入图片描述
    同学们再来看看此题的答案是多少呢?

    本题目就运用到结构体嵌套的其情况了~

💫解题方法:

  1. 先求嵌套结构体S3的结构体总的空间大小&最大对齐数在这里插入图片描述
  2. 再回到外面的结构体S4,进行上一题的做题步骤即可
    在这里插入图片描述
    【C语言】自定义类型(结构体类型+枚举类型+联合体类型)[进阶篇_ 复习专用]
    有了以上了解,内存对齐这一类的问题往后就不是问题啦~

👉补充: 为什么存在内存对齐?

  1. 平台原因(移植原因):
    不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常

  2. 性能原因:
    数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
    原因在于:为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问

  • 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️⃣位段的成员必须是 intunsigned intsigned int

  • 2️⃣位段的成员名后边有一个冒号和一个数字

👉示例:

struct S{int a:2;int b:5;int c:10;int d:30;};

特别注意:

  • 位段的意思就是:将这个成员变量存储于自己设定的空间大小内(单位:比特)

➡️就如上述的代码:

  • 变量a存储于2个比特位里

  • 变量b存储于5个比特位里

  • ……

🥐Ⅱ.位段的内存分配

💡分配规则:

  • 1️⃣位段的成员可以是 intunsigned intsigned 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;仅仅是给常量赋初值,并不是修改常量的值【所以赋值可以正常运行,但修改常量的值机器是不允许如下图:】

【C语言】自定义类型(结构体类型+枚举类型+联合体类型)[进阶篇_ 复习专用]

🔅我们可得知:

  • ➡️上述定义的enum Color枚举类型

    • 1️⃣ { }中的内容都是枚举类型的可能取值,也叫枚举常量

    • 2️⃣枚举常量的取值默认从0开始,往下逐个递增1 【在定义的时候赋初值,则往下的枚举常量按初值递增1

🥐Ⅱ.枚举类型的优点

💡#define也可以定义常量,我们为什么使用枚举类型

  1. 利用好枚举类型可以增加代码的可读性可维护性

  2. #define定义的标识符比较,枚举有类型检查,更加严谨

  3. 防止了命名污染(封装)

  4. 便于调试

  5. 使用方便,一次可以定义多个常量

👉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. 在内存中存储整型1【即对于二进制序列来说:000000……0001】

  2. 使用char类型的指针对其进行读取【读取1字节8个比特位)的内容】,看读取的内容为1还是0

  3. 判断:若为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语言中的 “自定义类型(结构体类型+枚举类型+联合体类型)” 🍭 的知识啦~~

恭喜你的内功又双叒叕得到了提高!!!

感谢你们的阅读😆

后续还会继续更新💓,欢迎持续关注📌哟~

💫如果有错误❌,欢迎指正呀💫

✨如果觉得收获满满,可以点点赞👍支持一下哟~✨
在这里插入图片描述