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

C语言进阶——自定义类型:结构体

🌇个人主页:_麦麦_

📚今日名言:生活不可能像你想象的那么好,也不会像你想象的那么糟。——莫泊桑《羊脂球》

目录

一、前言

二、正文

1结构体

1.1结构体的基础知识

1.2结构的声明

1.3特殊的声明

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

1.5结构的自引用 

 1.6结构体内存对齐

1.7修改默认对齐数

1.8结构体传参

2.位段

2.1什么是位段

 2.2位段的内存分配

 2.3位段的跨平台问题

2.4位段的应用 

三、结语 


一、前言

        好久不见,今天为小伙伴们带来C语言中有关结构体的详细知识,干货满满,图文并茂一定要看到底哦!

二、正文

1结构体

1.1结构体的基础知识

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

1.2结构的声明

struct   tag

{

        member—list;

} ; 

注:分号不能丢

//结构的声明演示:描述一个学生
struct Stu
{char name[20];	//名字char sex[5];	//性别int age;		//年龄char id[20];	//学号
};

1.3特殊的声明

        在声明结构的时候,可以不完全声明【匿名结构体类型】,由于匿名,所以声明后就得在后面直接创建变量。

//匿名结构体类型
struct
{int a;char b;float c;
}x;	//创建变量x

 注:哪怕两个匿名结构体中的内容完全一样也会被编译器当成两个完全不同的类型

//匿名结构体类型1
struct
{int a;char b;float c;
}x;	//匿名结构体类型2
struct
{int a;char b;float c;
}*p;//非法操作
p = &x;

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

        结构体变量的定义共分为三类:

①全局变量定义

②局部变量定义

③在结构的声明的同时定义变量

//结构体变量的三种定义方法
struct Stu
{char name[20];	//名字char sex[5];	//性别int age;		//年龄char id[20];	//学号
}Stu1;				//声明结构的同时定义变量struct Stu Stu2;	//全局变量定义int main()
{struct Stu Stu3;	//局部变量定义return 0;
}

        结构体变量的初始化即定义变量的同时赋初值。不过结构体的初始化也分为正常的初始化嵌套初始化

//结构体变量的初始化
struct Peo
{char name[20];	//名字char sex[5];	//性别int age;		//年龄
};struct Peo	Peo1 = { "陈书婷","女",35};	//结构体正常初始化struct Node
{char movie[20];struct Peo	p;
}Peo2 = { "狂飙",{"高志强","男",40 }};	//结构体嵌套初始化struct Node Peo3 = { "狂飙",{"高启盛","男","28"}};	//结构体嵌套初始化

 注:结构体的初始化其实可以更加灵活——乱序,按照自己想法来初始化。

struct Peo
{char name[20];	//名字char sex[5];	//性别int age;		//年龄
};struct Peo Peo4 = { .sex = "男",.name = "安欣",.age = 30 };

1.5结构的自引用 

        采取指针的形式

在结构中包含一个类型为该结构本身的成员

//自引用1(错误示范)
struct Node
{int date;struct Node next;
};//自引用2(正确示范)
struct Node
{int date;struct Node* next;
};

 

1.6结构体内存对齐

        在小伙伴们掌握了结构体的基本使用之后,接下来让我们深入讨论一个问题:如何计算结构体的大小?为了解决这个问题,我们就必须掌握结构体的对齐规则。

● 第一个成员在与结构体变量偏移量为0的地址处

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

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

        ★VS中默认的值为8

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

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

#include<stdio.h>
//结构体内存对齐代码1
struct s1
{double d;	//对齐数8char c;		//对齐数1int i;		//对齐数4
};//结构体内存对齐代码2
struct s2
{char c1;		//对齐数1struct s1 s1;	//对齐数8double d;		//对齐数8
};int main()
{printf("%d", sizeof(struct s1));	//16printf("%d", sizeof(struct s2));	//32return 0;
}

         结构体内存对齐代码1:首先我们将"d"成员放入内存中,由于它是第一个成员,且对齐数为8,所以从偏移量为0的地址一直放到偏移量为7的地址。其次是"c"成员,它的对齐数为1,因此可以放在偏移量为8的地址处,最后是"i"成员,它的对齐数是4,但是偏移量为9的地址并不是对齐数4的倍数,所以我们只好跳到偏移量为12的地址处,直至偏移量为15的地址处才放下"i"成员。在将所有的成员放入后,就是计算结构体s1的大小,由于15并不是最大对齐数8的倍数,所以结构体s1的大小为16个字节。

        结构体内存对齐代码2:首先我们将"c1"成员放入内存中,由于它是第一个成员,且对齐数为1,所以放入偏移量为0的地址处。其次是"s1"成员,该嵌套结构体内的最大对齐数为8,因此跳到偏移量为8的地址处开始存放double类型成员,直至偏移量为15的地址处才存放完毕。接着是char类型成员,对齐数为1,存放在偏移量为16的地址处。继而是整型成员,对齐数为4,从偏移量为20的地址存放,直至偏移量为23的地址处存放完毕。最后是"d"成员对齐数为8,从偏移量为24的地址存放,直至偏移量为31的地址处存放结束。在将所有的成员放入后,就是计算结构体s2的大小,由于31并不是最大对齐数8的倍数,所以结构体s2的大小为32个字节

        最后总结一下计算结构体大小的步骤

计算出所有成员的对齐数并得出结构体的最大对齐数

根据每个成员的对齐数依次存放每个成员

所有成员存放完毕后,依据结构体的最大对齐数得出结构体大小

        

        那么如何证明我们对结构体成员的偏移量计算是否正确呢?C语言中提供了一个宏来计算结构体成员的偏移量?(无需掌握,只是证明我们上述计算的思路无误)

#include<stddef.h>
int main()
{printf("%d\n", offsetof(struct s1,d));printf("%d\n", offsetof(struct s1,c));printf("%d\n", offsetof(struct s1,i));printf("%d\n", offsetof(struct s2, c1));printf("%d\n", offsetof(struct s2, s1.d));printf("%d\n", offsetof(struct s2, s1.c));printf("%d\n", offsetof(struct s2, s1.i));printf("%d\n", offsetof(struct s2, d));return 0;
}

        在了解完结构体的内存,可能有的小伙伴会发出如下的疑问:为什么会存在内存对齐呢,这到底有什么用呢?

大部分的参考资料都是如是说的:

1.平台原因(移植原因):

        不是所有的硬件平台都能任意访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些 特定类型的数据,否则就会抛出硬件异常

2.性能原因:

        数据结构(尤其是栈)应该尽可能地在自然边界对齐。

        原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

总的来说:结构体的内存对齐是拿空间来换取时间的做法

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

占用空间小的成员尽量集中在一起

1.7修改默认对齐数

        那么默认对齐数可以修改吗?答案是肯定的。在C语言中存在#pragma这个预处理指令,通过这个我们就可以改变默认对齐数了。

//修改默认对齐数
#include <stdio.h>#pragma pack(2)
struct s1
{char c1;int i;char c2;
};

 注:如果将默认对齐数设置为1,则不存在对齐效果。在平常的使用中,小伙伴们一定要根据实际需求修改默认对齐数

1.8结构体传参

         在之前的指针学习中我们了解到了"传值调用"和"传址调用"这两个概念,那么在结构体传参时依旧存在以上两种方式,那么那种方式是最优选择呢?

//结构体传参
#include <stdio.h>struct s
{int date[1000];int num;
};struct s s1 = { {1,2,3,4},666 };void print1(struct s s1)
{printf("%d",s1.num);
}void print2(struct s* s1)
{printf("%d", s1->num);
}
int main()
{print1(s1);		//传结构体print2(&s1);	//传地址
}

        其实,在结构体传参这一步中传结构体的地址是一个更好的选择。因为函数传参的时候,参数是需要压栈的,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降。

2.位段

        在结构体讲完之后就要向小伙伴们介绍结构体实现位段的能力

2.1什么是位段

位段的声明和结构是类似的,却能更加的节省空间,但是有两个地方存在差异:

①位段的成员必须是int、unsigned int 或 signed int

②位段的成员名后边有一个冒号和数字【表示占几个二进制位】

#include <stdio.h>
struct A
{int _a : 2;		//a只占2个二进制位int _b : 5;		//b只占5个二进制位int _c : 10;	//c只占10个二进制位int _d : 30;	//d只占30个进制位
};int main()
{printf("%d\n", sizeof(struct A));	//打印为8个字节,是不是更节省空间了呢return 0;
}

 

2.2位段的内存分配

1.位段的成员可以是int 、unsigned int、signed int、或者是char(属于整形家族)类型

2.位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的

3.位段设计很多不确定因素,位段是不跨平台的,注意可移植的程序应该避免位段

4.位段是不存在内存对齐的

注:在不同的编译器中,同一位段的大小也是不确定的,接下来我们以VS2019的环境下解释上述位段的大小为何为8个字节。

        在理解完位段在VS下的内存开辟,那么内存是如何使用的呢?在VS的环境下,内存开辟后是从右向左使用的且为小端存储,依旧以上面的代码为例:

 

2.3位段的跨平台问题

①int位段被当成有符号数还是无符号数是不确定的

②位段中最大位的数目不能确定(16位机器最大16,32位机器最大32,写成27,在16位机器会出现问题)

③位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义

④当一个结构包含两个位段,第二个位段成员比较大,无法容纳与第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的

总结:跟结构相比,位段可以达到同样的效果,但是可以很好地节省空间,但是有跨平台的问题存在

2.4位段的应用 

        大家都知道彼此之间相互的交流看似简单,其实文字交流的背后是大量的网络数据,而位段的使用恰好可以对数据进行压缩,减少网络的压力和负担。形象的来说,我们可以把网络想象成高速公路,如果上面全是未经压缩的数据,也就都是大卡车的话,就会十分拥挤。而如果采取位段的方式,对没有必要使用的空间进行压缩,就可以将大卡车变成小轿车,从而缓解交通压力。

三、结语 

        关于结构体的讲解就已经全部结束了,下期我们会继续分享自定义类型的其他成员!

        关注我 _麦麦_分享更多干货:_麦麦_的博客_CSDN博客-领域博主
        大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下期见!

 

相关文章:

C语言进阶——自定义类型:结构体

&#x1f307;个人主页&#xff1a;_麦麦_ &#x1f4da;今日名言&#xff1a;生活不可能像你想象的那么好&#xff0c;也不会像你想象的那么糟。——莫泊桑《羊脂球》 目录 一、前言 二、正文 1结构体 1.1结构体的基础知识 1.2结构的声明 1.3特殊的声明 1.4结构体变量的…...

SpringSecurity学习笔记01

目录 一、课程介绍 二、框架概述 三、入门案例 四、基本原理&#xff08;过滤器链&#xff09; 五、基本原理&#xff08;过滤器加载过程&#xff09; 六、基本原理&#xff08;两个重要的接口) 七、web权限方案-用户认证&#xff08;设置用户名密码上&#xff09; 八、…...

Python语言零基础入门教程(十一)

Python 列表(List) 序列是Python中最基本的数据结构。序列中的每个元素都分配一个数字 - 它的位置&#xff0c;或索引&#xff0c;第一个索引是0&#xff0c;第二个索引是1&#xff0c;依此类推。 Python有6个序列的内置类型&#xff0c;但最常见的是列表和元组。 序列都可以…...

现货白银基础知识

任何活动&#xff0c;任何项目&#xff0c;任何工作都离不开基础知识&#xff0c;这是肯定的。万丈高楼平地起&#xff0c;要想要简称百层高楼&#xff0c;首先得把低级打好&#xff01;现货白银投资也是一样的道理&#xff0c;现在我们就来一起聊聊现货白银基础知识的问题&…...

数据库原理及应用基础知识点

数据库原理基础知识点大全数据库原理及应用1、数据库系统概述1.1 基本概念1.2 数据模型1.3 数据库系统的结构2、实体 -- 联系模型2.1 基本概念2.2 实体-联系图2.3 弱实体集3、关系数据模型3.1 关系数据库的结构3.2 从ER模型到关系模型3.3 关系操作、完整性约束、关系代数4、关系…...

【数据结构】栈(stack)

写在前面本篇文章开始讲解栈的有关知识&#xff0c;其实把顺序表和链表学好&#xff0c;那么这一章便不在话下&#xff0c;栈实际上就是顺序表或链表的一些特殊情况。用顺序表实现的栈叫做顺序栈用链表实现的栈叫做链栈文章的内容分为几个部分&#xff0c;希望读者能快速了解文…...

初识shell

文章目录一、shell基本知识1.1为什么学习和使用Shell编程1.2 什么是Shell1.2.1 shell的起源1.2.2 shell的功能1.3 shell的分类1.4 作为程序设计的语言——shell1.5 如何学好shell1.6 shell脚本的基本元素1.7 shell脚本编写规范1.8shell脚本的执行方式1.9 执行脚本的方法1.10 sh…...

程序员如何编写好开发技术文档 如何编写优质的API文档工作

编写技术文档&#xff0c;是令众多开发者望而生畏的任务之一。它本身是一件费时费力才能做好的工作。可是大多数时候&#xff0c;人们却总是想抄抄捷径&#xff0c;这样做的结果往往非常令人遗憾的&#xff0c;因为优质的技术文档是决定你的项目是否引人关注的重要因素。无论开…...

二级C语言操作例题(四十)

一、程序填空题 在此程序中&#xff0c;函数fun的功能是&#xff1a;在形参s所指字符串中寻找与参数c相同的字符&#xff0c;并在其后插入一个与之相同的字符&#xff0c;若找不到相同的字符则不做任何处理。 例如&#xff0c;若s所指字符串”baacda”&#xff0c;中c的字符为…...

vue-router 源码解析(二)-创建路由匹配对象

文章目录基本使用导语createRouterMatcher 创建匹配路由记录addRoute 递归添加matchercreateRouteRecordMatcher 创建matchertokenizePath 解析pathtokensToParser 记录打分insertMatcher 将matcher排序总结基本使用 const routes [{path:"/",component: Demo2,nam…...

分布式新闻项目实战 - 10.Long类型精度丢失问题

怒发冲冠&#xff0c;凭阑处、潇潇雨歇。抬望眼&#xff0c;仰天长啸&#xff0c;壮怀激烈。三十功名尘与土&#xff0c;八千里路云和月。莫等闲、白了少年头&#xff0c;空悲切。 靖康耻&#xff0c;犹未雪。臣子恨&#xff0c;何时灭。驾长车&#xff0c;踏破贺兰山缺。壮志饥…...

如何将本地jar包安装到maven仓库

mvn install:install-file:主要是将本地自定义jar安装到maven仓库&#xff0c;然后在pom中可以直接通过dependency的方式来引用。 此命令有如参数&#xff1a; 命令说明-DgroupId自定义groupId设置groupId 名-DartifactId自定义artifactId设置该包artifactId名-Dversion自定义…...

C++:map和set的认识和简单使用/关联式容器

关联式容器 关联式容器即是用来存储数据的&#xff0c;并且存储的是<Key&#xff0c;Value>结构的键值对&#xff0c;在数据检索时效率比序列式容器高。 序列式容器也就是vector、list、queue等容器&#xff0c;因为其底层为线性序列的数据结构&#xff0c;里面存储的是…...

网络工程师一定要学会的知识点:OSPF,今天给大家详细介绍

1. OSPF 概念OSPF&#xff08;Open Shortest Path First 开放式最短路径优先&#xff09;是一种动态路由协议&#xff0c;属于内部网关协议(Interior Gateway Protocol,简称 IGP)&#xff0c;是基于链路状态算法的路由协议。2. OSPF 的运行原理&#xff08;1&#xff09;OSPF 的…...

企业管理的三大基石及其关系

企业管理的三大基石三大基石是什么三大基石的关系制度&#xff1a;管理&#xff1a;文化&#xff1a;三大基石是什么 一个企业&#xff0c;不管它是属于哪种类型&#xff0c;影响员工行为的都有三种力量——制度、管理和文化&#xff0c;这是管理的三大基石。 三大基石的关系 …...

6个月软件测试培训出来后的感悟 —— 写给正在迷茫是否要转行或去学软件测试的学弟们

本人刚从某培训机构学习结束&#xff0c;现在已经上班一个月了。这篇文章我不会说太多的知识点&#xff0c;或噱人去培训机构学习的话语&#xff0c;仅作为一个普通打工者的身份&#xff0c;来写给那些对于软件测试未来发展、薪资待遇等不清楚的正在为家庭&#xff0c;解决信用…...

IoU Loss综述(IOU,GIOU,CIOU,EIOU,SIOU,WIOU)

边界框回归&#xff08;BBR&#xff09;的损失函数对于目标检测至关重要。它的良好定义将为模型带来显著的性能改进。大多数现有的工作假设训练数据中的样本是高质量的&#xff0c;并侧重于增强BBR损失的拟合能力。 一、L2-norm 最初的基于回归的BBR损失定义为L2-norm&#xf…...

Node=>Express中间件 学习3

1.概念&#xff1a; 例&#xff1a;在处理污水的时候&#xff0c;一般都要经过三个处理环节&#xff0c;从而保证处理过后的废水&#xff0c;达到排放标准 处理污水的这三个中间处理环节&#xff0c;就可以叫中间件 2.中间件调用流程 当一个请求到达Express的服务器之后&#x…...

【STM32笔记】HAL库UART串口配置及重定向(解决接收中断与scanf不能同时工作的问题)

【STM32笔记】HAL库UART串口配置及重定向&#xff08;解决接收中断与scanf不能同时工作的问题&#xff09; 首先 要使用printf和scanf 必不可少的就是 #include <stdio.h>这里需要做的就是配置单片机的UART 并且使其能够被printf和scanf调用 打开异步工作模式 并且选择…...

【前端CSS面试题】2023前端最新版css模块,高频15问

&#x1f973;博 主&#xff1a;初映CY的前说(前端领域) &#x1f31e;个人信条&#xff1a;想要变成得到&#xff0c;中间还有做到&#xff01; &#x1f918;本文核心&#xff1a;博主收集的CSS面试题 目录 一、CSS必备面试题 1.CSS3新特性 2.CSS实现元素两个盒子垂…...

MongoDB学习和应用(高效的非关系型数据库)

一丶 MongoDB简介 对于社交类软件的功能&#xff0c;我们需要对它的功能特点进行分析&#xff1a; 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具&#xff1a; mysql&#xff1a;关系型数据库&am…...

【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密

在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

Bean 作用域有哪些?如何答出技术深度?

导语&#xff1a; Spring 面试绕不开 Bean 的作用域问题&#xff0c;这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开&#xff0c;结合典型面试题及实战场景&#xff0c;帮你厘清重点&#xff0c;打破模板式回答&#xff0c…...

Python 训练营打卡 Day 47

注意力热力图可视化 在day 46代码的基础上&#xff0c;对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...

如何配置一个sql server使得其它用户可以通过excel odbc获取数据

要让其他用户通过 Excel 使用 ODBC 连接到 SQL Server 获取数据&#xff0c;你需要完成以下配置步骤&#xff1a; ✅ 一、在 SQL Server 端配置&#xff08;服务器设置&#xff09; 1. 启用 TCP/IP 协议 打开 “SQL Server 配置管理器”。导航到&#xff1a;SQL Server 网络配…...

二维FDTD算法仿真

二维FDTD算法仿真&#xff0c;并带完全匹配层&#xff0c;输入波形为高斯波、平面波 FDTD_二维/FDTD.zip , 6075 FDTD_二维/FDTD_31.m , 1029 FDTD_二维/FDTD_32.m , 2806 FDTD_二维/FDTD_33.m , 3782 FDTD_二维/FDTD_34.m , 4182 FDTD_二维/FDTD_35.m , 4793...

2025年低延迟业务DDoS防护全攻略:高可用架构与实战方案

一、延迟敏感行业面临的DDoS攻击新挑战 2025年&#xff0c;金融交易、实时竞技游戏、工业物联网等低延迟业务成为DDoS攻击的首要目标。攻击呈现三大特征&#xff1a; AI驱动的自适应攻击&#xff1a;攻击流量模拟真实用户行为&#xff0c;差异率低至0.5%&#xff0c;传统规则引…...

数据结构:泰勒展开式:霍纳法则(Horner‘s Rule)

目录 &#x1f50d; 若用递归计算每一项&#xff0c;会发生什么&#xff1f; Horners Rule&#xff08;霍纳法则&#xff09; 第一步&#xff1a;我们从最原始的泰勒公式出发 第二步&#xff1a;从形式上重新观察展开式 &#x1f31f; 第三步&#xff1a;引出霍纳法则&…...

PydanticAI快速入门示例

参考链接&#xff1a;https://ai.pydantic.dev/#why-use-pydanticai 示例代码 from pydantic_ai import Agent from pydantic_ai.models.openai import OpenAIModel from pydantic_ai.providers.openai import OpenAIProvider# 配置使用阿里云通义千问模型 model OpenAIMode…...

【版本控制】GitHub Desktop 入门教程与开源协作全流程解析

目录 0 引言1 GitHub Desktop 入门教程1.1 安装与基础配置1.2 核心功能使用指南仓库管理日常开发流程分支管理 2 GitHub 开源协作流程详解2.1 Fork & Pull Request 模型2.2 完整协作流程步骤步骤 1: Fork&#xff08;创建个人副本&#xff09;步骤 2: Clone&#xff08;克隆…...