C语言的结构体类型
在我们使用C语言进行编写代码时,常常会使用已经给定的类型来创建变量,比如int型,char型,double型等,而当我们想创建一些较为复杂的东西时,单单用一个类型变量是没办法做到的,比如我们想创建一个学生,学生拥有的属性繁多,有年龄,有姓名,有性别,有成绩等等等等...那么此时我们应该如何创建一个学生类型变量呢?这就涉及到我们今天要学习的新知识,结构体类型。
一、结构体类型的声明
当我们要统计很多同类型数据的时候,我们可以使用数组,而通常定义复杂的类需要多种不同的类型数据,而结构体就是用来存放这些不同类数据的。
struct tag
{member - list;
}variable - list;
这段代码中,tag代表的意思是要创建结构体的名字,member-list代表结构体中的各种成员变量,variable-list可以不写,写的时候可以代表创建结构体类型时顺带创建的结构体变量。
比如此时我们想定义一个学生变量:
struct Stu
{int age;//年龄char name[20];//名字double score;//成绩char sex[5];//性别
};//分号一定不能忘记
二、结构体变量的定义和初始化
① 结构体变量的定义
对于结构体变量的定义,我们可以像上面提到的,在创建结构体类型时直接创建出结构体变量:
struct Stu
{int age;//年龄char name[20];//名字double score;//成绩char sex[5];//性别
}s1;//分号一定不能忘记
这样就成功定义了结构体变量s1,当然除了这种方法,还可以在声明结构体之后再定义:
struct Stu
{int age;//年龄char name[20];//名字double score;//成绩char sex[5];//性别
};
int main()
{struct Stu s1;return 0;
}
② 结构体变量的初始化
而对于结构体变量的初始化,也可以根据上面两种定义方式,分成两种初始化的方法:
在创建结构体类型时直接创建出结构体变量的情况下结构体变量初始化:
struct Stu
{int age;//年龄char name[20];//名字double score;//成绩char sex[5];//性别
}s1 = { 18,"zhangsan",99.5,"男" };
int main()
{printf("%d %s %.1f %s\n", s1.age, s1.name, s1.score, s1.sex);return 0;
}
在声明结构体之后再定义的情况下对结构体变量初始化:
struct Stu
{int age;//年龄char name[20];//名字double score;//成绩char sex[5];//性别
};
int main()
{Stu s1 = { 18,"zhangsan",99.5,"男" };//struct Stu s1 = { 18,"zhangsan",99.5,"男" };//两种方法都是可以的printf("%d %s %.1f %s\n", s1.age, s1.name, s1.score, s1.sex);return 0;
}
(注:如果定义的结构体名和变量名不冲突,那么在定义结构体变量时,可以省略掉struct关键字。)
③ 结构的特殊声明
在声明结构的时候,可以不完全的声明,也就是说在声明结构体时并不为此结构体命名,此声明被称为匿名结构体类型。
struct
{int age;char name[20];double score;char sex[5];
}s1;
struct
{int age;char name[20];double score;char sex[5];
}*sp;
int main()
{sp = &s1;return 0;
}
那么如果我们分别声明两个成员变量类型相同的匿名结构体,一个定义s1结构体变量,另一个定义指针*sp,那么再对s1取地址,&s1与sp会是相等的嘛?答案是,此行为是非法的:
编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的。 匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用一次。
④ 结构的自引用
结构体能够存储各种成员变量,那我们是否能在结构中包含一个类型为该结构本身的成员呢?
正常来说,在结构中包含整形变量就是int a,字符型变量就是char a,那同样的,我们要包含同类型结构变量,是写成struct ...吗?例如:
struct Node
{int date;struct Node next;
}
这样看似是代表了该结构本身成员date,但实际上操作起来,比如我们此时计算sizeof(struct Node)会发生"套娃"的情况,一直循环的进入Node结构体,而每一次进入都多了一个占四字节的date,最后变成无穷大,所以这样写是错误的。
而其实我们想做到的就是找到同类型成员变量,那其实我们直接使用指针去指向下一个变量就好了~所以其实正确的方法很简单,只需要我们取next地址,像这样写就好了:
struct Node
{int date;struct Node* next;
}
而后我们再来了解一下typedef的用法,它的作用很多,主要作用于:
① 定义一种类型的别名,而不只是简单的宏替换。可以用作同时声明指针型的多个对象。
意思就是说,我们正常来定义整形变量,就是int num,而我们可以通过typedef来创建一个独特的新词来代表int,比如此时我们输入,typedef int Int;那么此时我们再写入int a和INT a,其实是一样的。(需要注意的是后面那句话!!!可以用作同时声明指针型的多个对象)
多说无益,我们来看一道例题:
#define INT_PTR int*
typedef int* int_ptr;
INT_PTR i, j;
int_ptr x, y;
在上面代码中,i,j,x,y四个常量的数组类型哪一个是int数据类型
答案为j是int型,i,x,y这个常量为int*类型,为什么?
在#define这个宏,在预处理阶段将会转化为int* i , j;而typedef不会出现这种情况,新定义的名称所代表的类型是什么,对应创造的变量就是什么。
② 声明struct新对象时可以使用。
我们来看这段代码是否可行呢?
typedef struct{int data;Node* next;}Node;
答案是不行的,因为Node是对前面的匿名结构体类型的重命名产生的,但是在匿名结构体内部提前使用Node类型来创建成员变量,这是不行的。解决方案:
typedef struct Node{int data;struct Node* next;}Node;
三、结构体的内存对齐
结构体内存对齐非常重要!!!故需要讲解的内容也较多!!!所以我单独分出一篇文章来对结构体的内存对齐进行透彻深入的讲解,需要的小伙伴们请进入这个博客进行进一步学习~
⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇
(C语言结构体内存对齐-CSDN博客)
⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆
① 结构体内存对齐的规则
结构体中存放的数据各种各样,它们的存储是否能做到在内存中紧密排列呢?又或者说,结构体的内存应该怎样去计算呢?让我们来举个例子:
struct Stu1 {int age;char name;int id;char sex;
};
struct Stu2 {char sex;char name;int age;int id;
};
int main()
{int num1 = sizeof(struct Stu1);int num2 = sizeof(struct Stu2);printf("num1长度为:%d\nnum2长度为:%d\n", num1, num2);return 0;
}
如果按照之前计算整型数组和字符数组大小的常规思路来判断,这两个结构体的大小应该是相等的,一个int型变量占4个字节,一个char型变量占1个字节,那么Stu1有两个int型变量和两个char型变量,大小理应为4+4+1+1=10,同样的Stu2也应为10。那让我们将代码运行一下看看是不是这样的:欸?奇怪了,不仅两个结构体的大小不为10,甚至两个结构体的大小都不相等,这是怎么一回事呢?其实结构体Stu1在内存中的存址形式是这样的:
为什么会是这样的存储形式呢?这就是结构体内存对齐所造成的。
结构体内存对齐的规则:
① 结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处。
② 其他成员变量要对齐一个叫"对齐数"的数字的整倍数的地址。
(对齐数的概念)
* 在不同的编译器中,默认的对齐数也有所不同,Visual Studio Code的编译器的对齐数是8,Linux的编译器的对齐数是4。
* 在结构体存址时,对齐数取(编译器默认的对齐数)和(该成员变量大小)中较小的数。
③ 结构体所占内存大小等于最大对齐数(结构体中每个成员变量都有一个对齐数,各成员变量中最大的对齐数)的整数倍。
④ 如果结构体有嵌套,那么嵌套的结构体存储在自己成员的最大对齐数的整数倍地址处,嵌套结构体大小为所有成员(包括嵌套的结构体的成员)的最大对齐数的整数倍。
② 结构体内存对齐运算
比如此时我们创建一个结构体变量:
struct Stu1 {char name;int id;char sex;
};
用图片来表示:我们发现结构体的大小一下子从1变成了8,并且其中出现了三个浪费的空间,那么造成这种情况的原因是什么呢?让我们来一起计算一下它现在的对齐数:当我们只存放一个char型变量name时,对齐数为1,所以此时内存大小仅仅为1。但当我们存入int型变量id时,对齐数变成4,所以结果的内存大小必须被4整除,故浪费三个空间,使内存大小变成8。
最后也浪费三个空间,与上面是同理的。
四、结构体实现位段
① 位段的定义与初始化
既然讲了结构体,就不得不提到结构体的位段了。那么,什么是结构体的位段呢?有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。为了充分利用好内存空间,就出现了位段~
位段的声明:
① 位段的成员必须是int,unsigned int,或signed int,在C99中位段成员的类型也可以选择其他类型。
② 段位的成员名后面有一个冒号和一个数字。
③ 后面的数字用来限定成员变量占用的位数。
让我们来用代码来进行一下举例:
struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};
这就是位段的初始化,四个成员变量是int型,但使用位段将它们的内存大小进行了定义。而这种内存的定义应该从何体现呢?我们对着这段代码进行一些增加:
struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};
int main()
{struct A arr = { 1,15,511,536870911 };printf("%d\n%d\n%d\n%d\n", arr._a, arr._b, arr._c, arr._d);return 0;
}
此时我们定义的四个成员变量,所占用的内存分别为2bit,5bit,10bit,30bit,相应的,因为int型变量是有正负之分的,所以还需要有一个符号位,那么此时真正储存大小的bit位就分别是1,4,9,29。而此时我输入的1,15,511,536870911恰好就是除符号位以外其他位全部为1的情况~所以此情况是能够正常输出的:现在我们将这四个数全部都加一,就会变成:
这是因为此时所有位进1,使符号位从0变成了1,所以数字都变成了负数。而符号位一般都是最后一位,这也就证明初始化位段,冒号后的数字是bit个数~
那么位段A所占的内存大小又是多少呢?
struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};
int main()
{struct A arr = {};printf("%d\n",sizeof(arr));return 0;
}
答案是:8。那么这是为什么呢?接下来让我们学习一下位段的内存分配。
② 位段的内存分配
Ⅰ 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
Ⅱ 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
Ⅲ 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
让我们看一段代码,结构体中存放了四个char型变量,并且都通过位段改变了内存大小,那么这个结构体的大小是多少呢?
struct S
{char a : 3;char b : 4;char c : 5;char d : 4;
};
int main()
{struct S s = {};s.a = 10;s.b = 12;s.c = 3;s.d = 4;printf("%d", sizeof(s));return 0;
}
答案是3。那么接下来让我们通过这张图来深入的了解一下结构体内存分配:这就是结构体S的大小为3的原因啦~
接下来再让我们对数据s.a,s.b,s.c,s.d四个成员变量所占用的地址进行一下分析:最后得到的地址是:62 03 04 xx。结果是否是这样呢?让我们证实一下~
没错~我们的答案是正确的。使用位段能够有效的节省大量不必要的空间,而需要注意的是:地址的存储是从右往左使用的,并且当一个字节中剩余空间不足时,会浪费一段空间,在新的空间中存储,所以使用位段时也需要精心计算,否则或许反而会使占用内存变多!
然而,位段的好处如此多,也就相应的证明它也有坏处!!!
③ 位段的跨平台问题
①. int 位段被当成有符号数还是无符号数是不确定的。
②. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会 出问题)
③. 位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义。
④. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
总结:
跟结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。
那么关于结构体的相关知识,这次就为大家分享到这里啦~值得关注的是,结构体属于自定义类型中的一种,而自定义类型还有联合与枚举,而联合与枚举的相关知识就要留到下一篇文章给大家讲解啦~还请大家多多期待,如果这篇文章有讲得不细致的地方,或者有出现错误的地方,还请大家多多指出~我也会虚心学习的!那么我们下一篇再见啦~
相关文章:

C语言的结构体类型
在我们使用C语言进行编写代码时,常常会使用已经给定的类型来创建变量,比如int型,char型,double型等,而当我们想创建一些较为复杂的东西时,单单用一个类型变量是没办法做到的,比如我们想创建一个…...
illustrator 收集字体插件VBscript
这是早些年从俄罗斯网站上看到的一个收集字体插件,语言是用VBscript写的,能用,但个别字体不能收集完成,现在Adobe也在illustrator中加入了收集字体打包功能,所以这个也很少用啦。 使用方法: 下好插件,或把下面的代码存入到本地侯后缀名改为.vbs,然后把.ai文件往.vbs文…...

【LLM多模态】文生视频评测基准VBench
note VBench的16个维度自动化评估指标代码实践(待完成)16个维度的prompt举例人类偏好标注:计算VBench评估结果与人类偏好之间的相关性、用于DPO微调 文章目录 note一、相关背景二、VBench评测基准概述:论文如何解决这个问题&…...

通过覆写 url_for 将 flask 应用部署到子目录下
0. 缘起 最近用 flask 写了一个 web 应用,需要部署到服务器上。而服务器主域名已经被使用了,只能给主域名加个子目录进行部署,比如主域名 example.org ,我需要在 example.org/flask 下部署。这时 flask 应用里的内部连接们就出现…...

攻防世界---->埃尔隆德32
做题笔记。 下载 查壳。 32ida 打开。 发现就一个判断。 跟进看看。 // 首次a20 int __cdecl sub_8048414(_BYTE *a1, int a2) {int result; // eaxswitch ( a2 ){case 0:if ( *a1 105 )goto LABEL_19;result 0;break;case 1:if ( *a1 101 ) // e…...

redis短信登录模型
基于Session实现登录 ,...

【React】React18.2.0核心源码解读
前言 本文使用 React18.2.0 的源码,如果想回退到某一版本执行git checkout tags/v18.2.0即可。如果打开源码发现js文件报ts类型错误请看本人另一篇文章:VsCode查看React源码全是类型报错如何解决。 阅读源码的过程: 下载源码 观察 package…...

深度学习-目标检测(四)-Faster R-CNN
目录 一.模型框架 二:步骤详细 1.conv layers 2.RPN 3.anchors 4.cls layer分类 5.reg layer回归 6.Proprosal 7.Rol pooling 8.Classification 三.训练 1.训练RPN网络 2.全连接层部分训练: 都看到这里了,点个赞把!&a…...
MATLAB中的无线通信系统设计有哪些最佳实践
在无线通信系统设计领域,MATLAB提供了一套强大的工具箱,使得系统设计、仿真、测试和分析变得更加高效和精确。本文将探讨MATLAB在无线通信系统设计中的最佳实践,包括信号处理、调制与解调、信道建模、误码率分析以及无线通信标准的实现。 1.…...

Java的发展史与前景
🌈个人主页:Yui_ 🌈Linux专栏:Linux 🌈C语言笔记专栏:C语言笔记 🌈数据结构专栏:数据结构 🌈C专栏:C 文章目录 0. Java语言的发展史1.概述1.1 什么是Java1.2 …...

2024年上海小学生古诗文大会倒计时30多天:做几道今年的官方模拟题
2024年上海市小学生古诗文大会自由报名活动的初赛日期于10月19日开始,距离今天只有34天了。 小学生古诗文大会考什么?怎么考呢?今天好真题就带着大家来做一做官方发布的2024年小学生古诗文大会的模拟题,根据往年的经验࿰…...

IDEA 常用配置和开发插件
件市场中搜索并安装“Git Integration”插件。 一、前言 在本篇文章中我会为大家总结一些我自己常用的配置和开发插件,此外也给大家提供一个建议,可以根据自己的项目需求和个人偏好选择适合的插件。另外,IDEA 也在不断更新,可能会…...

还在为企微联系人烦恼?一招解决!企业微信2024年效率升级全攻略
现在信息多得让人眼花,微信里头那些企业微信的联系人是不是让你头疼? 看着满屏的绿色头像,心里想:“我就想和朋友聊聊天,怎么就这么难?”别急,今天教你个办法,轻松搞定这些小烦恼&am…...
【docker npm】npm 私库
1.部署环境 window 11 x64Docker Desktop 4.34.1 (166053) Docker Engine v27.2.0 1.1.Docker 镜像源 1.1.1.Docker Engine 配置 {"builder": {"features": {"buildkit": true},"gc": {"defaultKeepStorage": "32…...

完整gpt应用(自用)
qrc.py 把gpt_qrc.qrc转化成gpt_qrc.py pyrcc5 -o icons_rc.py icons.qrc <RCC><qresource prefix"img"><file>img/53.png</file><file>img/ai.png</file><file>img/关闭.png</file><file>img/最小化.png&l…...

【信息论基础第二讲】离散信源的数学模型及其信息测度包括信源的分类、信源的数学模型、离散信源的信息测度、二元信源的条件熵联合熵
一、信源的分类 二、信源的数学模型 1、信源的概念 在通信系统中,收信者在未收到信息以前,对信源发出什么消息是不确定的、随机的、因此我们可以用随机变量、随机序列或者随机过程来描述信源的输出。严格地说,用概率空间来描述信源输出。 …...
在 Spring Boot 项目中连接 IBM AS/400 数据库——详细案例教程
文章目录 1. 添加 jt400 依赖2. 下载 jt400 驱动包依赖下载手动下载下载地址:手动下载 JAR 的步骤: 3. 配置 application.properties 或 application.yml(1)application.properties(2)application.yml 4. 数…...

VUE + NODE 历史版本安装
以node 12.20.0为例子,想下载哪个版本,后面写哪个版本 https://registry.npmmirror.com/binary.html?pathnode/v12.20.0/ 安装国内镜像7.1.0 cnpm npm install -g cnpm7.1.0 -g --registryhttps://registry.npmmirror.com 安装vue脚手架4.5.15 cnpm …...
git reset 几点疑问
疑问:使用 git reset --hard <commit-hash-from-branch-B> 将工作区状态reset为其他branch的某点。 如果当前工作区的分支(比如 branch A)上使用 git reset --hard 将其状态重置为另一个分支(比如 branch B)的某…...

Rust Windows下编译 静态链接VCRuntime140.dll
Rust 编译出来的exe默认动态链接VC运行库,分发电脑上需要安装有Microsoft Visual C Redistributable for Visual Studio 2015运行库。 编译时能静态链接进去,就省去客户端未安装运行库的问题。方法如下: 只需在当前根目录下新建.cargo\config.toml&#…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
JVM垃圾回收机制全解析
Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...

现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...

如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...