自定义数据类型

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

 


 

目录

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

二.结构体

结构体内存对齐

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

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

结果如下:

 

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

#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.枚举的优点

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

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

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

六.总结

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

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

 

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

我们下期再见!!! 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Qt5开发及实例V2.0-第二十三章-Qt-多功能文档查看器实例

Qt5开发及实例V2.0-第二十三章-Qt-多功能文档查看器实例 第23章 多功能文档查看器实例23.1. 简介23.2. 界面与程序框架设计23.2.1. 图片资源23.2.2. 网页资源23.2.3. 测试用文件 23.3 主程序代码框架23.4 浏览网页功能实现23.4.1 实现HtmIHandler处理器 23.5. 部分代码实现23.5…

AI 编码助手 Codewhisperer 安装步骤和使用初体验

文章作者&#xff1a;为了自己加油 最近亚⻢逊云科技推出了一款基于机器学习的AI编程助手 Amazon Code Whisperer&#xff0c;可以实时提供代码建议。在编写代码时&#xff0c;它会自动根据现有的代码和注释给出建议。Amazon Code Whisperer与 GitHub Copilot 类似&#xff0c;…

Vite的安装与使用

Vite也是前端的构建工具&#xff0c;相较于Webpack&#xff0c;Vite使用了不同的运行方式&#xff1a; 开发时并不对项目进行打包&#xff0c;而是直接采用ESM的方式来运行项目。在项目部署时再进行打包。 因此vite的执行速度相较于Webpack快了许多&#xff0c;操作起来也比W…

SQL中:提示不允许修改表结构,如何更改

SQL&#xff1a;不允许修改表结构 步骤图例注意 步骤 选择菜单栏中的“工具”-“选项”&#xff0c;在选项对话框左栏中找到“设计器”&#xff0c;在设计器右边取消勾选“阻止保存要求重新创建表的更改”即可。 图例 注意 设计表时&#xff0c;尽量一次性设计成功&#xff…

Android12之解封装NuMediaExtractor::setDataSource过程(四十七)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药. 更多原创,欢迎关注:Android…

Springboot ruoyi配置mysql备份定时任务

一、RuoYiConfig.class 新增获取备份路径方法 public static String getDataBaseBackUp() {return getProfile() "/dbBackUp";} 二、RyTask&#xff1a;新增备份数据库方法 mySqlDump方法&#xff1a;参数详见代码 package com.ruoyi.quartz.task;import cn.hut…

【动手学深度学习-Pytorch版】序列到序列的学习(包含NLP常用的Mask技巧)

序言 这一节是对于“编码器-解码器”模型的实际应用&#xff0c;编码器和解码器架构可以使用长度可变的序列作为输入&#xff0c;并将其转换为固定形状的隐状态&#xff08;编码器实现&#xff09;。本小节将使用“fra-eng”数据集&#xff08;这也是《动手学习深度学习-Pytor…

linux用户和权限命令学习记录

文章目录 版权声明root用户&#xff08;超级管理员&#xff09;su和exit命令sudo命令为普通用户配置sudo认证 用户、用户组管理用户组管理getent命令 查看权限控制认知权限信息 修改权限控制chmod修改文件、文件夹的权限权限的数字序号chown修改所属用户、用户组 版权声明 本博…

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

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

机器学习第十课--提升树

一.Bagging与Boosting的区别 在上一章里我们学习了一个集成模型叫作随机森林&#xff0c;而且也了解到随机森林属于Bagging的成员。本节我们重点来学习一下另外一种集成模型叫作Boosting。首先回顾一下什么叫Bagging? 比如在随机森林里&#xff0c;针对于样本数据&#xff0c;…

使用命令行快速创建Vite项目

一、构建项目 在终端中使用如下命令行&#xff1a; npm create vite 二、定义项目名称 三、选择项目类型 Vanilla是我们常用的JavaScript&#xff0c;Vue和React是常用前端框架&#xff0c;可以根据自己的需要进行选择 通过上下键进行选择&#xff0c;按下回车进行确认 创建…

docker容器安装MongoDB数据库

一&#xff1a;MongoDB数据库 1.1 简介 MongoDB是一个开源、高性能、无模式的文档型数据库&#xff0c;当初的设计就是用于简化开发和方便扩展&#xff0c;是NoSQL数据库产品中的一种。是最 像关系型数据库&#xff08;MySQL&#xff09;的非关系型数据库。 它支持的数据结构…

Neo4j图数据库_web页面关闭登录实现免登陆访问_常用的cypher语句_删除_查询_创建关系图谱---Neo4j图数据库工作笔记0013

由于除了安装,那么真实使用的时候,就是导入数据了,有了关系和节点的csv文件以后如果用 cypher进行导入数据和创建关系图谱,还有进行查询,以及如果导入错误如何清空,大概是这些 用的最多的,单独把这些拿进来,总结一下,用的会比较方便. 1.实现免登陆访问: /data/module/neo4j-…

Spring学习笔记6 Bean的实例化方式

Spring学习笔记5 GoF之工厂模式_biubiubiu0706的博客-CSDN博客 Spring为Bean提供了多种实例化方式,通常包括4中(目的:更加灵活) 1.通过构造方法实例化 2.通过简单工厂模式实例化 3.通过factory-bean实例化 4.通过FactoryBean接口实例化 新建模块 spring-005 依赖 <!--S…

结构体,联合体与位段

1.结构体的内存对齐(计算结构体的大小) 1.1 为什么需要结构体内存对齐? 原因1:平台原因 不是所有的硬件平台都能访问任意地址上的任意数据的&#xff1b;某些平台只能在某些地址处取得某些特定类型的数据&#xff0c;否则抛出硬件异常。 比如&#xff0c;当一个平台要取一个…

Qt5开发及实例V2.0-第二十二章-Qt.Quick Controls 2新颖界面开发

Qt5开发及实例V2.0-第二十二章-Qt.Quick Controls 2新颖界面开发 第22章 Qt Quick Controls 2新颖界面开发22.1 Qt Quick Controls 2简介22.1.1 第一个Qt Quick Controls 2程序22.1.2 Qt Quick Controls 2程序的构成 22.2 Qt Quick Controls 2与1的比较22.2.1 ApplicationWindo…

紫光展锐6nm国产5G处理器T820_国产手机芯片5G方案

紫光展锐T820是一款采用先进6nm EUV工艺的芯片&#xff0c;采用134三丛集八核心CPU架构&#xff0c;由1个主频为 2.7GHz 的 Arm Cortex-A76 大核和 3个主频为2.3GHz 的Arm Cortex-A76大核以及4个主频为2.1GHz的 Arm Cortex-A55组成 &#xff0c;支持高达3MB 三级缓存&#xff0…

MySQL 篇

目录 1、数据库三范式 2、数据库事务的特性 3、MySQL数据库引擎 4、说说 InnoDB 与 MyISAM 的区别 5、索引是什么&#xff1f; 6、索引数据结构 7、MySQL 索引类型有哪些&#xff1f; 8、索引有什么优缺点&#xff1f; 9、索引设计原则 9、使用索引应该注意些什…

ubuntu 开启笔记本摄像头并修复画面颠倒问题

文章目录 基本环境状况&#xff1a; 没找到摄像头检查 opencv检查系统应用 键盘右侧&#xff0c;硬件层面开启摄像头画面镜像问题 基本环境 笔记本&#xff1a; 联想拯救者 系统&#xff1a; ubuntu 22.04 状况&#xff1a; 没找到摄像头 检查 opencv 使用 cv::VideoCaptu…

实验室安全教育与考试

目录 我的错题&#xff08;2个&#xff09;新知识题目&#xff08;10个&#xff09;刚开始不太理解的题目&#xff08;10个&#xff09;写在最后&#xff08;免责声明&#xff09; 我的错题&#xff08;2个&#xff09; 18.发生电气火灾时可以使用的灭火设备包括&#xff1a;&…