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

mybatis-plus 动态表名简易使用

场景&#xff1a;由于有些表是分表的&#xff0c;需要给表名添加后缀才能正确地访问表&#xff0c;如sys_user_2024_01 代码 依赖版本 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><ve…

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…

qt——窗口置灰不可操作

在Qt中实现一个窗口&#xff08;或窗口中的特定部分&#xff09;置灰并不可操作&#xff0c;通常涉及到两个概念&#xff1a;禁用窗口的交互功能以及视觉上的置灰效果。下面我会介绍如何使用Qt实现这两个功能。 1. 禁用窗口的交互功能 如果你希望整个窗口都不可交互&#xff0c…

《深入浅出.NET框架设计与实现》笔记6.4——ASP.NET Core应用程序多种运行模式之四——服务承载

ASP.NET Core应用程序可以在多种运行模式下运行&#xff0c;包括自宿主&#xff08;Self-Hosting&#xff09;、IIS服务承载、桌面应用程序、服务承载。 因此选择和时的模式很重要。 服务承载 在服务承载模式下&#xff0c;ASP.NET Core应用程序将注册为Windows服务&#xf…

idea的macOS Apple Silicon (dmg)版本和macOS (dmg)版本有什么区别

“macOS Apple Silicon (dmg)” 版本则是专门为使用 Apple Silicon 芯片的 Mac 设备而设计的版本。 区别通常在于目标硬件平台和优化程度&#xff1a; 目标硬件平台&#xff1a;macOS Apple Silicon 版本是专门为基于 Apple Silicon 芯片的 Mac 设备&#xff08;例如 M1、M1 P…

再谈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…

vue3 ts table合计样式更改

table合计代码 <el-tableshow-summary:summary-method"summary":cell-style"cellStyle"></el-table>// 引入 &#xff0c;因为返回不能是VNode&#xff0c;所以需要引入h函数 import {h} from "vue";// 方法 &#xff08;计算和官…

让你的电脑准时“打个盹”: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;用于构建和训练深…

用Python自动获取PDF图纸的图纸大小,并依此分类整理

在建筑行业&#xff0c;设计师用CAD设计完建筑图纸后&#xff0c;常常需要上传到市、省二级图审平台上&#xff0c;故需要将每张图纸转成PDF文档&#xff0c;并盖电子章&#xff08;出图章、建造师章和结构章&#xff09;&#xff0c;然后上传图审系统。如果设计师在CAD转PDF时…

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

内网穿透!如何配置frp新版0.56.0的配置文件.toml:提供web、samba、ssh远程连接示例

最新版本的frp在配置上与之前有很大不同&#xff0c;需要使用.toml文件进行配置。其中主要问题出现在toml文件内部。因此&#xff0c;本文将专门讨论这个问题。 一、服务端配置 下载并解压&#xff08;以debian系统为例&#xff09;&#xff1a; sudo apt update sudo apt i…