C语言—自定义类型(结构体、枚举、联合)
自定义类型
- 结构体
- 结构体的声明
- 特殊的声明
- 结构的自引用
- 结构体变量的定义和初始化
- 结构体内存对齐
- 修改默认对齐数
- offsetof宏
- 结构体传参
- 位段
- 位段的定义(声明)
- 位段的内存分配
- 位段的跨平台问题
- 位段的应用
- 枚举
- 枚举类型的定义及使用
- 枚举的优点
- 联合(共用体)
- 联合类型的定义
- 联合的特点
- 联合大小的计算
结构体
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
结构体的声明
struct tag
{member-list;
}variable-list;struct——关键字
tag——结构体的标签名
struct tag——结构体类型名
member-list——成员列表(成员变量的列表)
variable-list——变量列表
实例一(描述一本书):
struct Book
{char name[20];int price;char id[12];
}b4,b5,b6;//b4,b5,b6是全局的int main()
{//b1,b2,b3是局部变量struct Book b1;struct Book b2;struct Book b3;return 0;
}
特殊的声明
在声明结构的时候,可以不完全的声明。
实例一:
//匿名结构体类型
//两个结构在声明的时候省略掉了结构体标签(tag)
struct
{int a;char b;float c;
}x;
struct
{int a;char b;float c;
}a[20], *p;int main()
{p=&x;//会报警告//注:在编译器看来虽然结构体中的成员是一模一样的,但编译器仍然认为这两个结构体是不同的类型(在编译器看来是不合理的)return 0;
}
注:匿名结构体类型创建好之后只能用一次(没有标签构不成结构体类型)
结构的自引用
实例一:
struct Node
{int data;struct Node next;
};
//不可以这样定义(这样会死递归的)
注:在结构中包含一个类型为该结构本身的成员是不可以的
结构体自引用:
struct Node
{int data;struct Node* next;
};
注:
- 数据结构是数据在内存中存储的结构
- 线性数据结构:顺序表、链表;树形数据结构:二叉树
- 链表中的节点分为两部分数据域(存储的是数据)和指针域(存放的是下一个节点的地址)
总结:结构体自引用不是包含同类型的结构体变量而是包含同类型的结构体的指针
实例二:
typedef struct
{int data;Node* next;
}Node;
//这样写代码,是错误的
//先有Node*定义出的成员才能对类型进行重命名产生Node(陷入先有蛋还是先有鸡的问题)//解决方案:
typedef struct Node
{int data;struct Node* next; //重命名的Node不能在成员里用
}Node;
结构体变量的定义和初始化
实例一:
struct Point
{int x;int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2//初始化:定义变量的同时赋初值。
struct Point p3 = {x, y};struct Stu //类型声明
{char name[15];//名字int age; //年龄
};
struct Stu s = {"zhangsan", 20};//初始化struct Node
{int data;struct Point p;struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化
实例二:
struct S
{char c;int i;
}s1, s2;struct B
{double d;struct S s;char c;
};
int main()
{struct S s3 = {'x', 20};struct B sb = { 3.14, {'w', 100},'q' };//. //->printf("%lf %c %d %c\n", sb.d, sb.s.c, sb.s.i, sb.c);return 0;
}
注:
- 结构体不完全初始化时其余没初始化的默认赋值0
- 结构体是直接可以赋值的
结构体内存对齐
实例一:
#include<stdio.h>
struct S
{char c;int i;char c2;
};
int main()
{struct S s = { 0 };printf("%d\n", sizeof(s));return 0;
}
分析
结构体的对齐规则:
- 第一个成员在与结构体变量偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
- 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
注:
- 偏移量是结构体变量中成员的地址和结构体变量地址之差(偏移量是相对于结构体起始位置的偏移量)。
- VS默认对齐数为8,linux无对齐数(即该成员的字节大小为对齐数)
- 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
- 对于Linux中GCC编译器对齐数就是该成员的大小
实例二:
#include <stdio.h>struct S4
{double d;char c;int i;
};
struct S5
{char c1;struct S4 s4;double d;
};int main()
{struct S5 s5 = {0};printf("%d\n", sizeof(s5)); //32return 0;
}
解析:
为什么存在内存对齐?
大部分的参考资料都是如是说的:
- 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
- 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说:结构体的内存对齐是拿空间来换取时间的做法。
在设计结构体的时候,既要满足对齐,又要节省空间。可以通过**让占用空间小的成员尽量集中在一起。**从而达到节省空间的目的
实例三:
可以看出两个结构体的成员相同,但顺序不同,从而结构体所占内存的大小也就不同
修改默认对齐数
#pragmg pack(N) //设置默认对齐数为N
实例一:
#include<stdio.h>
//默认对齐数是8
//把默认对齐数改为2
#pragma pack(2)
struct S
{char c1; //结构体S按照默认对齐数为2进行内存对齐int i;char c2;
};
#pragma pack() //取消设置的默认对齐数,还原为默认
int main()
{struct S s = { 0 };printf("%d\n", sizeof(s));return 0;
}
分析
注:
- 设置默认对齐数时一般设置成2^n,几乎不会设置成奇数
- #pragma pack(1)将默认对齐数设置成1,不存在结构体的内存对齐
- 修改默认对齐数的意义是结构在对齐方式不合适的时候,程序员可以自己更改默认对齐数
offsetof宏
offsetof是C语言本身具有的宏,用于计算结构体中某个成员相较于这个结构体变量起始位置的偏移量。
offsetof宏的使用
结构体传参
实例一:
struct S
{int data[1000];int num;
};struct S s = {{1,2,3,4}, 1000};//结构体传参
void print1(struct S s)
{printf("%d\n", s.num);
}//结构体地址传参
void print2(struct S* ps)
{printf("%d\n", ps->num);
}int main()
{print1(s); //传结构体print2(&s); //传地址return 0;
}
注:printf1函数和printf2函数的功能一样,但是printf2函数要比printf1函数的性能要好(时间和空间消耗较低)。原因是函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。因此结构体传参的时候,要传结构体的地址。
位段
位段是依附于结构体的
位段的定义(声明)
位段的声明和结构是类似的,有两个不同:
- 位段的成员必须是 int、unsigned int 或signed int或char。
- 位段的成员名后边有一个冒号和一个数字
位段的定义:
struct A //A就是一个位段类型
{int _a:2;int _b:5;int _c:10;int _d:30;
};
位段的内存分配
实例一:
struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;};
int main()
{printf("%d\n", sizeof(struct A));return 0;
}
注::冒号后+数字是占多少个比特位。
分析
struct A
{//4个字节-32bitint _a : 2;//成员占2个bit位int _b : 5;//成员占5个bit位int _c : 10;//成员占10个bit位//4个字节-32bitint _d : 30;//成员占30个bit位 //int _d : 30;中的_d位段成员在内存中开辟30个比特位是用之前开辟完剩余的比特位和新开辟的32位比特位还是只用新开辟的32位比特位,这是不确定的。因为C语言没有明确规定到底用不用前面的空间。
};
int main()
{printf("%d\n", sizeof(struct A));//4+4=8return 0;
}
位段的内存分配
- 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
- 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
- 位段涉及很多不确定因素,位段是不跨平台的(不同平台上实现方式是不同的),注重可移植的程序应该避免使用位段。
实例二:
struct S
{char a : 3;char b : 4;char c : 5;char d : 4;
};
int main()
{struct S s = { 0 };s.a = 10;s.b = 12;s.c = 3;s.d = 4;return 0;
}
分析( 该分析只适用于VS )
注:
- 在VS编译器下一个字节(四个字节)内部的数据在使用时先使用低比特位的数据再使用高比特位的数据(从右向左使用)
- 当一块空间剩余的空间内容不够下一个成员使用的时候这块空间会浪费掉
位段的跨平台问题
- int 位段被当成有符号数还是无符号数是不确定的。
- 位段中最大位的数目不能确定。(16位机器int最大16,32位机器int最大32,写成27,在16位机器会出问题。
- 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位时,还是利用,这是不确定的。
补充:
- 在16位机器下整型类型占2个字节;在32位机器下整型类型占4个字节
总结:跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
位段的应用
IP数据报的格式
枚举
枚举是把可能的取值一一列举。
枚举是创造新的类型,这种类型取值是非常有限的(明确的几种取值),这时可以用枚举创造枚举类型
枚举类型的定义及使用
实例一:
//声明枚举类型enum Day//星期
{Mon,Tues,Wed,Thur,Fri,Sat,Sun
};enum Sex//性别
{MALE,FEMALE,SECRET
};enum Color//颜色
{RED,GREEN,BLUE
};
实例二:
enum Color //enum枚举关键字 Color枚举的类型
{RED, GREEN, //枚举类型的可能取值 这些都是枚举常量BLUE
};
int main()
{enum Color c = BLUE; //在C++中可以这样写enum Color c = Color::BLUE; ::(限定符),BLUE来自于Colorreturn 0;
}
注:
- 枚举类型是用来描述那些能够把值一一确定出来的有限的几种值的类型的一种描述枚举,而结构体是为了描述复杂对象的。
- 枚举类型是一种类型
- 枚举那些可能取值的值的数值是整型
要点:
- {}中的内容是枚举类型的可能取值,也叫枚举常量 。
- 这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。
实例三:
注:
- 不能写成enum Color c=2;在C++编译器中语法是不支持的(int类型无法转换成enum Color类型)类型不匹配
- 枚举常量不能修改,但可以赋初值
实例四:
//声明枚举类型
enum Color
{RED=5,//5GREEN=8,//8BLUE//9
};int main()
{enum Color c = BLUE;printf("%d\n", sizeof(c)); //4//计算枚举类型的大小时它只有一种可能取值,所以它的大小就是整形大小return 0;
}
注:枚举类型所占内存的大小恒为4
枚举的优点
枚举与#define 定义常量的区别:
- 增加代码的可读性(数字替换成它所对应符号的名字,这些名字具有可读性)和可维护性
- 和#define定义的标识符比较枚举有类型检查,更加严谨。
- 防止了命名污染(封装)
- 便于调试
- 使用方便,一次可以定义多个常量
注:调试代码是在生成可执行程序之后,运行代码时才可以进行一定的调试
联合(共用体)
联合类型的定义
联合也是一种特殊的自定义类型。这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。
联合类型的声明、定义:
//联合类型的声明
union Un
{ char c; int i;
}; //联合变量的定义
union Un un; //计算联合变量的大小
printf("%d\n", sizeof(un));
实例一:
union Un
{char c;//1int i;//4
};
int main()
{union Un u = {10};//union Un u = { 10,1000 }; //err 联合体多个成员共用一块空间初始化时不可能对多个成员进行初始化,其实只初始化一个成员u.i = 1000;u.c = 100;printf("%p\n", &u);printf("%p\n", &(u.c));printf("%p\n", &(u.i));printf("%d\n", sizeof(u)); //4return 0;
}
注:联合体在同一时间只能使用它其中的一个成员
实例二:
注:联合体初始化时会对联合体第一个成员进行初始化
联合的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。
实例一:
经典面试:
判断当前计算机的大小端存储
//判断当前计算机的大小端存储int check_sys1()
{int a = 1;if ((*(char*)&a) == 1){return 1;//小端}else{return 0;//大端}
}int check_sys2()
{union U{char c;int i;}u;u.i = 1;return u.c;//返回1 就是小端//返回0 就是大端
}int main()
{int ret = check_sys2();if (ret == 1)printf("小端\n");elseprintf("大端\n");return 0;
}
联合大小的计算
联合体大小计算规则:
- 联合的大小至少是最大成员的大小。
- 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
实例一:
union Un
{char a[5];//对齐数1(相当于放了5个字符类型的变量) int i;//对齐数4};int main()
{union Un u;printf("%d\n", sizeof(u)); //8return 0;
}
实例二:
union Un
{ short s[5];//对齐数2 int a;//对齐数4
};int main()
{union Un u;printf("%d\n", sizeof(u)); //12return 0;
}
相关文章:

C语言—自定义类型(结构体、枚举、联合)
自定义类型 结构体结构体的声明特殊的声明结构的自引用结构体变量的定义和初始化结构体内存对齐修改默认对齐数offsetof宏 结构体传参 位段位段的定义(声明)位段的内存分配位段的跨平台问题位段的应用 枚举枚举类型的定义及使用枚举的优点 联合ÿ…...

Node.js博客项目开发思路笔记
博客项目介绍 1. 目标 开发一个博客系统,具备博客基本功能只开发 server 端,不关心前端 2. 需求 首页、作者页、博客详情页登陆页管理中心、新建页、编辑页 3. 技术方案 数据如何存储 博客 idtitlecontentcreatetimeauthor1标题 1内容 11111112z…...
python 之 shutil 文件的复制、删除、移动文件以及目录,并支持文件的归档、压缩和解压
一、shutil shutil 模块于文件和文件集合的高级操作,包括:复制、删除、移动文件以及目录,并支持文件的归档、压缩和解压等 二、使用例子 复制文件及权限 shutil.copy(src, dst)复制文件及权限;src 和 dst 文件路径。dst 文件名或…...

jface
JFace 是建立在 SWT 之上的 UI 部件,它是 SWT 的扩展并能和SWT交互。 ApplicationWindow和Action org.eclipse.jface.window.ApplicationWindow; JFace为了简化窗口的设计特别设计了类,比如ApplicationWindow这一个类,它里面包含了六个默认…...

六级备考28天|CET-6|听力第一讲|基本做题步骤与方法|13:30~14:30
目录 1. 重点词汇 proofread / ˈpruːfriːd / v.校对,校阅 autonomous adj.独立的 obsession n. 喜好 ample …...

系统设计 - 设计一个速率限制器
实施速率限制器的位置主要取决于我们的应用程序、技术栈、技术团队等因素。通常有三个位置可供选择:客户端、服务器端或中间件。 客户端是不可靠的地方来执行速率限制,因为恶意行为者可以轻易伪造客户端请求。 比将速率限制器放在服务器端更好的方法是使…...

[技术分享]Android平台实时音视频录像模块设计之道
实现背景 录像有什么难的?无非就是数据过来,编码保存mp4而已,这可能是好多开发者在做录像模块的时候的思考输出。是的,确实不难,但是做好,或者和其他模块有非常好的逻辑配合,确实不容易。 好多…...
JDKMissionControl官方用户指南--人工翻译
1. JMC8新增功能 暂时用不到,暂略 2. JDK Mission Control是什么 JMC是一组高级工具,用于管理、监视、分析Java应用程序并排除其故障。JMC能够对代码性能、内存和延迟等领域进行高效而详细的数据分析,而不会引入通常与分析和监控工具相关的…...

MySql-高级(分库分表问题简析) 学习笔记
文章目录 1. 为什么要分库分表?2. 用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?3. 你们具体是如何对数据库如何进行垂直拆分或水平拆分的?4. 分库分表时,数据迁移方案5. 如何设计可以动态扩容缩容…...

【5.20】五、安全测试——安全测试工具
目录 5.4 常见的安全测试工具 1. Web漏洞扫描工具——AppScan 2. 端口扫描工具——Nmap 3. 抓包工具——Fiddler 4. Web渗透测试工具——Metasploit 小提示:Kali Linux 5.4 常见的安全测试工具 安全测试是一个非常复杂的过程,测试所使用到的工具也…...
【13900k】i9 核显升级驱动
这里写自定义目录标题 官方的助手不能用显卡控制中心提示最新的更新搜索显卡 intel uhd graphics 770 手动下载安装自定义音频为啥也要卸载?新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片…...
使用Python将绿色转换为红色、红色转换为蓝色的图像处理
使用Python将绿色转换为红色、红色转换为蓝色的图像处理 在图像处理中,我们经常需要对图像进行颜色转换和修改。本篇博客介绍了如何使用Python的Pillow库来读取一个文件夹中的所有图像,并将其中的绿色转换为红色,红色转换为蓝色。我们还展示…...

Web2与Web3开发的不同之处
Web2是引入交互功能的第二代互联网,也是我们今天所熟悉的。随着Web的不断发展,第三代互联网,也被称为Web3,正处于积极开发中。Web3引入了在区块链上运行的去中心化和无需许可的系统。但是Web2和Web3开发之间有什么区别呢ÿ…...
递增数组的判断【python实现】
有时候需要对某一组数组的数据进行判断是否 递增 的场景,比如我在开发一些体育动作场景下,某些肢体动作是需要持续朝着垂直方向向上变化,那么z轴的值是会累增的。同理,逆向考虑,递减就是它的对立面。 下面是查找总结到…...
在自定义数据上训练 YOLOv8 实例分割
图像分割是一个核心视觉问题,可以为大量用例提供解决方案。从医学成像到分析流量,它具有巨大的潜力。实例分割,即对象检测+分割,甚至更强大,因为它允许我们在单个管道中检测和分割对象。为此,Ultralytics YOLOv8 模型提供了一个简单的管道。在本文中,我们将对自定义数据…...
洛谷密钥被破解:加密安全面临新挑战
密钥管理是加密系统中非常重要的一环,它涉及到密钥的生成、存储、分发、管理和销毁等多个方面。在密码学中,密钥是保护数据隐私和安全性的核心因素之一,因此,确保密钥的安全和保密性显得尤为重要。在2016年举办的 CQOI 数论竞赛中…...

02 Android开机启动之BootLoader及kernel的启动
Android开机启动之BootLoader及kernel的启动 1、booloader的启动流程 第一阶段:硬件初始化,SVC模式,关闭中断,关闭看门狗,初始化栈,进入C代码 第二阶段:cpu/board/中断初始化;初始化内存以及flash,将kernel从flash中拷贝到内存中,执行bootm,启动内核 2、kernel的启…...
代码随想录算法训练营 Day 49 | 121.买卖股票的最佳时机,122.买卖股票的最佳时机 II
121.买卖股票的最佳时机 讲解链接:代码随想录-121.买卖股票的最佳时机 确定 dp 数组以及下标的含义: dp[i][0] 表示第 i 天持有股票所得最多现金dp[i][1] 表示第 i 天不持有股票所得最多现金 确定递推公式: 如果第 i 天持有股票即 dp[i][0]&…...

精炼计算机网络——数据链路层(一)
文章目录 前言3.1 数据链路和帧3.1.1 数据链路和帧3.1.2 三个基本问题 3.2 点对点协议PPP3.2.1 PPP协议的特点3.2.2 PPP协议3.2.3 PPP协议的工作状态 总结 前言 上篇文章,我们一同学完了物理层的全部内容,在本篇文章中,我们初步学习数据链路…...

猿创征文|Spring系列框架之面向切面编程AOP
⭐️前面的话⭐️ 本篇文章将介绍一种特别重要的思想,AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。 …...

第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...

MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)
概述 在 Swift 开发语言中,各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过,在涉及到多个子类派生于基类进行多态模拟的场景下,…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...

2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...

C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...