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

news2024/9/20 15:09:20

目录

一、结构体的类型的声明

二、结构体变量的创建和初始化

三、匿名结构体类型

四、结构体自引用

五、结构体内存对齐

(1)对齐规则

(2)计算结构体大小练习

(3)需要内存对齐的原因

(4)修改默认对齐数

六、结构体传参

七、结构体实现位段

(1)什么是位段

(2)位段的内存分配

(3)位段的跨平台的问题

(4)位段的应用

(5)位段不能使用取地址符&


一、结构体的类型的声明

        形式如下:

struct tag
{
	member - list; // 成员列表
}variable - list; // 变量列表,属于全局变量,也可以没有

        例如,定义一个学生结构体类型,并创建了全局变量s1:

struct Stu
{
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
}s1;

二、结构体变量的创建和初始化

// struct Stu 类型的定义
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;
}

三、匿名结构体类型

        省略掉结构体标签 (tag):

struct
{
	int a;
	char b;
	float c;
}x;

struct
{
	int a;
	char b;
	float c;
}a[20], * p;

        因为匿名结构体类型没有名字,所以如果没有对匿名结构体重命名的话,只能使用一次(创建一次变量,即声明结构体类型的时候就创建)。并且两个成员相同的匿名结构体类型不相同,如下:

四、结构体自引用

        结构体中不能包含同类型的结构体成员。因为结构体类型还没完全声明结束就开始使用同类型是不行的(不清楚它的大小),相当于一个类型还不存在的时候就开始使用这个类型,并且仔细想想,这样声明的结构体的大小是无穷大的,如下:

        如果结构体想自引用同类型,只能定义为指针类型。指针类型是内置类型,本来就存在,大小也可知(4 或 8字节),如下:

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

        如果是对匿名结构体重命名,就算是包含同类型的指针类型,也是不行的,因为在重命名 Node 之前都还不知道这个匿名函数叫啥,就使用 Node 的指针,如下:

        因此,结构体自引用不能使用匿名结构体,改为如下就正确了:

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

五、结构体内存对齐

        是计算结构体大小的规则。

(1)对齐规则

① 结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处。

② 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

        对齐数 = 编译器默认的一个对齐数与该成员变量大小的较小值。

        VS 中默认的值为 8。

        Linux中 gcc 没有默认对齐数,对齐数就是成员自身的大小。

③ 结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的) 的
整数倍。

④ 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。

(2)计算结构体大小练习

练习一:

        结构体S1的大小:

        结构体S2的大小:

        S1 和 S2 类型的成员一模一样,但是 S2 比 S1 占的空间更小,是因为变量 c1 和 c2是放在一起的。因此,让占用空间小的成员尽量集中在一起,更节省空间

练习二:

        结构体S3的大小:

        结构体S4的大小:

(3)需要内存对齐的原因

① 平台原因(移植原因):

        不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,比如 int 类型数据只能在固定地址处存取,否则抛出硬件异常。为了提高代码的可移植性(对所有硬件平台都适用),需要内存对齐。

② 性能原因

        访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。比如对于结构体 s:

struct s{
    char c;
    int i;
};

        如果内存不对齐:

        如果内存对齐:

        32位机器上,数据总线是32根,读、写数据的时候,一次就读/写32位(4个字节)。如果不对齐,要读两个字节,才能拼凑出 i;如果对齐,发现第一个字节没有,直接跳到第二个字节,读取一次就可以得到 i 。因此,内存对齐更能节省读取时间(用空间换时间)。

(4)修改默认对齐数

        使用 #pragma 预处理指令,示例:

#pragma pack(1)//设置默认对⻬数为1
struct S
{
	char c1;
	int i;
	char c2;
};
#pragma pack()//还原为默认对齐数

        对齐数通常是 2 的 x 次方,如 1,2,4,8,不能随意设置。

六、结构体传参

        一种是传结构体本身,一种是传结构体的地址,如下:

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

        因为函数传参,参数要压栈,会有空间和时间上的系统开销。如果结构体比较大,传结构体本身,开销就比较大;如果传结构体地址,指针只有 4 个字节,开销就比较小。因此,结构体传参,最好传地址

七、结构体实现位段

(1)什么是位段

        与结构体类似,但有两个不同:

  •  成员类型必须是 int、unsigned int、signed int、char,C99 标准中也可以是其它类型。
  •  成员名后是 冒号 + 数字

        形式如下:

struct A
{
    int _a : 2;
    int _b : 5;
    int _c : 10;
    int _d : 30;
};

        位段 A 大小是?如果按照结构体的内存对齐的方法计算,4 * 4 = 16 个字节。看看运行结果:

显然不是按结构体的方式计算内存大小,实际上位段的位表示二进制位冒号后面的数字是该成员的大小,比如 _a 占 2 bit 。但是位段 A 的所有成员的大小加起来是 2+5+10+30 = 47,用 6 个字节(68 bit)就够了,为什么是8 字节呢?请看下节。

(2)位段的内存分配

  • 位段每次开辟 4 个字节(int)或者 1 个字节(char)。
  • 位段的不确定因素很多(比如每次开辟从左还是右存储;每次开辟的空间不够下一个成员使用,剩余的空间要不要接着使用),不可跨平台,注重可移植性的代码要避免用位段

        如下例子:

struct S
{
    char a : 3;
    char b : 4;
    char c : 5;
    char d : 4;
};

int main()
{
    struct S s = { 0 };
    printf("%d\n", sizeof(s));

    s.a = 10;
    s.b = 12;
    s.c = 3;
    s.d = 4;
    return 0;
}

        VS中的规则:

  •  char 类型,每次开辟一个字节。
  •  一个字节内,从右向左使用。
  •  一个字节内剩余的 bit 不够下一个成员使用,浪费掉并开辟新的一个字节存放。

        定义结构体变量 s 并初始化位段成员值为 0,开辟如下的空间(因为位段是 char 类型,每次开辟 1 个字节空间):

        一共是3个字节。再给所有位段成员赋值(超出的截断,不够的补0):

        调试验证,每 4 bit 是一个十六进制数,那么上面的值用十六进制表示就是(62 03 04):

        调试结果与理论一致:

        运行结果:

        解决(1)中遗留的问题:

struct A
{
    int _a : 2;
    int _b : 5;
    int _c : 10;
    int _d : 30;
};

        因为位段成员是 int 类型,所以每次开辟 4 个字节(一共开辟了 8 个字节):

(3)位段的跨平台的问题

  •  int 位段被当成有符号还是无符号数是不确定的。
  •  位段中最大的数目不确定。(32位机器最大位是32,16位机器最大位是16。如果是16位机器,像下面这样写就是错误的,因为 30 位已经超过了最大的 16 位;但在 32 位机器上是正确的。)
struct S
{
    int a : 30;
};
  • 每次开辟的空间,是从左向右,还是从右向左使用是不确定的。
  • 每次开辟的空间,剩余的空间不够下一个位段成员使用时,是浪费掉还是接着使用是不确定的。

总结:位段(可以设置使用的位)比结构体(固定的字节)更节省空间,但存在跨平台的问题。

(4)位段的应用

        数据在网络上传输,需要遵守网络协议,网络协议中有个IP数据报的概念(相当于快递包裹上的各种邮寄信息,有发件人、收件人,才知道包裹从哪发、发给谁),下面就是IP数据报的格式:

        如果不用位段,版本(4位)是整型,分配 4 个字节空间,就会浪费 28 位空间。可以发现每一行信息需要的空间加起来刚好是 32 位(4 个字节),刚好是一个整型的大小,设计成位段,将会没有一点空间浪费。

        IP数据报追求节省空间,因为使用更小的空间,网络越通畅。

(5)位段不能使用取地址符&

        位段中几个成员共用一个字节,而内存中是按字节编址的,所以一个字节内的 bit 没有地址,就不能对位段成员取地址,如下会报错:

        因此,不能使用 scanf 直接给位段成员输入值,只能先输入值放在变量中,变量再赋值给位段成员:

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

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

相关文章

Eigen3 教程基础篇(三)

参考 Eigen3 主页,Eigen3 官网教程 矩阵的本质,通过多种矩阵的应用去感受矩阵本质 3Blue1Brown 的线性代数,用可视化方法来表现线性代数的特性,强推 如何理解复数和虚数,有动画方便理解复数的意义 相关文章 Eigen…

基于SpringBoot+Vue+MySQL的在线宠物用品商城销售系统

系统展示 用户前台界面 管理员后台界面 系统背景 随着人们生活质量的提升和宠物经济的蓬勃发展,宠物已成为众多家庭不可或缺的一员。宠物市场的需求日益增长,涵盖了食品、用品、医疗、美容等多个领域。基于SpringBootVueMySQL的在线宠物用品商城销售系统…

新产品,推出 MLX90372GVS 第三代 Triaxis® 位置传感器 IC,适用于汽车和工业系统(MLX90372GVS-ACE-308)

Triaxis 旋转和线性位置传感器IC: MLX90372GVS-ACE-103 MLX90372GVS-ACE-108 MLX90372GVS-ACE-301 MLX90372GVS-ACE-200 MLX90372GVS-ACE-208 MLX90372GVS-ACE-303 MLX90372GVS-ACE-300 MLX90372GVS-ACE-350 MLX90372GVS-ACE-100 MLX90372GVS-ACE-101 MLX90372GVS-…

【Java算法】二叉树的深搜

🔥个人主页: 中草药 🔥专栏:【算法工作坊】算法实战揭秘 一.2331.计算布尔二叉树的值 题目链接:2331.计算布尔二叉树的值 代码 public boolean evaluateTree(TreeNode root) {if(root.leftnull){return root.val0?f…

VSCode值得推荐的插件(持续更新中)

VSCode值得推荐的插件(持续更新中) 说明1.Peacock 说明 主要记录VSCode开发过程中插件 1.Peacock 允许开发者为 Visual Studio Code 的工作区界面(如侧边栏、底栏和标题栏)自定义颜色,以区分不同的项目或编码环境。…

【machine learning-七-线性回归之成本函数】

监督学习之cost function 成本函数权重、偏置如何实现拟合数据成本函数是如何寻找出来w和b,使成本函数值最小化? 在线性回归中,我们说到评估模型训练中好坏的一个方法,是用成本函数来衡量,下面来详细介绍一下 成本函数…

需求3:照猫画虎

说起写需求,其实对于我这种小白而言,接到一个需求,最好的方式就是照猫画虎。 因为我从0到1写,以我现在这种水平,根本就不可能完成。所以照猫画虎,模仿着来写是最好的提升方法。 之前在聊天的时候&#xf…

记录|如何对批量型的pictureBox组件进行批量Image设置

目录 前言一、问题表述二、批量化处理更新时间 前言 参考文章: 一、问题表述 问题就是上图所示,这些的命名风格统一,只是最后的数字是不同的。所以存在可以批量化进行处理的可能性。 二、批量化处理 private void SetPictureBoxImages(){for…

Flat File端口更新:如何实现嵌套结构

Flat File端口可以实现平面文件和XML文件的互相转换,本文主要介绍在知行之桥EDI系统8971及更高版本中,Flat File端口如何支持类似EDI嵌套结构的转换。 Flatfile端口如何自定义嵌套结构 下载示例工作流以及示例文件 打开知行之桥EDI系统,创建…

OpenCV特征检测(1)检测图像中的线段的类LineSegmentDe()的使用

操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 检测图像中线段的类.。 遵循在 285中描述的算法。 函数原型1 绘制两组线,一组用蓝色,一组用红色,并计算非重…

[云服务器12] 搭建eaglercraft网页MC

众所周知,MC是一个炒鸡好玩的游戏! 但是,Mojang开发出来是经过Java JAR打包过的的.jar文件,这就不得不依赖HMCL PCL BakaXL等启动器来启动了…… 所以今天,我们将使用开源的eaglercraft来搭建一个在线版MC&#xff0…

鸟类识别系统Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+ResNet50算法模型+图像识别

一、介绍 鸟类识别系统。本系统采用Python作为主要开发语言,通过使用加利福利亚大学开源的200种鸟类图像作为数据集。使用TensorFlow搭建ResNet50卷积神经网络算法模型,然后进行模型的迭代训练,得到一个识别精度较高的模型,然后在…

【VUE3.0】动手做一套像素风的前端UI组件库---Button

目录 引言做之前先仔细看看UI设计稿解读一下都有哪些元素:素材补充 代码编写1. 按钮四周边框2. 默认状态下按钮颜色立体效果3. 鼠标移入聚焦4. 模拟鼠标点击效果 组件封装1. 按类型设置颜色2. 设置按钮禁用状态3. 处理一个bug4. 看下整体组件效果5. 组件完整代码6. …

数字自然资源领域的实现路径

在数字化浪潮的推动下,自然资源的管理与利用正经历着前所未有的变革。本文将从测绘地理信息与遥感专业的角度,深度分析数字自然资源领域的实现路径。 1. 基础数据的数字化 数字自然资源的构建,首先需要实现基础数据的数字化。这包括地形地貌…

db2恢复数据库

db2licm -l检查下license IBM Support: Fix Central - Please wait, Select fixes db2 force application all db2ckbkp -H JYC.0.DB2.NODE0000.CATN0000.20240603223001.001 db2 "restore db jyc logtarget x:\db2\log" db2 "rollforward db jyc to end of log…

音频北斗定位系统有什么用?

在当今科技飞速发展的时代,定位技术已经成为我们日常生活和各行各业不可或缺的一部分。其中,音频北斗定位系统作为一种新兴的定位技术,正逐渐展现出其独特的优势和应用价值。那么,到底音频北斗定位系统有什么用呢?我们一起来了解…

住宅代理IP如何提高 IP声誉?

你有没有遇到过类似的问题?发送的邮件被标记为垃圾邮件并被屏蔽、访问某些网站被拒绝、广告效果不理想,甚至网上交易无缘无故被拒绝?这到底是什么原因造成的?其实,这些问题可能都和 IP 信誉息息相关。 如果你的 IP 地址…

一文读懂HPA弹性扩展以及实践攻略

一文读懂HPA弹性扩展以及实践攻略 目录 1 概念: 1.1 什么是弹性扩展1.2 HPA 的工作原理1.3 通过监控指标来调整副本数 1.3.1 计算公式说明1.3.2 平均值计算1.3.3 未就绪 Pod 和丢失的指标处理1.3.4 多指标支持1.3.5 缩减副本的平滑策略 1.4 HPA的优缺点 2 实践攻略…

微服务保护学习笔记(五)Sentinel授权规则、获取origin、自定义异常结果、规则持久化

文章目录 前言4 授权规则4.1 基本原理4.2 获取origin4.3 配置授权规则 5 自定义异常结果6 规则持久化 前言 微服务保护学习笔记(一)雪崩问题及解决方案、Sentinel介绍与安装 微服务保护学习笔记(二)簇点链路、流控操作、流控模式(关联、链路) 微服务保护学习笔记(三)流控效果(…

C语言 14 结构体 联合体 枚举

之前认识过很多种数据类型,包括整数、小数、字符、数组等,通过使用对应的数据类型,就可以很轻松地将数据进行保存了,但是有些时候,这种简单类型很难去表示一些复杂结构。 结构体 比如现在要保存 100 个学生的信息&am…