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.mfbz.cn/a/572319.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第十五届蓝桥杯省赛第二场C/C++B组A题【进制】题解(AC)

解题思路 按照题意进行模拟&#xff0c;计算 x x x 的 b b b 进制过程中&#xff0c;若出现余数大于 9 9 9&#xff0c;则说明 x x x 的 b b b 进制一定要用字母进行表示。 #include <iostream> #include <cstring> #include <algorithm> #include &l…

vue3推荐算法

Vue 3 推荐算法主要指的是在 Vue 3 框架中实现的或者适用于 Vue 3 的算法库或组件库。Vue 3 由于其优秀的设计和性能&#xff0c;被广泛应用于构建各种类型的应用程序&#xff0c;包括需要复杂算法支持的项目。以下是一些在 Vue 3 中可能会用到的推荐算法资源&#xff1a; Vue-…

啊? 又要洗数据啦!! 还是两个key决定一个表! 二维Map学习,基于guava的HashBasedTable

一个洗数据的需求&#xff0c;表设计的外建不能判断某一个数据源&#xff0c;还要根据tyoe来进行判断才可以。 那此时呆逼的查发能实现但不够优雅&#xff0c;于是乎想到了二维数组&#xff0c;查了下资料有相关的实现给大家分享下&#xff01;&#xff01; 背景 表设计如下&a…

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

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

libVLC 专栏介绍

本专栏主要界面libVLC的使用&#xff0c;详细介绍了相关用法&#xff0c;使用Qt作为显示界面&#xff0c;不仅可以了解Qt的使用&#xff0c;QSS的美化&#xff0c;更能够熟悉libVLC核心接口的使用&#xff0c;最后打造一款属于自己的精美播放器。 每一节都有单独的源码供查看。…

CSS @media 媒体查询全解:打造极致跨平台页面的动态户体体验

随着互联网设备的多样化和用户浏览习惯的变化&#xff0c;现代网页设计越来越注重提供跨平台、跨设备的无缝用户体验。CSS媒体查询media在此背景下扮演着至关重要的角色&#xff0c;它赋予网页设计者精准控制网页样式的能力&#xff0c;使之能随设备环境变化而动态调整&#xf…

内存管理下及模板初阶

嗨喽&#xff0c;今天阿鑫给大家带来内存管理下以及模板初阶的博客&#xff0c;下面让我们开始今天的学习吧&#xff01; 内存管理下及模板初阶 new和delete的实现原理定位new表达式(placement-new)常见面试题泛型编程函数模板类模板 1. new和delete的实现原理 1.1 内置类型…

短链接推荐:一个可以监测用户行为的“营销神器”

客户对我的推广有兴趣吗&#xff1f;他喜欢我的产品吗&#xff1f;他打开了我的营销信息吗&#xff1f;这三个问题相信每一位推广者都遇到过。接下来&#xff0c;就将给大家介绍一位大聪明——它能帮你监测每一位用户的行为&#xff0c;让你分分秒秒掌握用户的心理&#xff01;…

consul服务注册与发现、服务配置与刷新

为什么要用服务注册&#xff1f;为什么要用consul不用eureka&#xff1f; 举个栗子&#xff1a; 微服务当中存在多个服务模块&#xff0c;每个服务模块的ip端口在每套环境是不一致的&#xff0c;开发切换环境部署时&#xff0c;如果漏了一个配置忘记改动&#xff0c;将是一个很…

黑龙江—等保测评三级安全设计思路

需求分析 6.1、 系统现状 6.2、 现有措施 目前&#xff0c;信息系统已经采取了下述的安全措施&#xff1a; 1、在物理层面上&#xff0c; 2、在网络层面上&#xff0c; 3、在系统层面上&#xff0c; 4、在应用层面上&#xff0c; 5、在管理层面上&#xff0c; 6.…

数码摄影色彩构成,数码相机色彩管理

一、资料描述 本套摄影色彩资料&#xff0c;大小58.54M&#xff0c;共有6个文件。 二、资料目录 《抽象彩色摄影集》.阿瑟.pdf 《色彩构成》.pdf 《色彩学》.星云.扫描版.pdf 《摄影色彩构成》.pdf 《数码相机色彩管理》.pdf 数码摄影进阶之4《色彩篇》.pdf 三、资料下…

【PCL】教程narf_feature_extraction 如何从深度图像中提取 NARF 特征

如何从范围图像中提取 NARF 特征  本教程演示如何从深度图像中在 NARF 关键点位置提取 NARF 描述符。该可执行文件使我们能够从磁盘加载点云&#xff08;或创建它&#xff0c;如果没有提供&#xff09;&#xff0c;在其上提取兴趣点&#xff0c;然后在这些位置计算描述符。然…

spring @value @configurationProperties比较

今天项目中需要使用数组的方式 来加载一批 配置 yml: xxxx: - xxxxx - xsssss javaBean Value("${xxxxx.xxxxx}") private List<String> xxxs; 启动时候报错&#xff0c;无法加载&#xff0c;TM试验了1个小时&#xff0c;我一开始想到是格式的问题&#x…

Android 10.0 Launcher3替换桌面app图标后大小和其他app图标不一样的问题解决方案

1.前言 在10.0的系统ROM产品定制化开发中,在关于launcher3的产品定制化开发中,在有些时候需要对一些第三方的app图标做 替换或者是做一些动态图标的替换,发现在替换以后图标大小和其他app的图标大小不一样,所以就需要看是具体哪里 对app的图标做了缩放功能,接下来就需要去…

【注解和反射】类加载器

继上一篇博客【注解和反射】什么时候类会和不会被初始化&#xff1f;-CSDN博客 目录 六、类加载器 测试&#xff1a;获得类加载器 &#xff08;1&#xff09;如何获取Java中的类加载器及其父类加载器 &#xff08;2&#xff09;测试当前类是哪个类加载器 &#xff08;3&am…

【C++】STL-vector模拟实现

目录 1、vactor的模拟实现 1.1 成员变量 1.2 size、capacity 1.3 迭代器 1.4 构造、析构、拷贝构造、operator 1.5 push_back、pop_back、reserve 1.6 operator[] 1.7 insert、erase 1.8 resize 2、使用memcpy拷贝问题 1、vactor的模拟实现 1.1 成员变量 vector是顺…

时尚新选择,小塔RFID技术重塑样衣管理

在时尚领域&#xff0c;样衣是创意与工艺的完美结合&#xff0c;每一件都承载着设计师的心血与期待。然而&#xff0c;当这些珍贵的样版在传统的管理体系下流转时&#xff0c;样版管理成为一个令人头疼的问题。手动记录、盘点和样板追溯成为常态&#xff0c;但这种方式容易出错…

机器学习(二)之监督学习

前言&#xff1a; 上一节大概讲解了几种学习方式&#xff0c;下面几张就具体来讲讲监督学习的几种算法。 以下示例中和都是权重的意思&#xff01;&#xff01;&#xff01; 注&#xff1a;本文如有错误之处&#xff0c;还请读者指出&#xff0c;欢迎评论区探讨&#xff01; 1…

17. map和set的模拟实现(也就是用红黑树封装map和set)

1.map和set底层调用的红黑树的实现 有不清楚的地方&#xff0c;参考AVL树的模拟实现和红黑树的模拟实现 红黑树迭代器的实现 // 红黑树迭代器的类模板 template<class T, class Ref, class Ptr> struct __RBTreeIterator {// 将红黑树节点的类类型定义为Nodetypedef R…

绽放新笑容:儿童换牙期的关怀与注意

引言&#xff1a; 儿童的换牙期是成长过程中的重要阶段&#xff0c;标志着他们逐渐迈向成人世界。然而&#xff0c;伴随着牙齿的脱落和新牙的生长&#xff0c;孩子们可能会经历一些不适和困扰。本文将探讨儿童换牙期的注意事项&#xff0c;以帮助家长和孩子们度过这一特殊时期&…