自定义类型详解 结构体,位段,枚举,联合

目录

结构体

1.不完全声明

2.结构体的自引用

3.定义与初始化

4.结构体内存对齐与结构体类型的大小

结构体嵌套问题

位段

1.什么是位段?

2.位段的内存分配

枚举

1.枚举类型的定义

2.枚举的优点

联合(共同体)

1.联合体类型的声明以及变量定义

2.联合体的特点

利用联合体判断当前机器是大端还是小端

3.联合体大小的计算


结构体

结构体的基础知识以前的博客已经介绍过了,这里是进阶版

1.不完全声明

在创建结构体类型的时候可以省略标签,如图

这种结构体被称为匿名结构体,如果这样创建,那我们就只能在创建类型的同时创建变量,无法在其他地方创建变量,比如这里的x就是在创建此匿名结构体类型的同时创建的变量。

如果我们再创建一次匿名结构体类型

他与上面的匿名结构体成员变量均相同,但是我在这里创建了一个结构体指针p,那么p=&x这种写法正确吗?答案是不正确,编译器会把上面的两个声明当做不同的类型。

2.结构体的自引用

有这样一段代码

对于这样一段代码,如果用sizeof来计算这种类型的大小,直接就计算不出来,因为在结构体类型里面又包含了一个同样类型的变量,那这个变量势必又会包含一个int类型的data和又一个结构体类型的变量,无限套娃,都无法计算这种类型的大小,显然这样的自引用是错误的。

正确的自引用方式是使用指针

来看下面一段代码

我们先把一个匿名结构体类型进行重命名为Node,然后使用他在主函数里面创建变量,可以吗?

答案是不可以,这就像我们遇到的经典问题,先有鸡还是先有蛋,再创建类型的时候我都还没有重命名为Node,居然就先使用了,显然是错误的。

正确写法

3.定义与初始化

可以先创建类型再初始化,也可以在创建类型的同时初始化

如图是在创建类型的同时初始化

初始化的时候可以直接像sn1一样按顺序写,这样编译器认为是按照结构体成员出现的顺序初始化的,也可以使用点操作符,这样就可以按照自己的顺序来初始化。

4.结构体内存对齐与结构体类型的大小

运行这样一段代码

我们想要计算这两个结构体类型的大小,而这个结构体类型里面成员变量有一个int类型和两个char类型,大小加起来应该是六个字节,但是我们打印出来发现,结果居然是12和8,不仅不是6,甚至都两次结果都不一样大,这是为什么呢?

通过offsetof计算出struct s1各个成员变量离起始地址的偏移量分别是0,4,8,(offsetof是一个宏,具体用法这里就不展开讲了)于是我们可以画出struct s1的成员变量在内存中的分布

那也就是占了九个字节,为什么struct s1的大小会是12呢?

这就涉及到了内存对齐

首先得掌握结构体的对齐规则:

1. 第一个成员在与结构体变量偏移量为0的地址处。

2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。VS中默认的值为8,gcc环境没有对齐数,如果成员变量是一个数组,则他的对齐数是数组中元素的字节大小与默认对齐数的较小值,比如char ch[5]={0};他的对齐数大小就是1

3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。

4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

知道了这些规则之后再来看上面的struct s1在内存中为什么这样存储,struct s1的第一个成员变量是char类型的,因此对齐数是1,因此第一个成员变量就放在了偏移量为零的单元,第二个成员变量i是int类型的,对齐数是4,应该放偏移量是在4的整数倍的单元处,因此从4开始放四个字节,浪费了三个字节单元,第三个成员变量也是char类型的,对齐数是1,因此接着放一个字节,如图所示

一共占用了9个字节,但是根据第三条规则,结构体类型的大小必须是所有成员变量中最大对齐数的整数倍,struct s1的成员变量最大对齐数是4,9不是4的倍数,此时离着4的倍数最近的字节数是12,因此struct s1的大小是12个字节。

同样的,再来看struct s2

他的第一个成员变量是int类型的,从偏移量为零的地址处开始存放,放了四个字节,第二个成员变量是char类型的,对齐数是1,直接接着放就行,因为任何数都是1的整数倍。第三个成员变量也是char类型的,对齐数是1,接着放一个字节,发现一共用了6个字节,最大对齐数是4,6不是4的倍数,离着最近的一个4的倍数是8,因此struct s2的大小是8

结构体嵌套问题

struct s4类型的第一个成员变量是char类型的,对齐数是1,直接在偏移量为0的地址处开始存放,第二个成员变量是struct s3类型的,他的成员变量最大对齐数是8,因此他要对齐到偏移量为8的整数倍的位置,离着最近的就是从偏移量为8的位置开始存放,存放多少个字节,那就要看struct s3类型占多少个字节。

我们假设又有一块空间存放着struct s3

struct s3的第一个成员变量是double类型,对齐数是8,从偏移量为0的地址处开始存放,存放了8个字节,存到了偏移量为7的位置,第二个成员变量是char类型的,对齐数是1,接着存一个字节,存到了偏移量为8的位置,第三个成员变量是int类型的,对齐数是4,但是接下来要存的单元偏移量是9,不是4的整数倍,因此要从偏移量为12的位置开始存放四个字节,到了偏移量为15的位置。又因为struct s3的成员变量最大对齐数是8,因此struct s3所占的字节大小就是离15最近的8的整数倍也就是16.

再回到struct s4的存储,从偏移量为8的单元开始放16个字节,到了偏移量为23的位置,struct s4的第三个成员变量是double类型的,对齐数是8,而接下来要存放的位置偏移量是24,恰好是8的整数倍,因此接着存放8个字节单元到了偏移量为31的单元处。共计占用了32个字节。所有成员变量的最大对齐数是8,而32恰好是8的整数倍,因此struct s4的大小是32个字节

为什么存在内存对齐?

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

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

总体来说结构体的内存对齐是拿空间来换取时间的做法。

注:可以通过#pragma pack(x)来设置默认对齐数为x,如果想还原默认的对齐数就再调用一次#pragma pack(),括号内什么也不写就行了

5.结构体传参

这在初阶的时候已经介绍过,可以传一个结构体变量,这样访问的时候就用点操作符,也可以传一个结构体指针,这样访问的时候就用箭头操作符,推荐使用传结构体指针的形式,因为传结构体变量会导致形参在内存中开辟一块与原来结构体一样大的内存,造成内存的浪费。再者如果是传的结构体变量,实际上他是实参的一份临时拷贝,对他进行的任何操作都不会改变实参,因而我们就无法通过调用函数来操作实参了。

位段

1.什么是位段?

位段的声明和结构是类似的,有两个不同:

1.位段的成员必须是 int、unsigned int 或signed int或char 。

2.位段的成员名后边有一个冒号和一个数字。冒号后面的数字代表这个成员变量占的比特位。注意是比特位,不是字节。

但是位段与结构体是两个不同的概念,比如

那么A就是一个位段类型,注意不是struct A,而是A就是一个位段类型。用A创建的变量,就是位段类型变量。

他表示_a占2个比特位,_b占5个比特位,以此类推。通过sizeof计算A的大小,发现是8个字节。

为什么要有位段呢?假如我只需要_a表示四种情况,那么两个比特位就足够了,但是int类型的_a我们却为他开辟了32个bit位,实际有30个bit浪费了,因此位段是一种节省空间的方法。

2.位段的内存分配

1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型

2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。

3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

为什么说位段具有不确定因素呢?还是以前面的例子为代表

再创建_a的时候,开辟了4个字节也就是32个比特位,然后用了两个,剩下了30个比特位,那这里就有不确定因素

比如这2个比特位是在刚才开辟的空间的左边还是右边?

接着创建了_b变量,占5个字节,接着在剩下的30个bit里面放5个,接着创建_c又放了10个,这时候还剩下15个,此时创建_d要占去30个bit,剩下的位置已经不够放了,只能再申请一块32bit的内存,那这里又有不确定因素了,新申请的空间能直接放下_d,那么原来剩下的15个bit还放不放?是先放15个到上一块空间中,再放15个bit到新申请的空间中,还是直接放30个bit到新申请的空间中?这些都是不确定因素,C语言并没有规定。

有这样一个位段,在我使用sizeof计算这个位段大小的时候发现是3个字节,那么S在内存中就有可能是这样存的

我画的是从右往左存的,当然也可能是从左往右存的,但是目前能确定的是这里如果原来开辟的内存不够用了,再申请一块新空间的时候,原来剩下不够的那些内存没有使用

再来测试一下是从左往右还是从右往左

对s进行如图的初始化,假如是从右往左放的话,内存中的存储应该是这样

换算成16进制应该是620304,通过的调试,发现确实如此。

因此在当前环境下位段就是从右往左,不够的空间就不用的方式分配内存。

枚举

1.枚举类型的定义

枚举顾名思义就是一一列举。把可能的取值一一列举。比如我们现实生活中:一周的星期一到星期日是有限的7天,可以一一列举。

enum Day 就是枚举类型。大括号里面是可能的取值,中间用逗号隔开,这与结构体类型的声明是不同的,结构体类型大括号里面是成员变量,用分号隔开。在使用枚举类型创建变量的时候就只能赋值成大括号里面的某个内容,比如enum Day x=Mon;

注:只有能一一列举的值才使用枚举类型,像身高体重这种可能的取值太多了,是不可能使用枚举类型的。

大括号里面列举的内容都是有值的,默认是从零开始,当然也可以在定义的时候进行赋值,如图所示

2.枚举的优点

我们可以使用 #define 定义常量,为什么非要使用枚举?

枚举的优点:1. 增加代码的可读性和可维护性

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

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

4. 便于调试

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

下面是一个枚举使用的例子

实际上这个GREEN就是2,但是我们不能直接把2赋给clr,增加了代码的可读性。

联合(共同体)

联合也是一种特殊的自定义类型这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。

1.联合体类型的声明以及变量定义

2.联合体的特点

通过打印地址我们发现c和i的地址居然是一样的,这就说明c和i共用了同一块内存单元。这样虽然节省了空间,但是会有其他的问题,比如我在修改c的同时必然会把i也修改了,因此对于联合类型来讲,只能用其中的某一个成员变量。

利用联合体判断当前机器是大端还是小端

我们把i初始化为1,如果是小端,那么这四个字节存的就是01 00 00 00(16进制),如果是大端,这四个字节就是00 00 00 01,也就是说我们只需要判断第一个字节的内容即可,由于i和c又共用一块内存,因此在初始化i的时候c也会被初始化,c的内容就是第一个字节单元的内容,小端机器,高位在高地址,第一个字节单元应该是01,大端则是00,由此我们可以判断出当前机器是大端存储还是小端存储。

注:也可以直接创建一个int类型的变量a,初始化为1,然后

*(char*)&a判断是0还是1

3.联合体大小的计算

联合的大小至少是最大成员的大小。当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

union Un1中第一个成员变量占5个字节,对齐数是1,第二个成员变量占4个字节,对齐数是4。

根据上面的规则,联合体大小最小的是最大成员变量所占字节大小,也就是5,但是5并不是最大对齐数也就是4的整数倍,离着最近的4的倍数是8,因此这个联合体union Un1的大小是8

union Un2第一个成员变量占14个字节,对齐数是2,第二个成员变量占4个字节,对齐数是4,。

union Un2中最大成员变量所占字节为14,最大对齐数是4,而14不是4的整数倍,离着最近的倍数是16,因此union Un2类型的大小是16个字节。

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

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

相关文章

Vue语法

1.vue模板语法2大类 插值语法: 功能:用于解析标签内容 用途:用于标签内容定义 写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性 指令语法: 功能:用于解析标签&am…

去空行小工具Html + Javascript

这是一个平常用到的小工具&#xff0c;为了节省屏幕空间把空行去掉&#xff0c;怕要用的时候找不到故记录在此。 效果图 网页版&#xff0c;放在浏览器里就可以用 <!doctype html> <html><head><meta charset"utf-8"><title>去回车…

JMeter性能测试系列一初识JMeter

1.JMeter介绍 Apache组织的Stefano Mazzocchi是JMeter项目的创始人。编写JMeter最初的目的是为了测试server的性能(后期被Tomcat替代)。随后&#xff0c;JMeter在Apache组织内部开始被其他项目所使用&#xff0c;并最终推广出来&#xff0c;成为独立的软件项目并不断更新&…

内网渗透Searchall敏感凭证信息搜索工具

一、开发背景 在实战中进入内网的时候&#xff0c;大家需要搜集一些敏感信息例如账号&#xff0c;密码甚至浏览器的账号密码。searchall完美解决了这个问题。所以我就结合自身的经验写了一款搜索敏感信息的利用工具。它可以搜索敏感信息&#xff0c;更快为你获取到有价值的信息…

【51单片机】LED点阵屏(江科大)

9.1LED点阵屏 1.LED点阵屏介绍 LED点阵屏由若干个独立的LED组成,LED以矩阵的形式排列,以灯珠亮灭来显示文字、图片、视频等。 2.LED点阵屏工作原理 LED点阵屏的结构类似于数码管,只不过是数码管把每一列的像素以“8”字型排列而已。原理图如下 每一行的阳极连在一起,每一列…

Codeforces Round 926 (Div. 2)(A,B,C,D,E,F)

这场还是很有含金量的&#xff0c;B题开始就有难度了&#xff0c;B是个推结论的题&#xff0c;C要推结论然后递推&#xff0c;D题是有点难的树上DP&#xff08;主要是状态转移方程不好写&#xff09;&#xff0c;E题是个二进制预处理然后状压DP&#xff0c;F题是个数论&#xf…

【Redis快速入门】Redis三种集群搭建配置(主从集群、哨兵集群、分片集群)

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大三在校生&#xff0c;喜欢AI编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;落798. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc;️…

python系统学习Day2

section3 python Foudamentals part one&#xff1a;data types and variables 数据类型&#xff1a;整数、浮点数、字符串、布尔值、空值 #整型&#xff0c;没有大小限制 >>>9 / 3 #3.0 >>>10 // 3 #3 地板除 >>>10 % 3 #1 取余#浮点型&#xff…

红队笔记Day2 -->上线不出网机器

今天就来讲一下在企业攻防中如何上线不出网的机器&#xff01;&#xff01; 1.基本网络拓扑 基本的网络拓扑就是这样 以下是对应得的P信息&#xff0c;其中的52网段充当一个内网的网段&#xff0c;而111充当公网网段 先ping一下&#xff0c;确保外网ping不通内网&#xff0c;内…

2024年道路运输企业主要负责人证模拟考试题库及道路运输企业主要负责人理论考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年道路运输企业主要负责人证模拟考试题库及道路运输企业主要负责人理论考试试题是由安全生产模拟考试一点通提供&#xff0c;道路运输企业主要负责人证模拟考试题库是根据道路运输企业主要负责人最新版教材&#…

你知道.NET的字符串在内存中是如何存储的吗?

一、字符串对象的内存布局 从“值类型”和“引用类型”来划分&#xff0c;字符串自然属于引用类型的范畴&#xff0c;所以一个字符串对象自然采用引用类型的内存布局。引用类型实例的内存布局总的来说整个内存布局分三块&#xff1a;ObjHeader TypeHandle Payload。对于一般…

EasyCaptcha,开源图形验证码新标杆!

引言&#xff1a; 随着互联网的普及&#xff0c;验证码已成为网站和应用程序中不可或缺的安全组件。它能够有效地防止自动化攻击、垃圾邮件和机器人活动。在众多验证码解决方案中&#xff0c;Easy-captcha以其简单易用和高度可定制的特点受到了开发者的青睐。本文将指导读者如…

浅谈业务场景中缓存的使用

浅谈缓存 一、背景二、缓存分类1.本地缓存2.分布式缓存 三、缓存读写模式1.读请求2.写请求 四、缓存穿透1.缓存空对象2.请求校验3.请求来源限制4.布隆过滤器 五、缓存击穿1.改变过期时间2.串行访问数据库 六、缓存雪崩1.避免集中过期2.提前更新缓存 七、缓存与数据库一致性1.设…

【MySQL】学习约束和使用图形化界面创建表

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-iqtbME2KmWpQFQSt {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

java.lang.NoClassDefFoundError: org/springframework/core/GenericTypeResolver

前言 小编我将用CSDN记录软件开发求学之路上亲身所得与所学的心得与知识&#xff0c;有兴趣的小伙伴可以关注一下&#xff01; 也许一个人独行&#xff0c;可以走的很快&#xff0c;但是一群人结伴而行&#xff0c;才能走的更远&#xff01;让我们在成长的道路上互相学习&…

BUGKU-WEB GET

题目描述 没有提示&#xff0c;就一个get&#xff0c;启动场景看看&#xff1a; 解题思路 显然是PHP语言解读分析代码吧写出你的payload 相关工具 略 解题步骤 进入场景分析代码 $what$_GET[what]; echo $what; if($whatflag) echo flag{****};前两句&#xff1a;使用get…

RCS系统之:机器人状态

在设计RCS系统平台时&#xff0c;机器人总共设计状态有&#xff1a; 离线模式&#xff1b; 如图&#xff0c;18号机器人呈灰黑色&#xff0c;表示机器人没有上电状态 工作模式&#xff1b; 如图&#xff0c;10号机器人成绿色&#xff0c;表示机器人处于工作模式&#xff0c;等…

边缘计算:重塑数字世界的未来

引言 随着物联网&#xff08;IoT&#xff09;设备的激增和5G网络的普及&#xff0c;我们正站在一个计算模式的新纪元门槛上——边缘计算。这一技术范式将数据处理和分析推向网络的边缘&#xff0c;即设备或终端&#xff0c;为实时性要求较高的应用提供了前所未有的可能性。 目…

Rust 数据结构与算法:5栈:用栈实现前缀、中缀、后缀表达式

3、前缀、中缀和后缀表达式 计算机是从左到右处理数据的&#xff0c;类似(A (B * C))这样的完全括号表达式&#xff0c;计算机如何跳到内部括号计算乘法&#xff0c;然后跳到外部括号计算加法呢&#xff1f; 一种直观的方法是将运算符移到操作数外&#xff0c;分离运算符和操…

antdpro框架npm install 报错,切换tyarn安装成功。

报错日志 有时间补 当前版本 解决办法 进入工作目录 安装官方推荐的tyarn工具&#xff1a;npm install yarn tyarn -g 进行依赖安装&#xff1a;tyarn 启动项目 &#xff1a;tyarn start 注意&#xff1a; 技术迭代较快&#xff0c;建议查询官网后实践&#xff0c;以上作为…