当前位置: 首页 > news >正文

结构体全解,适合初学者的一条龙深度讲解(附手绘图详解)

我们知道,C语言是允许我们自己来创造类型的,这些类型就叫做——自定义类型。

自定义类型又包括结构体类型,联合体类型还有枚举类型。

今天的文章,我们就着重讲解这其中的结构体类型。

目录

结构体的声明

1.1结构的基础知识

1.2结构的声明

1.3 匿名结构体的情况

1.4结构的自引用

 1.5重命名匿名结构体的情况

1.6 结构体变量的定义和初始化

 1.7 结构体内存对齐

1.8为什么存在内存对齐?

1.9我们可以耍些小聪明达到节省空间的效果。

2.1修改默认对齐数

2.2 结构体传参

3.1位段

3.2 位段的内存分配

3.3 位段的跨平台问题

结构体的声明

1.1结构的基础知识

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

1.2结构的声明

struct tag
{
member-list;
}variable-list;

我们以这种方式来描述一个结构体。下面是简单的示范,我们来描述一个学生:

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

 定义局部变量和全局变量的关系:

#define _CRT_SECURE_NO_WARNINGS
struct Stu
{char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号
}s1,s2,s3; //全局变量
int main()
{struct Stu s4;struct Stu s5;//局部变量return 0;
}

1.3 匿名结构体的情况

也可以省略不写结构体标签,不过这样会导致一个结果,结构体只能定义一次类型。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
struct 
{char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号
}s1; //全局变量
struct 
{char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号
}*ps; //全局变量
int main()
{s1.age = 1;printf("%d", s1. age);return 0;
}

  

在上述的代码中,体现为定义结构体变量s1之后,无法再次定义诸如s2,s3等结构体类型。

不过要是你本来就准备只用一次结构体的话,定义一个匿名结构体也不错就是了。 

上面的两个结构在声明的时候省略掉了结构体标签, 那么问题来了?

//在上面代码的基础上,下面的代码合法吗? 

ps=&s1; 

 答案是否定的,及时两个结构体里面的元素都相同,编译器也会他们当成两个完全不同的类型,所以是非法的。

1.4结构的自引用

我们想要使用结构体实现类似于链表的功能。

在结构中包含一个类型为该结构本身的成员是否可以呢? 
#include<stdio.h>
struct Node
{int data;struct Node n;
};
int main()
{return 0;
}

 我们开动小脑筋,立马就发现了错误。

struct Node这个节点它所占用的空间有多大呢?

它不仅要存放一个整形,还要存放一个n。

这就无限循环下去了,struct Node里面还有一个struct Node。

大小是无法得出的,这是一个错误示范。

我们转变战略,用指针来实现。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
struct Node
{int data;//4struct Node *next;//4/8
};
int main()
{struct Node n1;struct Node n2;n1.next = &n2;return 0;
}

创建两个节点n1,n2,把它们像链条一样串起来。 

 编译器没有报错,这样的写法是正确的,同时我们发现,struct Node的大小可以轻而易举地算出,我们得出一个结论:

不是在自己的类型里面包含一个自己类型的变量,而是在自己的类型里面包含一个自己类型的指针。这样的实现方式才是可行的。

 1.5重命名匿名结构体的情况

下面的代码是否可行呢?

#include<stdio.h>
typedef struct 
{int data;
}S;
int main()
{return 0;
}

 可行,不过S不再是匿名结构体的变量,而是变成了匿名结构体类型。

怎么用呢?这么用:

#include<stdio.h>
typedef struct 
{int data;
}S;
int main()
{S s;s.data = 1;printf("%d", s.data);return 0;
}

能用这种方式模拟实现上面的链表呢?

这样写行吗?

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

不行,在没有重命名出Node时就调用了Node。

在这种情况下,我们只能老老实实地写出类型名了!

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

1.6 结构体变量的定义和初始化

有了结构体类型,那如何定义变量,其实很简单。 

 int x;int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量
struct Point
{
p2
//初始化:定义变量的同时赋初值。
struct Point p3 = {x, y};
struct Stu        //类型声明
{char name[15];//名字int age;      //年龄
};
struct Stu s = {"zhangsan", 20};//初始化
struct Node
{int data;struct Point p;struct Node* next; 
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化

 1.7 结构体内存对齐

我们已经掌握了结构体的基本使用了。
现在我们深入讨论一个问题:计算结构体的大小。
这就到了本文的重中之重: 结构体内存对齐。

 计算以下的结构体大小。

#include<stdio.h>
int main()
{struct S1{char c1;int i;char c2;};printf("%d\n", sizeof(struct S1));//练习2struct S2{char c1;char c2;int i;};printf("%d\n", sizeof(struct S2));//练习3struct S3{double d;char c;int i;};printf("%d\n", sizeof(struct S3));//练习4-结构体嵌套问题struct S4{char c1;struct S3 s3;double d;};printf("%d\n", sizeof(struct S4));
}
运行结果如下:

 是不是跟想的完全不一样?

没错,结构体的大小并不是成员大小的简单相加,而是有自己的一套规则的。

  1.  结构体的第一个成员永远是放在零偏移处。
  2. 从第二个成员开始,以后每个对齐成员都要对齐到某个对齐数的整数倍处。
  3. 这个对齐数是成员自身大小和默认对齐数的较小值。
  4. VS中默认的值为8
  5.  结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
  6.  如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。如果不够,则浪费空间来对齐。

我们以s1为例子来试验一下上述规则,如图所示。 

因为从第二个成员开始,以后每个对齐成员都要对齐到某个对齐数的整数倍处。

所以1,2,3三个字节被浪费,int类型的存储从4开始到7,char类型存到8处。

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

 S1中最大对齐数为4,结构体总大小要为最大对齐数(每个成员变量都有一个对齐数)的整数倍。而现在大小为9,为了让其变为4的倍数,结构体S1的总大小变为12。

再看S4的情况:

白色为浪费部分,黄色为char,绿色是double,粉色是int。

1.8为什么存在内存对齐?

1.不同硬件平台不一定支持访问任意内存地址数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。使用内存对齐可以保证每次访问都从块内存地址头部开始存取

2.提高cpu内存访问速度,内存是分块的,如两字节一块,四字节一块,考虑这种情况:一个四字节变量存在一个四字节地址的后三位和下一个四字节地址的前一位,这样cpu从内存中取数据便需要访问两个内存并将他们组合起来,降低cpu性能

用内存对齐达到了用空间换时间的效果

1.9我们可以耍些小聪明达到节省空间的效果。

让占用空间小的成员尽量集中在一起。
//例如:
struct S1
{char c1;int i;char c2;
};
struct S2
{char c1;char c2;int i;
};

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

2.1修改默认对齐数

我们可以通过#pragma pack()指令来修改默认对齐数。

#include <stdio.h>
#pragma pack(1)
//设置默认对齐数为1
struct S1
{char c1;int i;char c2;
};
int main()
{//输出的结果是什么?printf("%d\n", sizeof(struct S1));return 0;
}

 可以看到,答案不再是12,默认对齐数确实被修改了。

想要取消的话就引入一个空指令。

#include <stdio.h>
#pragma pack(1)//设置默认对齐数为1
#pragma pack()//取消设置的默认对齐数,还原为默认
struct S1
{char c1;int i;char c2;
};int main()
{//输出的结果是什么?printf("%d\n", sizeof(struct S1));return 0;
}

 

2.2 结构体传参

 下面print1和print2那个比较好?

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函数。
原因:
  • 函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
  • 如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。

所以结构体传参数时,要传结构体的地址。

3.1位段

结构体讲完就得讲讲结构体实现位段的能力。
struct A
{int _a:2;int _b:5;int _c:10;int _d:30;
};

 A就是一个位段的类型,位段可以控制所给的空间大小,达到节省空间的目的。

 它所占空间是多大?

#include <stdio.h>
struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};
int main()
{printf("%d\n", sizeof(struct A));return 0;
}

 它占了8*8=64个比特位。

从16个字节优化到8个字节,位段的功能可以说是十分强大。

3.2 位段的内存分配

1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
#include <stdio.h>
//一个例子
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;
}
  • 首先做一个假设,假设内存中的比特位是由右向左使用的。
  • 一个字节内部,剩余的比特位不够使用时,直接浪费掉。

 

 我们猜想是这个样子。

转换成16进制为:

62  03  04

我们来调试看看:

 我们的猜想是正确的!

3.3 位段的跨平台问题

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

这篇博客旨在总结我自己阶段性的学习,要是能帮助到大家,那可真是三生有幸!😀如果觉得我写的不错的话还请点个赞和关注哦~我会持续输出编程的知识的!🌞🌞🌞 

相关文章:

结构体全解,适合初学者的一条龙深度讲解(附手绘图详解)

我们知道&#xff0c;C语言是允许我们自己来创造类型的&#xff0c;这些类型就叫做——自定义类型。 自定义类型又包括结构体类型&#xff0c;联合体类型还有枚举类型。 今天的文章&#xff0c;我们就着重讲解这其中的结构体类型。 目录 结构体的声明 1.1结构的基础知识 …...

什么是SD-WAN技术?企业网络优化的利器!

现今&#xff0c;企业网络架构已成为其发展不可或缺的组成部分。针对网络性能优化方面&#xff0c;SD-WAN是一种值得深思熟虑的选择&#xff0c;在企业网络中应用SD-WAN技术能够带来多重好处。 什么是SD-WAN技术以及它是如何工作的&#xff1f; SD-WAN是软件定义的广域网&…...

JAVA练习106- 生命游戏

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、题目-生命游戏 1.题目描述 2.思路与代码 2.1 思路 2.2 代码 总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 4 月12日练习…...

【案例教程】基于R语言、MaxEnt模型融合技术的物种分布模拟、参数优化方法、结果分析制图与论文写作实践技术

【原文链接】: 基于R语言、MaxEnt模型融合技术的物种分布模拟、参数优化方法、结果分析制图与论文写作实践技术https://mp.weixin.qq.com/s?__bizMzU5NTkyMzcxNw&mid2247537049&idx3&sn31ef342c4808aed6fee6ac108b899a33&chksmfe6897f3c91f1ee5c4fa8e4eeea34…...

php7类型约束,严格模式

在PHP7之前&#xff0c;函数和类方法不需要声明变量类型 &#xff0c;任何数据都可以被传递和返回&#xff0c;导致几乎大部分的调用操作都要判断返回的数据类型是否合格。 为了解决这个问题&#xff0c;PHP7引入了类型声明。 目前有两类变量可以声明类型&#xff1a; 形参&a…...

2023-04-11 无向图的匹配问题

无向图的匹配问题 之所以把无向图的这个匹配问题放到最后讲是因为匹配问题借鉴了有向图中一些算法的思想 1 最大匹配和完美匹配 二分图回顾 二分图&#xff1a;把一个图中的所有顶点分成两部分&#xff0c;如果每条边的两端分别属于不同部分&#xff0c;则这个图是二分图。更多…...

国家出手管人工智能AI了

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 全球都在封杀AI&#xff0c;国家也出手了&#xff0c;人工智能AI的强监管来了!这次反应速度算是很快了。国家出手&#xff0c;AI必须管。 国家网信办拟针对生成式人工智能服务出台管理办法&#…...

day24—选择题

文章目录1.将N条长度均为M的有序链表进行合并&#xff0c;合并以后的链表也保持有序&#xff0c;时间复杂度为&#xff08;A&#xff09;2.已知某个哈希表的n个关键字具有相同的哈希值&#xff0c;如果使用二次探测再散列法将这n个关键字存入哈希表&#xff0c;至少要进行&…...

自投递简历以来的第一次面试

投完简历之后HR小姐姐接着就安排了面试&#xff0c;原定时间是今天下午六点&#xff0c;我五点五十进的会议&#xff0c;结果等到六点二十&#xff08;真的有点不耐烦了说实话&#xff09;面试官打电话过来了说网络不是很好&#xff0c;所以改成电话面试了。 1、session信息保…...

【C++11】新特性 - 右值引用详解

文章目录STD容器使用右值引用场景移动语义在容器中的使用主要体现在两个方面&#xff1a;移动构造函数和移动赋值运算符。移动语义只对右值有效&#xff0c;对左值无效原因STD容器使用右值引用场景 移动语义在容器中的使用主要体现在两个方面&#xff1a;移动构造函数和移动赋…...

C++学习笔记

C学习笔记函数一般有返回值&#xff0c;构造函数有没有返回值&#xff1f;有返回值&#xff0c;返回一个对象&#xff0c;确定所以没写&#xff1b;在头文件中&#xff0c;防卫式声明&#xff0c;#ifndef…#define … #endif;pass by value或者 reference&#xff0c;传值是整包…...

项目1实现login登录功能方案设计第三版

需求优化点:MySQL表常用功能模块实现方案index页面home页面需求 实现一个登录功能 实现的功能 注册(邮箱注册)登录(邮箱密码)重置密码查看操作记录(登录, 注册, 重置密码, 登出. 都算操作)登出在第2版的基础上进行优化:\ 优化点: VerificationCode(验证码储存库): 增加时间字段…...

Node【七】初识Express框架

文章目录&#x1f31f;前言&#x1f31f;Express框架&#x1f31f;1.什么是框架&#x1f31f;2.express安装&#x1f31f;3.创建web服务基本遵循之前的四个步骤&#xff1a;&#x1f31f;4.路由&#x1f31f; 由 &#xff1a;请求方式请求路径&#xff08;1&#xff09;get发送…...

Android 高通Camera2 Camera Device Close

1、很多人看到这个日志第一感觉可能觉得哪里没有合理释放&#xff0c;于是带着这个思路去进行百度探索 2、一开始我去寻找 ImageReader.OnImageAvailableListener 这个问题 var afterBitmap: Bitmap? null/**监听拍照的图片 */private val imageAvailableListener ImageRead…...

TensorFlow Lite,ML Kit 和 Flutter 移动深度学习:1~5

原文&#xff1a;Mobile Deep Learning with TensorFlow Lite, ML Kit and Flutter 协议&#xff1a;CC BY-NC-SA 4.0 译者&#xff1a;飞龙 本文来自【ApacheCN 深度学习 译文集】&#xff0c;采用译后编辑&#xff08;MTPE&#xff09;流程来尽可能提升效率。 不要担心自己的…...

4、浅谈Makefile文件及其简单的使用知识

文章目录1、什么是Makefile&#xff1f;&#xff08;1&#xff09;makefile关系到了整个工程的编译规则。&#xff08;2&#xff09;makefile带来的好处就是——“自动化编译”&#xff08;3&#xff09;make是一个命令工具&#xff0c;是一个解释makefile中指令的命令工具2、为…...

5G/V2X赛道「重启」

在提升高阶智能驾驶安全性和感知冗余能力的道路上&#xff0c;除了激光雷达、高精度地图及定位&#xff0c;还有一项技术可能即将掀起一场新的风暴。 就在今年3月&#xff0c;作为全球通信领域的年度风向标 — 2023世界移动通信大会&#xff08;MWC&#xff09;上&#xff0c;…...

pytorch进阶学习(四):使用不同分类模型进行数据训练(alexnet、resnet、vgg等)

课程资源&#xff1a;5、帮各位写好了十多个分类模型&#xff0c;直接运行即可【小学生都会的Pytorch】_哔哩哔哩_bilibili 目录 一、项目介绍 1. 数据集准备 2. 运行CreateDataset.py 3. 运行TrainModal.py 4. 如何切换显卡型号 二、代码 1. CreateDataset.py 2.Train…...

Java面向对象高级【注解和反射】

目录 注解 什么是注解&#xff1f; 自定义注解 元注解 反射 什么是反射 静态语言和动态语言 动态语言 静态语言 对比 Class类 Java内存分析 类加载过程 类加载器 获取运行时类的完整结构 通过Class对象实例化对象 1.调用Class对象的newInstance 2.Constructor…...

Pytorch基础 - 4. torch.expand() 和 torch.repeat()

目录 1. torch.expand(*sizes) 2. torch.repeat(*sizes) 3. 两者内存占用的区别 在PyTorch中有两个函数可以用来扩展某一维度的张量&#xff0c;即 torch.expand() 和 torch.repeat() 1. torch.expand(*sizes) 【含义】将输入张量在大小为1的维度上进行拓展&#xff0c;…...

变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析

一、变量声明设计&#xff1a;let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性&#xff0c;这种设计体现了语言的核心哲学。以下是深度解析&#xff1a; 1.1 设计理念剖析 安全优先原则&#xff1a;默认不可变强制开发者明确声明意图 let x 5; …...

React19源码系列之 事件插件系统

事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...

C++ 基础特性深度解析

目录 引言 一、命名空间&#xff08;namespace&#xff09; C 中的命名空间​ 与 C 语言的对比​ 二、缺省参数​ C 中的缺省参数​ 与 C 语言的对比​ 三、引用&#xff08;reference&#xff09;​ C 中的引用​ 与 C 语言的对比​ 四、inline&#xff08;内联函数…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互

引擎版本&#xff1a; 3.8.1 语言&#xff1a; JavaScript/TypeScript、C、Java 环境&#xff1a;Window 参考&#xff1a;Java原生反射机制 您好&#xff0c;我是鹤九日&#xff01; 回顾 在上篇文章中&#xff1a;CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。

1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj&#xff0c;再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...

企业如何增强终端安全?

在数字化转型加速的今天&#xff0c;企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机&#xff0c;到工厂里的物联网设备、智能传感器&#xff0c;这些终端构成了企业与外部世界连接的 “神经末梢”。然而&#xff0c;随着远程办公的常态化和设备接入的爆炸式…...

JVM 内存结构 详解

内存结构 运行时数据区&#xff1a; Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器&#xff1a; ​ 线程私有&#xff0c;程序控制流的指示器&#xff0c;分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 ​ 每个线程都有一个程序计数…...

人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式

今天是关于AI如何在教学中增强学生的学习体验&#xff0c;我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育&#xff0c;这并非炒作&#xff0c;而是已经发生的巨大变革。教育机构和教育者不能忽视它&#xff0c;试图简单地禁止学生使…...

并发编程 - go版

1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程&#xff0c;系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...