自定义数据类型

前言:小伙伴们又见面啦,今天这篇文章,我们来谈谈几种自定义数据类型。

dfec8a4803204ff59f81f388be2adf91.webp 


 

目录

一.都有哪些自定义数据类型

二.结构体

结构体内存对齐

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呢???我们来看结果:

9af7a467b8f740e6b4ad5d71575cf41a.png

哇塞,天差地别不仅不是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的位置存放四个字节。

88759d38224744c89973bdafa46f2a92.png5dc6a562a3eb4073ae1756ff14dae5ad.png7b7996e53dfd46c1bf9222b8f83be9b7.png

这样一来我们就得到了我们的结果,8个字节

下边我们继续来看一个例子:

struct Str2
{
    char a;
    int b;
    char c;
};

第一个为char型,放在0处。

第二个为int型,对齐数为4,现在只占用了一个字节,不是4的整数倍,所以要浪费3个字节,从标号为4的位置开始,占用四个字节。

第三个为char型,对齐数为1,8是1的整数倍,所以直接从标号为8的位置开始,占用一个字节。

c7d7d1f527004ae0a0b555d4662c2e80.png3e8d8d0abaec4aceaf66ec86f35555a3.png200d3043814547c9bd9596f355682771.png

这个时候出问题了,这不是才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个字节。

 d5b8e42e74fb4388af7657ad38f75d27.pnga6e5580358774e189fc46a94d08c8dd6.pnga7672f08a9cd4c84a249a5d023a785ba.png

这样,我们总共就是占用了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的整数倍,同时也要记得及时恢复默认对齐数的原值,确保只有这一块的结构体我们需要修改,以免发生错误

来看结果:

 3a781cf34d07457fb2d837b577a99cf0.png

除此之外,我们还有第三种方法,那就是——位段。 


三.位段

讲完了结构体的内存分配情况之后,我们就得紧接着来谈谈结构体实现位段的能力。

 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个字节的大小,但是使用位段之后来看:

d8b742672ef845a2bca73823c5a3b05b.png

只用到了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.位段的跨平台问题

  1. int位段被当成有符号数还是无符号数是不确定的。
  2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32)
  3. 位段中的成员在内存中从左向右分配还是从右往左分配标准尚未定义。
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段的剩余位时,是舍弃剩余的位还是利用是不确定的。

对于第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;
}

结果如下:

 83f5e8c208074f8494c09b60c621091e.png

枚举常量不能在枚举类型外修改,但是可以在其定义时修改,并且会影响到后边的值

#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也会在此基础上累加

14f53248fcba43b78216bf4d206a9d8d.png

 我们现在也已经了解到,枚举其实也是一种定义常量的方式,那我们前边也学过#define同样可以定义常量,那么为什么非要用枚举类型呢???

2.枚举的优点

  1. 增加代码的可读性和可维护性。
  2. 和#define定义的标识符比较,枚举有类型检查,更加严谨。
  3. 便于调试。
  4. 使用方便,一次可以定义多个常量。

     

五.联合体

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;
}

 来看,我们输出一下这个联合体的大小、地址以及其成员的地址:

b21abd87d36d4339beab22c212986a6a.png

 能够看出,联合体的大小确实是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;
}

既然共用一块空间,那么我们就可以通过不同类型的字节数来进行大小端的判断

5448466ac8a8411fa59c4aca24d4cea8.png

得出我们当前编译器为小端存储

对于联合体的具体使用,我们指出一个方向:当成员变量不需要同时使用时,可以使用联合体

 3.联合体大小的计算

  • 联合体的大小至少是最大成员的大小。
  • 当最大成员大小不是最大对齐数的整数倍时候,就要对齐到最大对齐数的整数倍。
#include<stdio.h>
union Un
{int a;char b[5];
};
int main()
{printf("%d\n", sizeof(union Un));return 0;
}

来看这个联合体,它的大小是多少?5吗???

d87681a61e51453794fa21973c9ceba5.png

并不是,而是8

因为最大对齐数为4,5不是4的整数倍,所以就要浪费3个字节达到8。 

六.总结

关于自定义数据类型的讲解到这里就结束啦。

今天的文章也是相当的长啊,快累死博主我了呜呜呜~~~

 f197bc5bf465436dbab5a9ceb324b3a3.gif

最后还是希望文章能够帮助到大家,不要忘记一键三连哦!!!

我们下期再见!!! 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/141154.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

SD-MTSP:萤火虫算法(FA)求解单仓库多旅行商问题MATLAB(可更改数据集,旅行商的数量和起点)

一、萤火虫算法&#xff08;FA&#xff09;简介 萤火虫算法(Firefly Algorithm&#xff0c;FA)是Yang等人于2009年提出的一种仿生优化算法。 参考文献&#xff1a;田梦楚, 薄煜明, 陈志敏, et al. 萤火虫算法智能优化粒子滤波[J]. 自动化学报, 2016, 42(001):89-97. 二、单仓…

Java基础常考知识点(基础、集合、异常、JVM)

作者&#xff1a;逍遥Sean 简介&#xff1a;一个主修Java的Web网站\游戏服务器后端开发者 主页&#xff1a;https://blog.csdn.net/Ureliable 觉得博主文章不错的话&#xff0c;可以三连支持一下~ 如有需要我的支持&#xff0c;请私信或评论留言&#xff01; Java基础常考知识点…

【golang】调度系列之sysmon

调度系列 调度系列之goroutine 调度系列之m 调度系列之p 在golang的调度体系中&#xff0c;除了GMP本身&#xff0c;还有另外一个比较重要的角色sysmon。实际上&#xff0c;除了GMP和sysmon&#xff0c;runtime中还有一个全局的调度器对象。但该对象只是维护一些全局的数据&…

ROS2 的行为树 — 第 1 部分:解锁高级机器人决策和控制

一、说明 在复杂而迷人的机器人世界中&#xff0c;行为树&#xff08;BT&#xff09;已成为决策过程中不可或缺的一部分。它们提供了一种结构化、模块化和高效的方法来对机器人的行为进行编程。BT起源于视频游戏行业&#xff0c;用于控制非玩家角色&#xff0c;他们在机器人领域…

FPGA板卡启动以及LED灯带调试

环境配置 软件&#xff1a; MobaXterm&#xff08;free版本即可&#xff09;下载教程参考&#xff1a; MobaXterm&#xff08;终端工具&#xff09;下载&安装&使用教程_蜗牛也不慢......的博客-CSDN博客 Win32 Disklmager 下载教程参考&#xff1a; 不分类工具&am…

公众号迁移多久可以完成?

公众号账号迁移的作用是什么&#xff1f;只能变更主体吗&#xff1f;长期以来&#xff0c;由于部分公众号在注册时&#xff0c;主体不准确的历史原因&#xff0c;或者公众号主体发生合并、分立或业务调整等现实状况&#xff0c;在公众号登记主体不能对应实际运营人的情况下&…

在北京多有钱能称为富

背景 首先声明&#xff0c;此讨论仅限个人的观点&#xff0c;因为我本身不富嘛&#xff0c;所以想法应该非常局限。 举个栗子 富二代问我朋友&#xff0c;100~1000w之间&#xff0c;推荐一款车&#xff1f; 一开始听到这个问题的时候&#xff0c;有被唬住&#xff0c;觉得预…

基于Python+Django的热门旅游景点数据分析系统的设计与实现(源码+lw+部署文档+讲解等)

前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f447;&#x1f3fb;…

什么是IoT数字孪生?

数字孪生是资产或系统的实时虚拟模型&#xff0c;它使用来自连接的物联网传感器的数据来创建数字表示。数字孪生允许您从任何地方实时监控设备、资产或流程。数字孪生用于多种目的&#xff0c;例如分析性能、监控问题或在实施之前运行测试。从物联网数字孪生中获得的见解使用户…

18795-2012 茶叶标准样品制备技术条件

声明 本文是学习GB-T 18795-2012 茶叶标准样品制备技术条件. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了各类茶叶(除再加工茶)标准样品的制备、包装、标签、标识、证书和有效期。 本标准适用于各类茶叶(除再加工茶)感官品质…

怎么在OPPO手机桌面上添加文字?便签桌面插件添加教程

很多年轻女性在选择手机时&#xff0c;都比较青睐于设计时尚靓丽、轻薄且续航好、系统流畅、拍照清晰的OPPO手机&#xff0c;并且OPPO为不同的用户提供了高中低不同价格档位的手机型号&#xff0c;能够满足绝大多数女性消费者的使用需求。 不过有不少OPPO手机用户表示&#xf…

华为NFC设置教程(门禁卡/公交卡/校园卡等)

今天把华为NFC设置教程分享给大家 出门带门禁卡、校园卡、银行卡、身份证……东西又多&#xff0c;携带又麻烦&#xff0c;还容易搞丢&#xff0c;有没有一种方法可以把它们都装下&#xff1f;有&#xff01;只要一部手机&#xff0c;出门不带卡包&#xff0c;各种证件&#x…

【知识分享】Java获取全年每个月的有几周且每周是几号到几号

加哥本周给大家分享一期怎么用java把全年每个月有几周&#xff0c;本周是几号到几号的工具类。便于大家根据需求获取想要的形式进行改造。话不多说&#xff0c;直接给大家上代码。 package com.techfantasy.common.utils; import com.techfantasy.common.entity.DateRange; i…

安全测试之w3af 安装

背景&#xff1a; 学习安全测试时&#xff0c;离不开一些安全扫描工具&#xff0c;在类目众多的工具中&#xff0c;w3af是个绕不开的集成工具。由于没有linux环境&#xff0c;故在windows下利用wsl进行部署。亦可通过其它虚拟机安装亦可。 借鉴&#xff1a;Win10下安装w3af_Da…

NSSCTF做题

[第五空间 2021]WebFTP 打开题目 发现是登录的界面 用admin和password试一下发现不行 用dirsearch扫一下 发现了git泄露 但是用githack下载不下来文件 去网上查了一下webftp 发现是一个在线php文件管理系统 WebFTP——在线FTP工具:强大的PHP在线文件管理系统-時日 在这篇博客…

leetcode top100(20) 搜索二维矩阵 II

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。每列的元素从上到下升序排列。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,2…

6.1 使用scikit-learn构建模型

6.1 使用scikit-learn构建模型 6.1.1 使用sklearn转换器处理数据6.1.2 将数据集划分为训练集和测试集6.1.3 使用sklearn转换器进行数据预处理与降维1、数据预处理2、PCA降维算法 代码 scikit-learn&#xff08;简称sklearn&#xff09;库整合了多种机器学习算法&#xff0c;可以…

mysql Your password does not satisfy the current policy requirements

在修改密码时遇到 Your password does not satisfy the current policy requirements 原因&#xff1a;您的密码不符合当前策略要求&#xff0c;最好是把密码设置成复杂的&#xff0c;包括字母大小写、数字、特殊字符。 如果你还是先把数据库密码改简单&#xff0c;比如你本地…

C++【个人笔记1】

1.C的初识 1.1 简单入门 #include<iostream> using namespace std; int main() {cout << "hello world" << endl;return 0; } #include<iostream>; 预编译指令&#xff0c;引入头文件iostream.using namespace std; 使用标准命名空间cout …

AUTOSAR 面试知识回顾

如果答不上来&#xff0c;就讲当时做了什么 1. Ethernet基础: 硬件接口&#xff1a; ECU到PHY&#xff1a; data 是MII总线&#xff0c; 寄存器控制是SMI总线【MDCMDIO两根线, half duplex】PHY输出(100BASE-T1)&#xff1a; MDI总线&#xff0c;2 wire 【T1: twisted 1 pair …