当前位置: 首页 > 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;…...

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

【Java学习笔记】Arrays类

Arrays 类 1. 导入包&#xff1a;import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序&#xff08;自然排序和定制排序&#xff09;Arrays.binarySearch()通过二分搜索法进行查找&#xff08;前提&#xff1a;数组是…...

连锁超市冷库节能解决方案:如何实现超市降本增效

在连锁超市冷库运营中&#xff0c;高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术&#xff0c;实现年省电费15%-60%&#xff0c;且不改动原有装备、安装快捷、…...

【机器视觉】单目测距——运动结构恢复

ps&#xff1a;图是随便找的&#xff0c;为了凑个封面 前言 在前面对光流法进行进一步改进&#xff0c;希望将2D光流推广至3D场景流时&#xff0c;发现2D转3D过程中存在尺度歧义问题&#xff0c;需要补全摄像头拍摄图像中缺失的深度信息&#xff0c;否则解空间不收敛&#xf…...

Python爬虫(二):爬虫完整流程

爬虫完整流程详解&#xff08;7大核心步骤实战技巧&#xff09; 一、爬虫完整工作流程 以下是爬虫开发的完整流程&#xff0c;我将结合具体技术点和实战经验展开说明&#xff1a; 1. 目标分析与前期准备 网站技术分析&#xff1a; 使用浏览器开发者工具&#xff08;F12&…...

相机从app启动流程

一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...

【配置 YOLOX 用于按目录分类的图片数据集】

现在的图标点选越来越多&#xff0c;如何一步解决&#xff0c;采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集&#xff08;每个目录代表一个类别&#xff0c;目录下是该类别的所有图片&#xff09;&#xff0c;你需要进行以下配置步骤&#x…...

k8s业务程序联调工具-KtConnect

概述 原理 工具作用是建立了一个从本地到集群的单向VPN&#xff0c;根据VPN原理&#xff0c;打通两个内网必然需要借助一个公共中继节点&#xff0c;ktconnect工具巧妙的利用k8s原生的portforward能力&#xff0c;简化了建立连接的过程&#xff0c;apiserver间接起到了中继节…...

实现弹窗随键盘上移居中

实现弹窗随键盘上移的核心思路 在Android中&#xff0c;可以通过监听键盘的显示和隐藏事件&#xff0c;动态调整弹窗的位置。关键点在于获取键盘高度&#xff0c;并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版

7种色调职场工作汇报PPT&#xff0c;橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版&#xff1a;职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...