C语言自定义类型【结构体】

结构体的概念

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

1.结构体的声明

1.1普通声明

我们假设要创建一本书的类型,那我们需要书名,作者,价格,书的ID
代码如下:

struct Book
{char BName[20];//书名char Author[20];//作者float Price;//价格char BId;//书的ID
}Book;//分号前的名字可以省略,但分号不能省略

1.2结构体的初始化

struct Book
{char BName[20];char Author[20];float Price;char BId;
};//结构体的初始化方式
int main()
{struct Book b1 = { "C语言程序设计" , "张三", 29.9,"B100001" };//按照结构体的内部顺序初始化struct Book b2 = { .Price = 59.9, .BId = "B100002", .Author = "李四" ,.BName = "C语言进阶" };//		也可以乱序来初始化,但格式为 成员变量.初始化值
}

1.3结构体的特殊声明

在声明结构体的时候,可以不完全声明
例如:

//匿名结构体类型基本上只能使用一次
struct 
{char c;int i;float f;double d;
}s = {'x',100,3.1f,3.14};
int main()
{struct s;//error(这是错误的)//需要将上面代码删除或屏蔽printf("%c %d %f %lf", s.c, s.i, s.f, s.d);
}

那我们如果想让他能够重复使用该怎么办呢?
我们可以用 typedef 对匿名结构体进行重命名

typedef struct
{char c;int i;float f;double d;
}s;

但没有意义,我匿名了又给他取个名字,这就是饶了一圈又回到了普通声明了
这就有点多此一举了,还不如直接用普通声明呢。

1.4结构体的自引用

结构体内部包含一个自己类型的成员可以吗?
例如:定义一个链表的节点

#define NODEDATA int//给int起一个别名
typedef struct Node
{NODEDATA data;struct Node next;
}Node;

这个正确吗?
其实是不正确的,
仔细看就能发现⼀个结构体中再包含⼀个同类型的结构体变量
这样结构体变量的大小就会无穷的⼤
正确的自引用方式:

#define NODEDATA int
typedef struct Node
{NODEDATA data;struct Node* next;
}Node;

在结构体自引用使用的过程中,夹杂了typedef对匿名结构体类型重命名,也容易引出问题,看看下面的代码,看他是否可行:

#define NODEDATA int
typedef struct
{NODEDATA data;Node* next;
}Node;

这样可以吗
答案肯定是不行的
因为Node是typedef对这个匿名结构体进行重命名而产生的
但是在匿名结构体内部提前使用Node类型来创建成员变量是不行的
解决方式:定义结构体的时候不使用匿名结构体

#define NODEDATA int
typedef struct Node
{NODEDATA data;struct Node* next;
}Node;

2.结构体的内存对齐

先看代码:

struct S1
{char c1;int i;char c2;
};
int main()
{struct S1 s1 = { 0 };printf("S1大小为:%d\n", sizeof(s1));return 0;
}

看看这段代码中s1的大小为多少?
答案是6吗(c1 占一个字节,i 占四个字节, c2 占一个字节)
其实是12
在这里插入图片描述

那为什么是12呢,我们就需要知道结构体内存对齐的概念了 (这也是一个热门的考点)

2.1内存对齐的规则

1.结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处
2.其他成员变量要对齐某个数字(对齐数)的整数倍的地址处
对齐数 = = 编译器默认的一个对齐数 与 该成员变量的大小 进行比较得出的较小值
VS中的默认对齐数是8
Linux中gcc编译器是没有默认对齐数的,对齐数就是成员本身的大小
3.结构体的总大小为成员变量中对齐数最大的整数倍
4.如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。

那我们来看看为什么上面的代码结果会是12吧

在这里插入图片描述
在这里插入图片描述

2.2为什么要有内存对齐

大部分参考资料是这样说的

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

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

2.性能原因

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

假设一个处理器总是从内存中取8个字节,如果我们能保证所有的double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作来读/写值了。否者,我们可能需要进行两次内存访问才能拿到一个完整的double类型的数据,因为对象可能被放在两个8字节的内存中。

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

那我们在设计结构体的时候,然后满足对齐,又节省空间呢?
解决方法:将小的类型尽量聚集在一起

struct S1
{char c1;int i;char c2;
};struct S2
{char c1;char c2;int i;
};int main()
{struct S1 s1 = { 0 };struct S2 s2 = { 0 };printf("S1大小为:%d\n", sizeof(s1));printf("S2大小为:%d\n", sizeof(s2));return 0;
}

在这里插入图片描述

2.3修改默认对齐数

使用#pragma这个预处理指令,可以修改编译器的默认对齐数

#pragma pack(1)//将默认对齐数改为1
struct S1
{char c1;int i;char c2;
};
int main()
{printf("S1大小为%d\n", sizeof(struct S1));return 0;
}

在这里插入图片描述

#pragma pack()//不输入就改回原本的默认对齐数
struct S1
{char c1;int i;char c2;
};int main()
{printf("S1大小为%d\n", sizeof(struct S1));return 0;
}

在这里插入图片描述
当结构体在对齐方式不合适的时候,我们就可以自己修改默认对齐数

3.结构体传参

先看代码:

struct S1
{int data[1000];//4000个字节的大小char c1;//1
};struct S1 s1 = { {1,2,3,4,5,6,7,8,9,10},'A'};void print1(struct S1 s1)
//这里的参数其实是s1的临时拷贝,会复制一个和s1大小相同的空间(4004个字节)
{for (int i = 0; i < 10; i++){printf("%d ", s1.data[i]);}printf(" %c", s1.c1);
}void print2(struct S1* s)
//这里的参数是接收s1地址的指针变量,指针变量的大小就8/4个字节
{for (int i = 0; i < 10; i++){printf("%d ", s->data[i]);}printf(" %c", s->c1);
}
int main()
{print1(s1);//传结构体printf("\n");print2(&s1);//传地址
}

代码中的print1和print2函数哪个好?
答案是print2函数

原因

1.在函数传参的时候,参数是需要压栈的,会有时间和空间的开销
2.如果在传递一个结构体对象的时候,结构体过大,参数压栈的系统开销就比较大,会导致性能的下降

结论:结构体传参的时候,最好传结构体的地址

4.结构体实现位段

4.1什么是位段

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

1.位段的成员必须是int、unsigned int或signed int,但在C99标准中位段成员的类型也可以是其他类型。
2.位段的成员名后面一定要跟着一个冒号(:)和一个数字,具体为–> type name:number;

//结构体的位断
struct Str
{int a : 2;int b : 1;int c : 16;int d : 16;
};

注意:位段的单位是bit位
我们来猜猜他的大小,正常来说一个int类型占4个字节的空间,但是位段后的单位都是bit位了,所以a占2个bit位,b占1个bit位,c和d都占16个bit位,一共是35个bit,按理来说6个字节就能存放了,但事实是不是这样呢?
我们来看看运行结果吧
在这里插入图片描述
为什么会是8呢?
这就需要了解位段在内存中的分配了

4.2位段的内存分配

1.位段的成员可以是int家族和char类型
2.位段的空间是根据需求,一次以4个字节(int)或1个字节(char)开辟的
3.位段涉及很多不确定因素,位段是不跨平台的,重点在可移植的程序应该避免使用位段

现在我们来看看为什么上面代码的结果是8吧
如图
在这里插入图片描述
由于剩余的空间不够存放d,VS会再开辟一个int类型大小的空间用来存放d,这样大小就来到了8个字节(2个int的大小)

代码2

下面代码也是关于位段在内存的分配我们来看看吧

struct S
{char a : 3;char b : 4;char c : 5;char d : 4;
};
int main()
{struct S s = { 0 };s.a = 10;s.b = 12;s.c = 3;s.d = 4;return 0;
}

分析如下图
在这里插入图片描述
在这里插入图片描述

4.3位段的跨平台问题

前面说到了位段是不跨平台的,为什么不跨平台呢,我们来看看原因吧

1.在不同的环境下,int位段被当成有符号还是不符号是不确定的
2.位段中最大位的数目不确定,(早期16位机器的int类型大小为2个字节,32位和64位机器上int大小为4个字节),所有如果位段为16以上,在16位机器就会出现问题

3.位段中的成员在内存中是从左向右分配还是从右向左分配的标准是未定义的(VS是从右向左)
4.当一个结构体包含两个位段,第二个位段成员比较大,第一个位段后剩余的位无法容纳第二个位段时,是舍弃剩余的位还是利用,这是未定义的(VS是舍弃)

总结:与结构相比,位段可以达到同样的效果,同时也能很好的节省空间,但是有跨平台的问题存在(当你想要多平台使用且节约时间可以使用结构,当你想要节省空间可以使用位段)
解决方式:根据不同的平台写不同的代码(这样会比较麻烦)

4.4位段使用时的注意事项

有时候位段的几个成员会共用一个字节,这样有些成员的起始位置并不是字节的起始位置,那么这些位段是没有地址的。(内存会给每个字节分配一个地址,但字节内的bit位是没有地址的)
所以不能对位段的成员使用 &操作符,这样就不能用scanf直接给位段的成员输入值,只能是先输入到一个变量里,再将变量赋值给位段的成员

struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};
int main()
{struct A sa = { 0 };scanf("%d", &sa._b);//这是错误的//正确的⽰范int b = 0;scanf("%d", &b);sa._b = b;return 0;
}

结语

最后感谢您能阅读完此片文章,如果有任何建议或纠正欢迎在评论区留言。如果您认为这篇文章对您有所收获,点一个小小的赞就是我创作的巨大动力,谢谢

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

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

相关文章

2024年04月09日 Go生态洞察:2024年上半年Go开发者调查报告洞察

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a;…

Linux——web基础实验

实验前的安装 [rootwebserver ~]# yum -y install httpd [rootwebserver ~]# systemctl enable --now httpd Created symlink /etc/systemd/system/multi-user.target.wants/httpd.service → /usr/lib/systemd/system/httpd.service. [rootwebserver ~]# echo test for apach…

性能监控数据(本地、服务器)

CPU、内存、磁盘等的监控 一、mac本地性能监控 1. top 终端&#xff1a; top load Avg: 平均负载(1分钟&#xff0c;5 分钟&#xff0c;15 分钟)值不能超过 4&#xff0c;要不然就是超负荷运行 Tasks: 进程数 %Cpu(s): idle :剩余百分比 KiB Mem: free:剩余内存&#xff0…

再谈C语言——理解指针(五)(完结篇)

数组名的理解 在上⼀个章节我们在使⽤指针访问数组的内容时&#xff0c;有这样的代码&#xff1a; int arr[10] {1,2,3,4,5,6,7,8,9,10}; int *p &arr[0]; 这⾥我们使⽤ &arr[0] 的⽅式拿到了数组第⼀个元素的地址&#xff0c;但是其实数组名本来就是地址&#xf…

视频剪辑新高度:轻松为视频添加字幕,提升内容质量与传播力

视频已经成为最直观、最动人的信息传播方式。但是&#xff0c;仅仅依靠画面和声音&#xff0c;往往难以完全传达视频的核心内容或情感。这时&#xff0c;字幕的加入就显得尤为重要。它们不仅能够增强观众的观看体验&#xff0c;还能为视频增添独特的文字魅力。 首先&#xff0…

让你的电脑准时“打个盹”:Win10定时休眠

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 一、Windows 10任务计划程序 在快节奏的工作生活中&#xff0c;常常需要让电脑在特定时间执行某些任务&#xff0c;而Windows 10的任务计划程序就是为此而生的神器。它不仅可以自动更新系统、备份文件&#xff0c;甚…

九泰智库 | 医械周刊- Vol.23

⚖️ 法规动态 新疆药品和医疗器械产品注册费收费标准大幅降低平均降幅95% 近日&#xff0c;新疆自治区发展和改革委会同自治区财政厅印发《关于调整药品医疗器械产品注册费收费标准的通知》&#xff0c;明确自2024年4月29日起&#xff0c;取消药品补充申请注册费&#xff0c;药…

打造人脸磨皮算法新标杆,满足企业多元化需求

高清视频和图片已成为企业展示形象、传递信息的重要载体&#xff0c;拍摄过程中难以避免的皮肤瑕疵和纹理不均等问题&#xff0c;常常让精美的画面失色。美摄科技凭借其领先的人脸磨皮算法解决方案&#xff0c;为企业提供了高效、精细的图像处理服务&#xff0c;让每一帧画面都…

Git ignore、exclude for TortoiseGit 小结

1.Ignore Type&#xff1a;忽略类型&#xff0c;也即忽略规则&#xff0c;如何去忽略文件? 1.1.Ignore item(s) only in containing folder(s)&#xff1a;仅忽略在包含在文件夹中项目。 仅忽略该文件夹下选定的patterns。the patterns其实就是文件类型&#xff0c;比如.txt后…

python安装pytorch@FreeBSD

先上结论&#xff0c;最后在conda下安装成功了&#xff01; PyTorch是一个开源的人工智能深度学习框架&#xff0c;由Facebook人工智能研究院&#xff08;FAIR&#xff09;基于Torch库开发并维护。PyTorch提供了一个高效、灵活且易于使用的工具集&#xff0c;用于构建和训练深…

SpringMVC基础篇(一)

文章目录 1.基本介绍1.特点2.SpringMVC跟SpringBoot的关系 2.快速入门1.需求分析2.图解3.环境搭建1.创建普通java工程2.添加web框架支持3.配置lib文件夹1.导入jar包2.Add as Library3.以后自动添加 4.配置tomcat1.配置上下文路径2.配置热加载 5.src下创建Spring配置文件applica…

找不到openjdk-1.8-tools和openjdk-1.8-jconsole

每次打包都报找不到openjdk-1.8-tools和openjdk-1.8-jconsole&#xff0c;但是在项目中并没有用到。 这个是在maven仓库下的druid下的pom文件中。根本没有用到&#xff0c;于是把这两行注释调。解决 .m2\repository\com\alibaba\druid\1.2.6\druid-1.2.6.pom

钡铼IOy系列模块在无人值守智能仓库中的成功运用,提升仓储物流效率

随着科技的不断发展&#xff0c;无人值守智能仓库正成为现代物流行业的一个重要趋势。在这个快节奏的时代&#xff0c;提升仓储物流效率是企业追求的目标之一。钡铼IOy系列模块为无人值守智能仓库的成功运作提供了关键支持。本文将探讨钡铼IOy系列模块在无人值守智能仓库中的应…

深入Linux下的GCC编译器:从入门到精通

目录标题 1、GCC编译器概述2、安装GCC3、GCC的基本使用4、高级功能4.1 多文件编译4.2 静态和动态链接4.3 什么是链接&#xff1f;4.4 静态链接优点缺点 4.5 动态链接优点缺点 4.6 实际应用4.7 编译优化 GCC&#xff08;GNU Compiler Collection&#xff09;是一款免费、开源的编…

Elasticsearch下载

1 最新版下载地址 Download Elasticsearch | Elastic https://www.elastic.co/cn/downloads/elasticsearch 2 其他版本下载地址 https://www.elastic.co/cn/downloads/past-releases#elasticsearch 7.9.2:https://artifacts.elastic.co/downloads/elasticsearch/elasticsear…

基于springboot+vue+Mysql的篮球竞赛预约平台

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

【vue功能】多张图片合并

多张图片合并成一张图片 步骤一&#xff0c;多张图片上传步骤二&#xff0c;循环获取所有绘制图片的总高度new FileReader()方法作用new Image()方法作用介绍 步骤三&#xff0c;合并多张图片canvas.toDataURL()作用-dpr作用 步骤四&#xff0c;下载图片 步骤一&#xff0c;多张…

NASA数据集——有源空腔辐射计辐照度监测仪(ACRIM)II 本地格式的 UARS 机载太阳总辐照度(TSI)2022年1月版本

Active Cavity Radiometer Irradiance Monitor (ACRIM) II Total Solar Irradiance (TSI) aboard UARS in Native format 简介 ACRIMII_TSI_UARS_NAT 数据是上层大气研究卫星&#xff08;UARS&#xff09;上的有源空腔辐射计辐照度监测仪 II&#xff08;ACRIM II&#xff09;…

云安全防御篇:如何识别并做好服务器DDoS防护?

伴随着全球互联网业务和云计算的快速发展&#xff0c;作为一种破坏力巨大的攻击方式&#xff0c;DDoS攻击正以超出服务器承受能力的流量淹没网站&#xff0c;导致服务器宕机、企业营业额下跌&#xff0c;甚至企业品牌形象受损。越是面对复杂的攻击&#xff0c;就需要性能更强的…