【C语言(十三)】

自定义类型:结构体

一、结构体类型的声明 

1.1、结构体回顾

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

1.1.1、结构的声明 

例如描述⼀个学生:

struct Stu
{char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号
}; //分号不能丢
 1.1.2、结构体变量的创建和初始化
#include <stdio.h>struct Stu
{char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号
};int main()
{//按照结构体成员的顺序初始化struct Stu s = { "张三", 20, "男", "20230818001" };printf("name: %s\n", s.name);printf("age : %d\n", s.age);printf("sex : %s\n", s.sex);printf("id : %s\n", s.id);//按照指定的顺序初始化struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex = "⼥"};printf("name: %s\n", s2.name);printf("age : %d\n", s2.age);printf("sex : %s\n", s2.sex);printf("id : %s\n", s2.id);return 0;
}

1.2、结构的特殊声明

在声明结构的时候,可以不完全的声明。 

比如: 

//匿名结构体类型
struct
{int a;char b;float c;
}x;struct
{int a;char b;float c;
}a[20], * p;

 上面的两个结构在声明的时候省略掉了结构体标签(tag)。

 那么问题来了?

警告: 

编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的。
匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用⼀次。 

1.3、结构的自引用 

在结构中包含⼀个类型为该结构本身的成员是否可以呢? 

如,定义⼀个链表的节点:

struct Node
{int data;struct Node next;
};
上述代码正确吗?如果正确,那 sizeof(struct Node) 是多少?

仔细分析,其实是不行的,因为⼀个结构体中再包含⼀个同类型的结构体变量,这样结构体变量的大小就会无穷的大,是不合理的。

正确的自引用方式: 

struct Node
{int data;struct Node* next;
};
在结构体自引用使用的过程中,夹杂了 typedef 对匿名结构体类型重命名,也容易引入问题,看看
下面的代码,可行吗?

答案是不行的,因为Node是对前面的匿名结构体类型的重命名产生的,但是在匿名结构体内部提前使用Node类型来创建成员变量,这是不行的。

解决方案如下:定义结构体不要使用匿名结构体了

typedef struct Node
{int data;struct Node* next;
}Node;

二、结构体内存对齐 

我们已经掌握了结构体的基本使用了。
现在我们深入讨论⼀个问题:计算结构体的大小。
这也是⼀个特别热门的考点: 结构体内存对齐。

2.1、对齐规则 

首先得掌握结构体的对齐规则: 

1. 结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    对齐数 = 编译器默认的⼀个对齐数 与 该成员变量大小的较小值
        - VS 中默认的值为 8
        - Linux中 gcc 没有默认对齐数,对齐数就是成员自身的大小
3. 结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对齐数中最大的)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
//练习1
struct S1
{char c1;int i;char c2;
};//练习2
struct S2
{char c1;char c2;int i;
};int main()
{struct S1 s1 = { 0 };struct S2 s2 = { 0 };printf("%zd\n", sizeof(struct S1));printf("%zd\n", sizeof(struct S2));return 0;
}

#include <stddef.h>
//宏
//offsetof - 计算结构体成员相较于起始位置的偏移量//练习3
struct S3
{double d;char c;int i;
};//练习4-结构体嵌套问题
struct S4
{char c1;struct S3 s3;double d;
};

2.2、为什么存在内存对齐?

大部分的参考资料都是这样说的:
1. 平台原因 (移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用⼀个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。
总体来说:结构体的内存对齐是拿空间来换取时间的做法。

那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到: 

//例如:
struct S1
{char c1;int i;char c2;
};struct S2
{char c1;char c2;int i;
};

 S1 S2 类型的成员⼀模⼀样,但是 S1 S2 所占空间的大小有了⼀些区别。

2.3、修改默认对齐数 

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

#include <stdio.h>#pragma pack(1)//设置默认对⻬数为1
struct S
{char c1;int i;char c2;
};#pragma pack()//取消设置的对⻬数,还原为默认int main()
{//输出的结果是什么?printf("%d\n", sizeof(struct S));return 0;
}
结构体在对齐方式不合适的时候,我们可以自己更改默认对齐数。

三、结构体传参 

struct S
{int data[1000];int num;
};
struct S s = { {1,2,3,4}, 1000 };//结构体传参
void print1(struct S s)
{printf("%d\n", s.num);
}//结构体地址传参
void print2(struct S* ps)
{printf("%d\n", ps->num);
}int main()
{print1(s); //传结构体print2(&s); //传地址return 0;
}

上面的 print1 print2 函数哪个好些?

答案是:首选print2函数。

结论:

结构体传参的时候,要传结构体的地址。

四、结构体实现位段

4.1、什么是位段?

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

1. 位段的成员必须是 int unsigned int signed int ,在C99中位段成员的类型也可以选择其他类型。
2. 位段的成员名后边有⼀个冒号和⼀个数字。
比如:
struct A
{int _a:2;int _b:5;int _c:10;int _d:30;
};
A就是⼀个位段类型。
那位段A所占内存的大小是多少?

4.2、位段的内存分配 

1. 位段的成员可以是 int unsigned int signed int 或者是 char 等类型
2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
//⼀个例⼦
struct S
{char a:3;char b:4;char c:5;char d:4;
};
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;//空间是如何开辟的?

以上我们做了假设: ①假设内存空间从右向左使用;②如果剩余的空间不够下一个成员使用,就浪费。

4.3、位段的跨平台问题 

1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最⼤16,32位机器最大32,写成27,在16位机器会出问题。)
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当⼀个结构包含两个位段,第⼆个位段成员比较大,无法容纳于第⼀个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
总结:
跟结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。

4.4、位段的应用 

下图是网络协议中,IP数据报的格式,我们可以看到其中很多的属性只需要几个bit位就能描述,这里使用位段,能够实现想要的效果,也节省了空间,这样网络传输的数据报大小也会较小⼀些,对网络的畅通是有帮助的。

 

4.5、位段使用的注意事项 

位段的几个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配⼀个地址,⼀个字节内部的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/news/224006.shtml

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

相关文章

三层交换,DHCP的详解与VRRP

目录 一、三层交换 1、三层交换机的作用&#xff1a; 2.vlan的虚拟接口vlanif&#xff08;ifinterface接口&#xff09; 3.三层交换机实验 4.拓展实验​编辑 二、DHCP 1.自动获取ip地址&#xff1a; 2.DHCP的好处&#xff1a; 3.分配方式&#xff1a; 4.举例&#xff…

基于SpringBoot在线电子购物平台网的设计与实现

文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于SpringBoot在线电子购物平台网的设计…

分布式消息传递新时代:深入了解RabbitMQ_sharding插件的精髓【RabbitMQ 八】

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 分布式消息传递新时代&#xff1a;深入了解RabbitMQ_sharding插件的精髓 引言前言基础概念以及原理配置和使用应用场景结语 引言 &#x1f33a;&#xff1a;docker构建rabbitmq并配置延迟队列插件 ⏳…

KAKFA实践零碎记录

这里写目录标题 1 内存泄露2 生产者报错 1 内存泄露 错误信息 反复执行&#xff1a;创建消费者->关闭消费者后&#xff0c;内存缓慢上升且GC不能回收内存 错误原因 关闭消费者需要执行KafkaConsumer#close()函数 public void close() {this.close(Duration.ofMillis(30000…

Linux——进程中被打开的文件

C语言中有着许多对文件操作的函数&#xff0c;包括其他语言也有&#xff0c;但是从语言来了解文件有点浅显计算机的一切都离不开操作系统&#xff0c;那么文件跟操作系统也有着密切的关系&#xff0c;所以我们从系统层面来了解文件&#xff08;在进程中打开的文件&#xff09;文…

acwing算法提高之动态规划--状态机模型

目录 1 基础知识2 模板3 工程化 1 基础知识 暂无。。。 2 模板 暂无。。。 3 工程化 题目1&#xff1a;大盗阿福。 解题思路&#xff1a;状态表示多了一维&#xff0c;取0或者取1&#xff0c;表示不选择第i个物品和选择第i个物品。 C代码如下&#xff0c; #include <…

数据结构之---- 排序算法

数据结构之---- 排序算法 什么是排序算法&#xff1f; 排序算法用于对一组数据按照特定顺序进行排列。 排序算法有着广泛的应用&#xff0c;因为有序数据通常能够被更有效地查找、分析和处理。 如图所示&#xff0c;排序算法中的数据类型可以是整数、浮点数、字符或字符串等…

NXP应用随记(四):eMios阅读随记-整体功能概述

目录 1、eMios IP介绍 2、时钟结构 3、通道类型 4、功能介绍 5、中断与DMA 6、EMIOS -通道分配建议(针对S32K312) 1、eMios IP介绍 Emios是什么&#xff1f;eMIOS提供了独立的通道(UCs)&#xff0c;您可以配置这些通道来为不同的功能生成或测量时间事件。 每个eMIOS实例最…

本地项目添加到gitlab命令操作

gitlab上面创建一个跟项目名同名的文件夹 创建文件夹&#xff0c;填写信息 添加readme文档&#xff0c;先保存下创建的文件夹 回到项目&#xff0c;复制项目的git 地址 然后进入到本地项目的文件夹&#xff0c;如d:/workspace/spring-demo&#xff0c;右键打开git bash弹框 命令…

【C语言】二分查找(详解)

&#x1f3a5; 岁月失语唯石能言的个人主页 &#x1f525;个人栏专&#xff1a;秒懂C语言 ⭐若在许我少年时&#xff0c;一两黄金一两风 一、二分查找的思路 二分查找是一种高效的查找算法&#xff0c;尤其适用于有序数组。它的基本思想是通过将查找区间逐步缩小…

Amortized Bootstrapping of LWE:使用 BFV 打包处理

参考文献&#xff1a; [AP13] Alperin-Sheriff J, Peikert C. Practical bootstrapping in quasilinear time[C]//Annual Cryptology Conference. Berlin, Heidelberg: Springer Berlin Heidelberg, 2013: 1-20.[MS18] Micciancio D, Sorrell J. Ring packing and amortized F…

基于CNN+数据增强+残差网络Resnet50的少样本高准确度猫咪种类识别—深度学习算法应用(含全部工程源码)+数据集+模型(四)

系列文章目录 基于CNN数据增强残差网络Resnet50的少样本高准确度猫咪种类识别—深度学习算法应用(含全部工程源码)数据集模型&#xff08;一&#xff09; 基于CNN数据增强残差网络Resnet50的少样本高准确度猫咪种类识别—深度学习算法应用(含全部工程源码)数据集模型&#xf…

【动手学深度学习】(十四)数据增广+微调

文章目录 一、数据增强1.理论知识2.代码 二、微调1.理论知识 一、数据增强 1.理论知识 增加一个已有数据集&#xff0c;使得有更多的多样性 在语言里面加入各种不同的背景噪音改变图片的颜色和形状 使用增强数据训练 翻转 左右翻转上下翻转 不总是可行 切割 从图片中切…

【高效开发工具系列】DataGrip入门

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

如何应对内部威胁?iS-CDA全方位保障金融机构第三方合作安全

随着银行、保险等金融机构对第三方服务的依赖程度不断提高,如何保证内部的安全性和可靠性,成为了一项重要的任务。2023年6月,政府发布了《关于加强第三方合作中网络和数据安全管理的通知》,为企业提供了明确的政策指导。 为了应对这些威胁,企业需要一种可靠的解决方案。i…

文件操作2❤

一&#xff1a;文件的顺序读写 1&#xff1a;顺序读写函数 函数名 功能 适⽤于 fgetc 字符输⼊函数 所有输⼊流 fputc 字符输出函数 所有输出流 fgets ⽂本⾏输⼊函数 所有输⼊流 fputs ⽂本⾏输出函数 所有输出流 fscanf 格式化…

linux系统中出现大量不可中断进程和僵尸进程怎么办?

进程状态 当iowait升高时&#xff0c;进程很可能因为得不到硬件的响应&#xff0c;而长时间处于不可中断的状态&#xff0c;从ps或者top命令的输出中&#xff0c;可以发现它们都处于D状态&#xff0c;也就是不可中断状态。 通过top和ps可以查看进程的状态&#xff0c;S列表示…

python将路径下图片批量化重命名(前后缀及文件格式转化)

1 - 为文件下的图片批量化加上 / 去掉 后缀名 如 将文件夹中的图片名“label_001.png”, "label_002.png", ... 转化为 “001.png”, "002.png", ... import osdef rename_images(folder_path, prefix):# 获取文件夹中的所有文件files os.listdir(folde…

Python学习开发mock接口

#1.测试为什么要开发接口&#xff1f; 1)在别的接口没有开发好的时候, mock接口(模拟接口) 2)查看数据, 避免直接操作数据库 #2.开发接口的顺序 1)安装flask flask是一个轻量级开发框架 pip install flask 2)开发一个接口 开发步骤&#xff1a; 1.实例化一个服务server:f…

普冉(PUYA)单片机开发笔记(9): FLASH 读写

概述 单片机的 ROM 容量虽然不大&#xff0c;PY32F003 有 64K 字节的 ROM&#xff0c;但实际应用中会在 MCU 中存储持久化的数据&#xff0c;例如&#xff1a;在物联网应用中&#xff0c;需要把物模型持久化&#xff0c;作为非易失性数据&#xff0c;掉电了也要保存。这就要用…