鹏哥C语言复习——结构体

目录

结构体声明:

结构体内存存储相关介绍:

结构体的初始化与使用:

结构体的初始化:

结构体的使用:

结构体对齐:

结构体对齐原则解释:

结构体对齐存在的原因:

#pragma pack( ):

offsetof讲解:

位段:

什么是位段?

位段的注意事项:

位段跨平台问题:

位段的实际运用:

枚举:

枚举的优点:

联合(共用体)

联合体判断大小端存储:


结构是一些值的集合,这些值成为成员变量。结构的每个成员可以是不同类型的变量。

结构体声明:

假设要用结构体描述一个人

struct Peo //Peo为结构体名字,是由自己来取的
{char name[20]; //名字char tele[12]; //电话号码char sex[5]; //性别int high; //身高
};

如名字、电话号码等就称之为结构的成员变量。并且结构的成员可以是标量、数组、指针,甚至是其他结构体。

当我们需要创建四个人,p1、p2、p3和p4,那我们应该如何创建呢?

struct Peo 
{char name[20]; char tele[12];char sex[5]; int high; 
}p1,p2;int main()
{……;struct Peo p3,p4;return 0;
}

上述代码中跟在结构类型创建后的2个变量,是作为全局变量而存在;在mian( )函数中创建的则是作为局部临时变量而存在。

结构体内存存储相关介绍:

结构类型创建后并不会占用内存空间,它只是作为一种类型而存在;而像上述代码中的p1、p2等变量会在内存中开辟一个空间进行存放,是作为一种变量存在

类比记忆:

结构类型是房屋建造的图纸,而结构体变量是实实在在存在的房子

结构体的初始化与使用:

 笔者在此先创建了以下两个结构类型,第二个结构类型存在了结构体嵌套的现象

struct Peo 
{char name[20]; char tele[12];char sex[5]; int high; 
}struct St
(struct Peo p;int num;float f;
};

结构体的初始化:

int main()
{……;struct Peo p1 = {"zhangsan", "15596668862", "男", 181};struct St p2 = { {"lisi", "15596668888", "女", 168},100,3.14f};return 0;
}

结构体的使用:

如果需要使用上述代码中 zhangsan 这个名字,则需要 p1.name;如果需要使用 lisi这个名字,则需要 p2.p.name 。类似于数组下标,就是对结构体元素的指向。

而对于结构体的使用,共有两种方式来表示,其一为上述的 结构体变量.成员变量、结构体指针->成员变量。

函数传参时,参数是需要压栈的

如果传参一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降;同时,因为传参以后,函数需要创建形式参数,所以在传参以后,如果结构体过大,实参字节大小过大,形参内存占用也同样会比较多,这也就致使了内存空间的浪费。

而传参时如果传的是结构体指针,它是指向一个结构体整体的指针,只有4或8字节(不同环境下编译运行的区别),相较于结构体变量,大大节省了空间。

综上所述,结构体传参的时候,要传结构体的地址。

而我们在声明结构体的时候,可以不完全的声明。

比如:

struct
{int a;char b;float c;
}x;struct
{int a;char b;float c;
}a[20],*p;

上面的两个结构在声明的时候省略掉了结构体标签,而对于这种匿名结构体类型,会有以下两种问题:

1.结构体类型声明完以后,结构体变量只能在一处创建,即在结构体类型声明后创建

2.对于 p = &x ,编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的

结构体对齐:

而在结构体的使用中,我们需要掌握结构体的对齐规则:

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

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

        对齐数 = 编译器默认的一个对齐数与该成员大小的较小值

  •  例如Visual studio中默认的值为8

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

4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处

5.结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

6.数组可以看成某些个单个变量的集合,拆分开来,不难发现数组的对齐数就是它的类型对齐数

7.假设偏移量为0的地址处为0x0012ff40,那么偏移量为1的地址处为0x0012ff41

结构体对齐原则解释:

struct S2
{char c1;int i;char c2;
}s2;
//于vscode2022中进行的

如上述代码,c1作为字符型变量,占用1字节,vscode的默认对齐数为8,1字节小于8字节,因此对齐数为1;同理 i 应该对齐数为4,c2对齐数为1

                                                           69a8aa0c5fef48deabe028fba52ffcde.jpeg

                                 :蓝色地址存放的是c2,绿色地址存放的是i

在结构体存放时,除第一个成员变量以外,其余的成员变量要对齐到自身对齐数的整数倍处;例如,i的对齐数为4,那么就需要存放在4、8、12、4n(n >= 4, n eq?%5Cin Z) 位置处,两变量之间余留的直接舍去

而首成员变量存放的偏移为0的位置处可以看成是任何数的0倍

同时,该结构体的最大对齐数为4,即i的对齐数,所以偏移量8可以作为结构体的整体大小,那么该结构大小为9字节

struct S3
{char c1;struct S2 s2  //其中的最大对齐数为4char c2;
};

对于上述代码,笔者在刚刚已经介绍过嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处;因此就是从偏移量为4位置处开始存放,存放到偏移量12处,再存放一个c2,整体大小为4的倍数,结果为17字节

结构体对齐存在的原因:

1.平台原因(移植原因):

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

2.性能原因:

        数据结构(尤其是栈)应该尽可能地在自然边界上对齐。

        原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

总而言之,

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

因此我们在设计结构体的时候,如果要既做到对齐,又要节省空间,就应该

让占用空间小的成员尽量集中在一起

#pragma pack( ):

作用是调整默认对齐数,例如#pragama pack(1)就是在说默认对齐数为1

offsetof讲解:

offsetof是用来看某结构体成员变量的存放偏移量的

例:

还是以上述代码为例,offsetof(struct s2, c2)结果为8,因其偏移量为8;offsetof(struct s2, i)结果为4,因其偏移量为4

位段:

什么是位段?

bc4c6ec77ded43a59088c8111deac81c.jpg

 上述代码当中,整型变量应该为4字节,即32比特;而_a: 2就是将a这个变量变成2比特

有时候,我们不需要某个变量占用很大空间(例如某个变量只会是0或1),但是直接开辟必定会占用32比特,这种情况下我们就需要使用位段

而上述代码中是以整型大小为标准来开辟空间的 因此先开辟了32比特,然后存放了 2+5+10=17 以后,再存放30超过了32比特;因此又开辟了32比特的内存空间,存放30比特的数据

030a6224cb824a6ab9a23fd8706c4589.jpg

 在上述代码中,先是开辟了8比特,存放7比特内容;然后又开辟了8比特,存放5比特内容;最后开辟了8比特,存放4比特内容;因此总共开辟了3字节

029b0eaaa3774f34b7adc092064a75d5.jpg

 而对究竟体现出来是几,编译器是根据16进制来判断的,并且以2进制存入数据,每个数据占多少比特已经规定好了,计算时将四个比特位划分成一份来计算;如上图,编译器是从左往右来计算,从右往左来存放;先是a为10,二进制为1010,有3个比特位来存放,因此存入内存以后为010;b为12,存入1100,剩余1比特舍弃;c为3,存入011,剩下空间的直接舍弃;d为4,与上同理

计算时,以四个比特位为一份,先是第一个字节,结果是 2^2 + 2^1 = 6,2^1 = 2;以此类推,结果为62 03 04

位段的注意事项:

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

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

3.在使用位段时,最好只创建一种变量的位段

4.位段所改变的大小必须 ≤ 其类型本身,例如整型数据最多可以设置其为32比特

5.位段涉及很多不确定因素,位段是不垮平台的,注意需要移植的程序应该避免使用位段

位段跨平台问题:

1.int位段被当成有符号数还是无符号数是不确定的

2.位段中的最大位数目不确定(整型数据在16位机器中最大为16比特;而在32位机器中为32比特)

3.位段中的成员在内存中从左往右分配还是从右往左分配尚未定义

4.当一个结构包含两个位段,第二个位段成员较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的还是继续利用,这是不确定的

位段的实际运用:

例如网络传输,一个数据从我这传到别人电脑里是需要通过复杂的网络传输的,如下图所示

575b6691aae24c059d944ef05874acfe.jpg

 在这一过程中,所需要的比特大小各不相同;因此需要位段来控制大小,加快传输速度

枚举:

ecaff3118ce5417798a6592bd4589aaa.jpg

 形如上述代码即为枚举,笔者在此枚举了星期,分别是星期一至星期日

1f7a512287264ade99dd1d370ea8c5ed.jpg

 enum Day 作为自定义类型存在,因此可以看成一种数据类型,创建变量时也是

数据类型 变量名称 = 某成员变量

如上代码,周一到周三打印出来分别为 0、1、2;这表明了枚举中的成员是从0开始往后延续;若我们想让星期一打印出来是1,我们可以进行如下操作

9e720e575c934135af771d122497674e.jpg

 这时,打印结果如下

2ebde9763e8841fb8784197fb5f81653.jpg

枚举的优点:

1.增加代码的可读性和可维护性

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

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

4.便于调试

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

#define M 100

int i = M

如上代码,机器在编译时会变成100,M消失了;然而通过枚举的方法,定义的成员变量本身不会消失

联合(共用体):

联合是一种特殊的自定义类型

这种类型定义的变量也包含一系列的成员,而这些成员共用内存中的同一块空间(因此也叫共用体)

4212e720c9e64c82a883f5d13050e178.jpg

 如上述代码,如果我们要打印两个变量的地址以及整个联合体的大小

3a50cdc696724bd5b7b9ebf7d963dab5.jpg

 最后的结果为

1dd6dbb01ee44dfda60ee8772d2a8c1d.jpg

 原因解释:

bf6f0104f047460983f0b449e016632d.jpg

在上述代码中,先是存放了整型变量a,共4字节;然后存放了字符型变量c,1字节,和已经开辟好的4字节共用同一个空间;因此上述代码中a、c的地址为同一个,同时联合体大小为4

注:联合的成员是共用同一块空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力储存最大的那个成员)

联合体判断大小端存储:

在讲解大小端以前,我们先要了解好大小端的概念

假设存在一个整型变量a,且 a = 1,那么他存放在内存空间中即为0x 00 00 00 01

如果是

01 00 00 00 //由低位到高位

即为小端

如果是

00 00 00 01 //由低位到高位

即为大端

可以通过上述代码来判断计算机存储是大端还是小端

check_sys( )函数共有以下两种实现方式:

1.强制转换方法

int a = 1;

return *(char*)&a;        //先将a的地址取出,即01 00 00 00 或者 00 00 00 01

                                    //然后判断最开始的两位,所以应该是1字节的元素,即char型,因此

                                    //将a变量强制转换成char*,现在是 01 或 00,那么返回该地址数据

                                    //结果为 0 或 1

2.联合体方法

union Un

{

        char c;                //先是 01 00 00 00,然后取出 01

        int i;

}u;

        u.i = 1;

        return u.c;

}   

注:联合体也需要满足结构体对齐原则

例如

union Un
{char arr[6];int i;
};

 在开辟了6字节空间以后,需要进行对齐,因此总共开辟了9字节

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

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

相关文章

第十四届蓝桥杯省赛大学C组(C/C++)三国游戏

原题链接:三国游戏 小蓝正在玩一款游戏。 游戏中魏蜀吴三个国家各自拥有一定数量的士兵 X,Y,Z(一开始可以认为都为 0)。 游戏有 n 个可能会发生的事件,每个事件之间相互独立且最多只会发生一次,当第 i 个事件发生时…

Web API(二)之事件监听类型处理程序

Web API(二)之事件监听&类型&处理程序 事件事件监听事件类型事件处理程序 事件类型鼠标事件键盘事件焦点事件文本框输入事件 事件对象环境对象回调函数 学会通过为DOM注册事件来实现可交互的网页特效。 能够判断函数运行的环境并确字 this 所指代…

人工智能研究生前置知识—Anaconda与python工作环境

人工智能研究生前置知识—Anaconda与python工作环境 python环境管理 python工作环境的管理是需要满足的基本条件,指的是不同的python版本之间的切换。或者说是允许安装不同版本的python 解决:conda是一个跨平台的包管理工具,其环境管理功能允…

Docker容器与虚拟化技术:OpenEuler 部署 ES 与 Kibana

目录 一、实验 1.环境 2.OpenEuler 部署 ES (EalasticSearch) 3.OpenEuler 部署 Kibana 4.部署 Elasticvue插件 5.使用cpolar内网穿透 6.使用Elasticvue 一、实验 1.环境 (1)主机 表1 主机 系统架构版本IP备注LinuxopenEuler22.03 LTS SP2 1…

RabbitMQ3.13.x之九_Docker中安装RabbitMQ

RabbitMQ3.13.x之_Docker中安装RabbitMQ 文章目录 RabbitMQ3.13.x之_Docker中安装RabbitMQ1. 官网2. 安装1 .拉取镜像2. 运行容器 3. 访问 1. 官网 rabbitmq - Official Image | Docker Hub 2. 安装 1 .拉取镜像 docker pull rabbitmq:3.13.0-management2. 运行容器 # lates…

就业班 第二阶段 2401--4.1 day10 shell之“三剑客”+Expect

十一、shell 编程-grep egrep 支持正则表达式的拓展元字符 (或grep -E) #egrep [0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3} file1.txt [rootnewrain ~]# num11 1、运用正则,判断需要[[ ]] [rootnewrain ~]# [[ $num1 ~ ^[0-9]$ ]] &a…

Java类和对象之访问限制

学习-Java类和对象之访问限制 100 任务要求参考答案评论28 任务描述相关知识 defaultpublicprivateprotected编程要求测试说明 任务描述 本关任务:实现图书类,该类包含了图书的基本属性和信息。 相关知识 为了完成本关任务,你需要掌握&…

C++ 一种简单的软件验证码 程序授权使用 收费付费使用 无需注册 用机器码得到一个加密值 再对比加密值是否一致 只需加密

简单软件授权方案 1、获取机器码,发给软件开发者 2、开发者用机器码加密得到一个密文 发给使用者 3、使用者 用这个密文 与本地计算密文比较密文是否一致,一致就把密文写入到注册表,下次登录从注册表读密文对比。 (最重要的是密…

设计模式总结-适配器模式

适配器模式 模式动机模式定义模式结构适配器模式实例与解析实例一:仿生机器人实例二:加密适配器 总结 模式动机 在软件开发中采用类似于电源适配器的设计和编码技巧被称为适配器模式。 通常情况下,客户端可以通过目标类的接口访问它所提供的…

ollama 本地轻松安装及使用docker web

这里视频其实还是蛮多的,环境是win10 Linux当然更好,奈何win10还有很多其他的AIGC的环境 所以还是索性在一个环境上比较好 1 下载 Download Ollama on macOS 修改成windows 有windows 版本所以直接下载 下载完毕后直接cmd 输入相应的下载模型 这里…

Qt实现Kermit协议(二)

3 实现 3.1 Kermit 该模块是Kermit协议实现类。 3.1.1 Kermit定义 /*|<------Included in CHECK----->|| |----------------------------- - --------| MARK | LEN | SEQ | TYPE | DATA | CHECK |<terminator>----------------------------- - …

一套C#自主版权+应用案例的手麻系统源码

手术麻醉信息管理系统源码&#xff0c;自主版权应用案例的手麻系统源码 手术麻醉信息管理系统包含了患者从预约申请手术到术前、术中、术后的流程控制。手术麻醉信息管理系统主要是由监护设备数据采集子系统和麻醉临床系统两个子部分组成。包括从手术申请到手术分配&#xff0c…

如何做好 Code Review?【下】

本文来自极狐GitLab 资源中心。原文链接&#xff1a;https://resources.gitlab.cn/articles/614fb704-882f-4601-927f-00a8b1dca2ed。 关联阅读 如何做好 Code Review&#xff1f;【上】如何做好 Code Review&#xff1f;【中】 GitLab 是一个全球知名的一体化 DevOps 平台&a…

设计模式:观察者模式示例

让我们通过一个天气监测应用的例子来展示观察者模式。在这个应用中&#xff0c;WeatherStation 作为可观察的主题&#xff0c;它会跟踪天气数据的变化。Display 作为观察者&#xff0c;当天气数据更新时会显示最新的信息。 示例代码&#xff1a; import java.util.ArrayList;…

【御控物联】JavaScript JSON结构转换(19):数组To对象——规则属性重组

文章目录 一、JSON结构转换是什么&#xff1f;二、术语解释三、案例之《JSON数组 To JSON对象》四、代码实现五、在线转换工具六、技术资料 一、JSON结构转换是什么&#xff1f; JSON结构转换指的是将一个JSON对象或JSON数组按照一定规则进行重组、筛选、映射或转换&#xff0…

太阳能发电如何进行储存?常见的储存方法有3种!

在屋顶、院落或是其他太阳可以照射的地方&#xff0c;安装光伏板&#xff0c;即可实现太阳能发电&#xff0c;也称为光伏发电。这种发电方式是一种干净的可再生的新能源&#xff0c;越来越受到人们的青睐。太阳能发电后&#xff0c;如何将电能储存起来以备随时使用&#xff1f;…

纯小白蓝桥杯备赛笔记--DAY9(动态规划)

文章目录 一、动态规划基础&#xff08;1&#xff09;线性DP简介步骤例题数字三角形--1536破损的楼梯-3367安全序列-3423 &#xff08;2&#xff09;二维DP简介例题摆花--389选数异或--3711数字三角形--505 &#xff08;3&#xff09;最长公共子序列LCSLCS算法模型最长公共子序…

C语言函数实现冒泡排序

前言 今天我们来看看怎么使用函数的方式实现冒泡排序吧&#xff0c;我们以一个数组为例arr[] {9,8,7,6,5,4,3,2,1,0},我们将这个数组通过冒泡排序的方式让他变为升序吧。 代码实现 #include<stdio.h> void bubble_sort(int arr[], int sz) {int i 0;for (i 0;i < s…

js计算器实现

文章目录 1. 演示效果2. 分析思路3. 代码实现 1. 演示效果 2. 分析思路 给每个按钮添加点击事件&#xff0c;使用eval()进行计算。 3. 代码实现 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name&q…

安全防御产品—锐安盾重磅上线,助力更安全、更流畅的业务体验

在互联网时代&#xff0c;互联网技术蓬勃发展&#xff0c;然而&#xff0c;随之而来的网络安全问题也备受关注。诸如DDoS攻击、CC攻击、常见Web攻击等攻击手段突如其来&#xff0c;导致企业业务中断&#xff0c;严重影响企业业务正常运行。对此&#xff0c;锐成云重磅推出安全防…