自定义数据类型
前言:小伙伴们又见面啦,今天这篇文章,我们来谈谈几种自定义数据类型。
目录
一.都有哪些自定义数据类型
二.结构体
结构体内存对齐
1.如何对齐
2.为什么要对齐
3.节省空间和提升效率的方法
(1)让占用空间小的成员尽量集中在一起
(2)修改默认对齐数
三.位段
1.什么是位段
2.位段的的内存分配
3.位段的跨平台问题
四.枚举
1.枚举类型的定义
2.枚举的优点
五.联合体
1.联合体的定义
2.联合体的使用
3.联合体大小的计算
六.总结
一.都有哪些自定义数据类型
我们在C语言的基础中已经了解到了结构体,是一种对多种数据集中管理的一种自定义数据类型。
除此之外,我们还有另外三种:-
- 位段
- 枚举
- 联合
接下面我们就开始对这四种数据类型逐一展开讲解。
二.结构体
在我们前边的文章《C语言基础之——结构体》中我们已经对结构体展开了细致的讲解,所以在这篇文章中我们不再重复讲解。
那么在这篇文章中,我们来谈谈结构体类型的大小。
我们知道任何一种数据类型都有它所占用的内存大小,但是结构体类型却是多种数据类型的整合。
那小伙伴们是否知道结构体类型该如何计算大小呢???
来看例子:
#include<stdio.h>
struct Str1
{char a;char b;int c;
};
struct Str2
{char a;int b;char c;
};
int main()
{int num1 = sizeof(struct Str1);int num2 = sizeof(struct Str2);printf("%d\n", num1);printf("%d\n", num2);
}
小伙伴们可以猜一猜,Str1 和 Str2的内存大小会是多少呢???
有的小伙伴可能会说:啊,都是两个char类型和一个int类型,那大小不就是6呗。
那到底是不是6呢???我们来看结果:
哇塞,天差地别,不仅不是6,而且两个数还不一样。
这是为什么呢???
事实上,对于结构体,有结构体内存对齐这样一个概念。
结构体内存对齐
1.如何对齐
我们先来看对齐的规则:
1.第一个成员放在与结构体变量偏移量为0的地址处。
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。
- 博主所使用的VS2019的默认对齐数为8
- Linux中没有默认对齐数,对齐数就是成员变量本身的大小
3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4.如果出现嵌套结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
什么意思呢,下面我们根据例子来具体讲解:
struct Str1
{
char a;
char b;
int c;
};
首先来看这个结构体,它的第一个成员变量a为char型, 大小为1个字节,放在结构体变量偏移量为0的地址处,即从0开始存放。
然后第二个成员变量b也是char类型,大小为1个字节,从第二个成员变量开始我们要根据最大对齐数进行对齐,将1与8相比,肯定是1较小,所以对齐数为1,所谓对齐数,就是这个成员变量所存放的空间的前边所占用的空间个数需要是对齐数的整数倍,要对齐到1的整数倍的位置,我们现在只占用了一个字节,1是1的整数倍数,那自然就是从第二个字节开始存放,占用一个字节。
最后来看第三个成员变量c为int类型,大小为4个字节,则其对齐数为4,那么我们要对齐到4的整数倍的位置,但是现在我们只有两个字节的位置被占用,2不是4的整数倍数,所以我们还需要浪费两个字节,来实现对齐到4的倍数的位置,所以要从标号为4的位置存放四个字节。
这样一来我们就得到了我们的结果,8个字节。
下边我们继续来看一个例子:
struct Str2
{
char a;
int b;
char c;
};
第一个为char型,放在0处。
第二个为int型,对齐数为4,现在只占用了一个字节,不是4的整数倍,所以要浪费3个字节,从标号为4的位置开始,占用四个字节。
第三个为char型,对齐数为1,8是1的整数倍,所以直接从标号为8的位置开始,占用一个字节。
这个时候出问题了,这不是才9个字节吗,那结果为什么是12个字节呢???
这时候来看我们规则的第三条: 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
我们上述结构体的最大对齐数为int型的4,但是现在我们只占用了9个字节,并不是4的整数倍,所以我们还得浪费3个字节,达到12个字节,才是我们最终的结果。
下面我们来看最后一个例子:
struct Str3
{
double a;
char b;
int c;
};struct Str4
{
char a;
struct Str3 s3;
double c;
};
我们来计算Str4这样一个嵌套结构体的大小。
经过我们上边的学习,已经能够很容易的算出,Str3的大小为16个字节。下边我们来计算Str4。
第一个为char型,放在地址为0的位置,占用一个字节。
第二个为struct Str3结构体类型,大小为16个字节,那么根据我们的规则4,嵌套的结构体对齐到自己内部的最大对齐数的整数倍处,那么Str3内部的最大对齐数为double类型的8,所以要是8的倍数,很显然1并不是8的倍数,所以要浪费7个字节,从标号为8的位置开始,占用16个字节。
第三个为double类型,大小为8个字节,对齐数为8,前边刚好占用了24个字节,是8的整数倍,所以我们就从标号为24的位置开始,占用8个字节。
这样,我们总共就是占用了32个字节。
到这里,我们就讲完了通过结构体内存对齐的规则来计算结构体大小的知识。
那么小伙伴们是否有个疑惑,我们为什么就非得对齐呢?
2.为什么要对齐
我们通过查阅大量的资料,最终得出以下两点:
1.平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据;某些硬件平台只能在某些地址处取某些特定的类型的数据,否则就会抛出硬件异常。
数据结构(尤其是栈)应该尽可能的在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要做两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说:
结构体的内存对齐是拿空间来换取时间的做法。牺牲空间来换取效率。
那么有没有什么办法能够帮助我们即能够提升效率,又能节省空间呢???
3.节省空间和提升效率的方法
(1)让占用空间小的成员尽量集中在一起
struct Str1
{
char a;
char b;
int c;
};
struct Str2
{
char a;
int b;
char c;
};
就比如还是我们这两个结构体,成员变量一模一样,但是大小却不一样,但是Str1的空间是小于Str2的,所以,第一种方法就是:让占用空间小的成员尽量集中在一起。
(2)修改默认对齐数
我们知道,默认对齐数这个规则对我们的空间占用影响很大,那我们便可以通过修改默认对齐数的方法来实现节省空间和提升效率。
那么默认对齐数该如何修改呢???
通过#pragma这个预处理指令来改变。
#include<stdio.h>
#pragma pack(1)//将默认对齐数修改为1
struct Str1
{char a;char b;int c;
};
#pragma pack()//恢复默认对齐数的原值
struct Str2
{char a;int b;char c;
};
int main()
{int num1 = sizeof(struct Str1);int num2 = sizeof(struct Str2);printf("%d\n", num1);printf("%d\n", num2);
}
我们通过#pragma pack()这个指令,可以将默认对齐数修改为()内的值,比如我们上述代码修改为1, 修改之后,默认对齐数就固定为1,每个数据都对齐到1的整数倍,同时也要记得及时恢复默认对齐数的原值,确保只有这一块的结构体我们需要修改,以免发生错误。
来看结果:
除此之外,我们还有第三种方法,那就是——位段。
三.位段
讲完了结构体的内存分配情况之后,我们就得紧接着来谈谈结构体实现位段的能力。
1.什么是位段
位段的出现,是为了让结构体更加节省空间。
位段的声明和结构体是类似的,有两处不同:
- 位段的成员必须是int、unsigned int 、signed int或char类型
- 位段的成员名后边有一个“冒号”和一个数字
来看例子:
struct Str1
{
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};
位段的位,指的是二进制的位数,char a : 3,就表示a这个数据占用3个bit位。
如果不用位段,我们这个结构体就是4个字节的大小,但是使用位段之后来看:
只用到了3个字节,节省了空间。
这种情况适用于能够知道创建的数据大概会占用多少的空间。
2.位段的的内存分配
那么我们上述结构体通过位段实现的8个字节的空间又是怎么来的呢???
位段开辟空间是一步一步来的,如果是int型,就会先开辟4个字节给你用,如果不够,那就再开辟一个,char同样。
我们很清楚,一个字节是8个bit位,那么a、b、c、d分别为3、4、5、4个bit位,3 + 4 = 7 < 8,所以a和b共用一个字节,剩下一位不够c用,那就丢掉,再开辟一个。存入c之后剩余3位,不够d用,便继续丢掉,在开辟,最终一共开辟3个字节。
虽然位段能够帮助我们节省一大部分的空间,但这并不代表着我们可以随便的使用位段。
因为其涉及着很多的不确定因素,不能跨平台,所以注重可移植性的程序应避免使用位段。
3.位段的跨平台问题
- int位段被当成有符号数还是无符号数是不确定的。
- 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32)
- 位段中的成员在内存中从左向右分配还是从右往左分配标准尚未定义。
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段的剩余位时,是舍弃剩余的位还是利用是不确定的。
对于第4条,我们上边的代码已经能够验证,在当前的VS2019编译器下是直接舍弃。
四.枚举
枚举,顾名思义也就是一一的列举。
把我们可能需要用到的数据一一列举出来。
一周的七天;
一年的月份;
一年的四季
这些都能够写成一个枚举类型来一一列举,下面我们就来看看枚举的具体用法。
1.枚举类型的定义
定义枚举常量要用到enum
enum Season
{
SPRING,
SUMMER,
AUTUMN,
WINTER
};
这样我们就定义出来一个简单的季节枚举。
值得注意的是,每个枚举常量之间都用逗号隔开,最后一个枚举常量后边不用跟任何符号,而且枚举常量一般都用其英文的大写字母表示。
在枚举类型中,枚举常量都表示一个常数,从第一个枚举常量开始,代表0,此后逐个递增,因此枚举常量都是int类型。
#include<stdio.h>
enum Season
{SPRING,SUMMER,AUTUMN,WINTER
};
int main()
{printf("%d\n", SPRING);printf("%d\n", SUMMER);printf("%d\n", AUTUMN);printf("%d\n", WINTER);return 0;
}
结果如下:
枚举常量不能在枚举类型外修改,但是可以在其定义时修改,并且会影响到后边的值:
#include<stdio.h>
enum Season
{SPRING,SUMMER = 100,AUTUMN,WINTER
};
int main()
{printf("%d\n", SPRING);printf("%d\n", SUMMER);printf("%d\n", AUTUMN);printf("%d\n", WINTER);return 0;
}
例如我们将SUMMER改为100,那么AUTUMN和WINTER也会在此基础上累加。
我们现在也已经了解到,枚举其实也是一种定义常量的方式,那我们前边也学过#define同样可以定义常量,那么为什么非要用枚举类型呢???
2.枚举的优点
- 增加代码的可读性和可维护性。
- 和#define定义的标识符比较,枚举有类型检查,更加严谨。
- 便于调试。
- 使用方便,一次可以定义多个常量。
五.联合体
1.联合体的定义
联合也是一种特殊的自定义类型。
这种类型定义的变量也包含一系列的成员,特征是这些成员共用同一块空间(所以也叫共用体)
#include<stdio.h>
union Un
{int a;char b;
};
int main()
{union Un un;printf("%d\n", sizeof(un));printf("%p\n", &un);printf("%p\n", &(un.a));printf("%p\n", &(un.b));return 0;
}
来看,我们输出一下这个联合体的大小、地址以及其成员的地址:
能够看出,联合体的大小确实是int型的4个字节,且成员变量的地址都相同,这就说明它们共用同一块内存空间。
2.联合体的使用
用来判断编译器的大小端存储:
#include<stdio.h>
int check_sys()
{union Un{int a;char b;}u;u.a = 1;return u.b;//返回1表示小端,返回0表示大端
}
int main()
{int ret = check_sys();if (ret == 1)printf("小端\n");elseprintf("大端\n");return 0;
}
既然共用一块空间,那么我们就可以通过不同类型的字节数来进行大小端的判断。
得出我们当前编译器为小端存储。
对于联合体的具体使用,我们指出一个方向:当成员变量不需要同时使用时,可以使用联合体。
3.联合体大小的计算
- 联合体的大小至少是最大成员的大小。
- 当最大成员大小不是最大对齐数的整数倍时候,就要对齐到最大对齐数的整数倍。
#include<stdio.h>
union Un
{int a;char b[5];
};
int main()
{printf("%d\n", sizeof(union Un));return 0;
}
来看这个联合体,它的大小是多少?5吗???
并不是,而是8。
因为最大对齐数为4,5不是4的整数倍,所以就要浪费3个字节达到8。
六.总结
关于自定义数据类型的讲解到这里就结束啦。
今天的文章也是相当的长啊,快累死博主我了呜呜呜~~~
最后还是希望文章能够帮助到大家,不要忘记一键三连哦!!!
我们下期再见!!!
相关文章:

自定义数据类型
前言:小伙伴们又见面啦,今天这篇文章,我们来谈谈几种自定义数据类型。 目录 一.都有哪些自定义数据类型 二.结构体 结构体内存对齐 1.如何对齐 2.为什么要对齐 3.节省空间和提升效率的方法 (1)让占用空间小的成员…...
产品团队的需求验证和确认
需求核实过程是确保软件满足特定的规格要求,而验证则侧重于软件是否达到了最终用户的期望和需求。 如果你正在开发一种医疗产品,这种区别也可能在法规和标准中有所体现,例如: 820.30(f):设计验证应确认设计的成果符合…...

【JVM】类加载的过程
文章目录 类的生命周期加载验证准备解析初始化简要概括 类的生命周期 一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载 (Loading)、验证(Verification)、准备…...
Golang 结构化日志包 log/slog 详解(四):分组、上下文和属性值类型
上一篇文章讲解了 log/slog 包中的自定义日志属性字段和日志级别,本文讲解下分组、上下文和属性值类型 分组输出 slog 支持将字段放在组中并且可以给分组指定名称。如何展示分组的内容,取决于使用的 handler,例如 TextHandler 使用点号分隔…...

小白学Python:提取Word中的所有图片,只需要1行代码
#python# 大家好,这里是程序员晚枫,全网同名。 最近在小破站账号:Python自动化办公社区更新一套课程:给小白的《50讲Python自动化办公》 在课程群里,看到学员自己开发了一个功能:从word里提取图片。这个…...
pip修改位于用户目录下的缓存目录
默认 pip 缓存目录: Windows: C:\Users\${用户名}\AppData\Local\pip\cache Linux: ~/.cache/pip 一、修改方式 1.命令方式 pip config set global.cache-dir "D:\kwok\data\pip-cache" 2.配置文件方式 ① Windows: C:\Users\${用…...

更新、修改
MySQL从小白到总裁完整教程目录:https://blog.csdn.net/weixin_67859959/article/details/129334507?spm1001.2014.3001.5502 语法: update 表名 列名该列新值, 列名该列新值, ... where 记录匹配条件; 说明:update 更新、修改 set 设置 …...

山西电力市场日前价格预测【2023-09-25】
日前价格预测 预测说明: 如上图所示,预测明日(2023-09-25)山西电力市场全天平均日前电价为442.30元/MWh。其中,最高日前电价为720.46元/MWh,预计出现在19: 00。最低日前电价为276.06元/MWh,预计…...
从collections库的Counter类看items()方法和enumerate()方法
下面的代码是针对文件的词频统计,使用了collections库及其Counter类 import collections def count_word_frequency(text): words text.lower().split() word_counts collections.Counter(words) return word_counts def count_fileword_frequency(fi…...
2023-09-24 LeetCode每日一题(LRU 缓存)
2023-09-24每日一题 一、题目编号 146. LRU 缓存二、题目链接 点击跳转到题目位置 三、题目描述 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类: LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存i…...

《计算机视觉中的多视图几何》笔记(10)
10 3D Reconstruction of Cameras and Structure 本章主要描述了如何利用2张图片来恢复相机的参数以及物体在三维空间中的形状。 文章目录 10 3D Reconstruction of Cameras and Structure10.1 Outline of reconstruction method10.2 Reconstruction ambiguity10.3 The proje…...

【一、虚拟机vmware安装】
安装虚拟机 下载 官方下载地址:https://www.vmware.com/cn.html 大概流程就是,最重要的事最后一步...

uniapp 离线打包 plus.runtime.install 安装页面不弹起
uniapp 离线打包 plus.runtime.install 安装页面不弹起 updateVersion(webview : any, eventTitle : string, eventContent : string) {const loading plus.nativeUI.showWaiting(准备下载);var dtask plus.downloader.createDownload(eventContent,{method: GET,timeout: 5…...

Docker 自动化部署(保姆级教程)
Docker 自动化部署 1. jenkins 介绍1.1 参考链接:1.2 jenkins 概述1.3 jenkins部署项目的流程 2. jenkins 安装2.1 基于docker 镜像2.2 启动 jenkins 后端服务2.3 登录 jenkins 服务后端 3. jenkins自动化部署开始3.1 下载需要的插件3.2 创建任务3.2.1 描述3.2.2 配…...

北工大汇编题——分支程序设计
题目要求 信息检素程序设计:在数据区,有9个不同的信息,编号 0-8,每个信息包括20 个字符。从键盘接收 0-8 之间的一个编号,然后再屏幕上显示出相应编号的信息内容,按“q”键退出 完整代码 DATAS SEGMENTn0…...

贴片电容耐压值选取和特性(包含实际电路和PCB)
一、一般电容的特性 ①容值大的电容,一般通低频率; ②容值小的电容,一般通高频率。 注:详细请看这位博主的篇文章: 大电容为什么虑低频小电容为什么又虑高频?(个人整理) 二、贴片电容的耐压选取 ①贴片电容有2…...
【云原生】kubernetes中pod(进阶)
目录 一、资源限制 业务cpu 内存 1.1CPU 资源单位 1.2 内存 资源单位 示例1 示例2: 二、健康检查:又称为探针(Probe) 2.1探针的三种规则 2.2 Probe支持三种检查方法 2.3示例 示例1:exec方式 示例3…...
Cesium 问题:获取高度值,高度值又是相对于谁来说的
文章目录 问题分析 问题 今天在开发中,甲方提出一个这样的问题,你的高度是怎么算出来的,对此,我只知道使用并不知道怎么来的,因此特意查了一番资料,希望帮助到大家 分析 在 Cesium 中,我们可以使…...
第三、四、五场面试
第三场 共享屏幕做题(三道简单题) 替换空格成%20(双指针) 删除升序链表中的重复元素(指针)有效的括号(栈) 第四场、第五场 自我介绍 项目拷打 整个项目架构rpc模块的情况分析的数…...

力扣-290.单词规律
Idea 先建立一个hashmap,记录s串中的每个单词以及对应的下标再建立一个hashmap,记录pattern串中相同字母以及对应的下标遍历pattern串时,遇到不同字母存到pat表中,同时将下标对应的s中的单词存入到查重test集中,因为如…...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?
在建筑行业,项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升,传统的管理模式已经难以满足现代工程的需求。过去,许多企业依赖手工记录、口头沟通和分散的信息管理,导致效率低下、成本失控、风险频发。例如&#…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...

MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...
LLM基础1_语言模型如何处理文本
基于GitHub项目:https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken:OpenAI开发的专业"分词器" torch:Facebook开发的强力计算引擎,相当于超级计算器 理解词嵌入:给词语画"…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...

Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...
AGain DB和倍数增益的关系
我在设置一款索尼CMOS芯片时,Again增益0db变化为6DB,画面的变化只有2倍DN的增益,比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析: 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...