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

news2025/1/19 14:16:21

结构体的概念

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

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.coloradmin.cn/o/1622455.html

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

相关文章

kubernetes中Pod调度-Taints污点和污点容忍

一、污点的概念 所谓的污点&#xff0c;是给k8s集群中的节点设置的&#xff0c;通过设置污点&#xff0c;来规划资源创建是所在的节点 污点的类型 解释说明PreferNoshedule 节点设置这个污点类型后&#xff1b; 表示&#xff0c;该节点接收调度&#xff0c;但是会降低调度的概…

美易官方:AI热潮“熄火”了?Meta Q1财报较差

近期&#xff0c;随着Meta&#xff08;前Facebook&#xff09;发布了其2023年第一季度的财报&#xff0c;一场科技股的震荡在美股市场上演。曾经风光无限的AI热潮似乎出现了“熄火”的迹象&#xff0c;引发了市场的广泛关注和讨论。 Cresset Wealth Advisors首席投资官Jack Abl…

Maven 安装及配置教程(Windows)【安装】

文章目录 一、 下载1. 官网下载2. 其它渠道 二、 安装三、 配置四、 验证五、 本地仓储配置六、 配置镜像七、 配置JDK八、完整配置九、常用命令十、IDEA 中配置 Maven1. 配置当前项目2. 配置新建 / 新打开 项目 软件 / 环境安装及配置目录 一、 下载 1. 官网下载 安装地址&a…

rocky9 yum安装 Nginx

前置条件&#xff1a; 把yum包更新到最新 [rootlocalhost ~]# yum update 查看系统中是否已安装 nginx 服务 rpm -qa|grep nginx 如果有安装nginx,则需要先卸载之前安装的nginx&#xff1a; yum -y remove nginx 然后再查看nginx是否都卸载完成,如果还有没卸载完成的&am…

B站自研数字版权管理方案完成Cartesian的Farncombe Security™安全审计

2024年4月&#xff0c;bilibili自研的软件级数字版权管理方案BiliDRM顺利完成了知名第三方内容安全审计专家Cartesian的审查。 Cartesian 的 Farncombe Security™ Audit是一种独立的、行业认可的审计方法&#xff0c;用于审计内容保护解决方案&#xff08;CAS或DRM&#xff0…

【人工智能书籍】深度学习(花书)[书籍PDF分享]

哈喽啊大家&#xff0c;今天又来给大家推荐一本深度学习方面的书籍<花书 深度学习>。 本书由全球知名的三位专家Ian Goodfellow、Yoshua Bengio 和Aaron Courville撰写&#xff0c;是深度学习领域奠基性的经典教材。 全书的内容包括3个部分&#xff1a; 第1部分介绍基本…

VS调试、debug和release、栈区底层简单介绍、const 修饰指针变量介绍

文章目录 前言一、调试二、debug和release三、调试需要多用&#xff0c;多熟悉四、栈区底层简单介绍五、优秀的代码&#xff1a;常见的coding技巧: 六、const 修饰指针变量1. const 出现在 * 左边2. const 出现在 * 右边 七、strcpy函数的仿写1.版本12. 版本23. 版本34. 版本4 …

专业护眼灯什么牌子好?2024年专业护眼灯品牌排行分享

专业护眼灯什么牌子好&#xff1f;各位家长可能已经注意到一个令人关切的现象&#xff1a;戴眼镜的孩子人数在不断上升&#xff0c;许多孩子正在接受眼部治疗。眼睛健康的问题变得越来越普遍&#xff0c;这无疑令人担忧。在当今数字化时代&#xff0c;孩子们每日需长时间阅读和…

ssm框架的网上招聘系统的设计与实现,ssm框架,java编程,mysql数据库 myeclipse开发平台

网上招聘是一个功能很复杂的系统&#xff0c;各个部门之间要有一定的协调能力。 要建立一个高效的管理系统的关键问题就是系统内部的各个模块的相互作用&#xff0c;简单的编写一个网站 只用html &#xff0c;css &#xff0c;javascript &#xff0c;xml &#xff0c;xsl技术…

Linux shell编程学习笔记47:lsof命令

0 前言 今天国产电脑提示磁盘空间已耗尽&#xff0c;使用用df命令检查文件系统情况&#xff0c;发现/dev/sda2已使用100%。 Linux shell编程学习笔记39&#xff1a;df命令https://blog.csdn.net/Purpleendurer/article/details/135577571于是开始清理磁盘空间。 第一步是查看…

ocr文字识别软件是干什么的?

OCR&#xff08;Optical Character Recognition&#xff0c;光学字符识别&#xff09;文字识别软件是一种能够将图像或者扫描的文档中的文字内容转换为可编辑的文本格式的软件。它的主要功能包括&#xff1a; 1. **文字提取&#xff1a;**识别图像中的文字并提取出来&#xff0…

freecad的试用

最近折腾了一台3D打印机&#xff0c;想打印些齿轮啥的做减速箱 然后机械设计软件SolidWorks好像下载很复杂&#xff0c;还得破解。 这里找到一个网站还可以&#xff0c;推荐下 https://321.pw/ 然后我发现solidworks好像太专业了&#xff0c;及时安装了也大材小用。 后来发…

日志集中审计系列(5)--- LogAuditor接收USG设备日志

日志集中审计系列(5)--- LogAuditor接收USG设备日志 前言拓扑图设备选型组网需求配置思路操作步骤结果验证前言 近期有读者留言:“因华为数通模拟器仅能支持USG6000V的防火墙,无法支持别的安全产品,导致很多网络安全的方案和产品功能无法模拟练习,是否有真机操作的实验或…

剑指 Offer 03.:数组中重复的数字

剑指 Offer 03. 数组中重复的数字 找出数组中重复的数字。 在一个长度为 n 的数组 nums 里的所有数字都在 0&#xff5e;n-1 的范围内。数组中某些数字是重复的&#xff0c;但不知道有几个数字重复了&#xff0c;也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。…

高价硕士时代

今天翻到一篇关注读研的文章&#xff0c;里面列举了三个不同经历的研究生的故事&#xff0c;我感觉颜雨宁的经历更有代表性&#xff0c;于是将整理的笔记分享大家。 先来看看学硕和专硕的区别。 颜雨宁考的是专硕&#xff0c;她清楚地意识到这条路不会是平坦的。复旦大学的门槛…

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

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

【超简单实用】Zotero 7 内置pdf背景颜色更改插推荐以及安装

Zotero beta7 pdf 内置颜色更换 zetore 6 很多成熟的插件在 zetore 7都不能用了。版本回退看起来内置文章的注释会被消除&#xff0c;所以又不想退回去。前几个月在找beta 7 的pdf 护眼色的插件一直没有&#xff0c;今天终于发现了&#xff01;&#xff01;&#xff01;&#…

5分钟——快速搭建后端springboot项目

5分钟——快速搭建后端springboot项目 1. idea新建工程2. 构建pom.xml文件3. 构建application.yml配置文件4. 构建springboot启动类5. 补充增删改查代码6. 运行代码7. 下一章 1. idea新建工程 点击右上角新建一个代码工程 别的地方不太一样也不用太担心&#xff0c;先创建一个…

如何用微信发布考试成绩(如月考、期中、期末等)

自教育部《未成年人学校保护规定》颁布后,教育部明确表示:学校不得公开学生的考试成绩、排名等信息!同时学校应采取措施,便利家长知道学生的成绩等学业信息,对于教师来说,如何用微信发布考试成绩(如:月考、期中、期末等)就成了一道难题... 公开吧,会伤害到学生自尊心,甚至被投诉…

IDEA本地将镜像推送到coding制品仓库

创建制品仓库 假设仓库名称为docker 在IDEA 添加Docker 注册表 IDEA必须先安装docker插件 地址 用户名和密码就是coding的登录名和密码服务器 最好本地安装docker桌面版&#xff0c;更容易操作 测试连接成功 推送镜像到coding的docker制品仓库 选中某个镜像 鼠标右键 注册表…