结构体专题详解

news2024/11/29 9:57:36

目录

    🥎什么是结构体?

    ⚾结构体的声明

    🏀简单结构体的声明

    🏐结构体的特殊声明

    🏈结构体嵌套问题

    🏉结构体的自引用

    🎳结构体的内存大小

    🥌结构体的内存对齐 

    ⛳内存对齐的优点


    ⚽还记得之前我们讲过的结构体吗?当时我们只是简单的认识了一下结构体应该如何书写以及到底应该怎样传参。今天,我们就来详细介绍一下我们的结构体。话不多说,就让我们开始我们今天博客的主要内容吧!

    🥎什么是结构体?

    ⚽首先我们来认识一下什么是结构体呢?说到结构体就不得不和我们的数组进行类比了。所谓的结构体就是:结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。确实结构体的概念和我们数组的概念很像,但是有一点需要我们特别注意:数组是一组相同的数据的集合体,但是我们的结构体是一组不同数据的集合。举一个简单的例子来说:数组就像是一个肠道很脆弱的宝宝,这个宝宝只能喝粥,其他什么也吃不下。所以我们只能喂这个小宝宝喝粥。我们的数组中的元素类型都取决于前面的变量类型所决定,一旦决定之后我们的数组终究只能放同一种元素。而我们的数组就像是身体状况的恢复健康的青年人,可以吃的下任何东西。同样的道理我们的结构体当中可以存放不同类型的元素。所以使用结构体进行描述我们生活中具有多中特点的数据比较容易。那么接下来就让我们 看一看结构体的书写方式吧!

    ⚾结构体的声明

    🏀简单结构体的声明

    ⚽那么我们就通过一个简单的例子来认识一下结构体的书写形式:

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

    ⚽我们的学生就像是我们所说的那样,并不是一个简单的统一元素所能进行定义的。具有名字,年龄,性别等特点,所以我们需要将我们的学生的信息构建成为一个结构体的形式。在结构体中,我们就像是构建变量的方式进行构建一个 char 类型的数组,以及 int 类型的变量等来丰富这个结构体的特点。那我们已经向大家说明了结构体的类型,那么我们接下来再来向大家介绍一下这样的结构体详细的使用方法。

    ⚽结构体的使用方法:

#include<stdio.h>

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

//也可以在这里创建结构体变量
//在任何函数之外创建的结构体变量为全局变量,什么时候都可以使用
//struct Stu s1 = { "张三",18,"男","A123456" };

int main()
{
	struct Stu s1 = { "张三",18,"男","A123456" };//创建具体的结构体变量
	//假设我们想打印结构体我们所创建的元素
	printf("%s %d %s %s", s1.name, s1.age, s1.sex, s1.id);
	return 0;
}

     ⚽就像是我们上面的代码所运行的效果所表示的那样:我们可以创建全局的结构体变量也可以创建部分函数内部的结构体变量。(我们刚开始创建的只是结构体的类型,系统不会为其开辟内存,想要使用必须重新创建结构体变量)同样的我们还可以在结构体的类型创建好的时候再大括号的后面直接紧跟着创建一个结构体变量。

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

//在结构体类型创建好的时候紧跟着也可以创建结构体变量。
//在这里创建的结构体变量同样为全局变量

    ⚽就像是我们上面的例子一样。我们在结构体类型创建好时创建的结构体变量同样为全局变量,在任何地方都可以使用。上面的结构体变量在创建好之后我们要想使用只需要利用我们能的变量名即 s1 之后再通过 . 操作符找到我们结构体中所指向的变量的元素即可。

    🏐结构体的特殊声明

    ⚽接着要介绍的就是我们结构体的特殊声明。我们先通过例子有一个初步的了解。

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

    ⚽像是上面的声明结构体的方式相信大家都发现了和我们普通结构体不一样的地方:那就是没有结构体的标签。没错,我们要介绍的特殊的结构体的声明方式就是我们的匿名结构体。就像是我们上面的例子一样,我们的匿名结构体在创建结构体类型没有结构体的标签,这就意味着,我们之后不能通过结构体的标签找到我们这次创建的结构类型进行重新添加结构体变量。所以我们这样创建的结构体只能在创建好结构体类型的时候就添加上结构体变量,只能进行有限次使用。之后就会被废弃,其使用的方法和我们正常的结构体类型一样。所以当我们想要创建的一个只能使用一次的结构体变量的时候我们就能使用匿名结构体进行编写我们的程序。

    🏈结构体嵌套问题

    ⚽要是经常阅读代码的小伙伴们一定会遇到像是结构体的嵌套的问题。所谓的结构体的嵌套举一个简单的例子来说就是:我们的学校同样是一个复杂的对象,所以我们想通过结构体来构建一个School的结构体类型,我们的School结构体类型当中肯定会包含学生等信息。那么这又是一个复杂的对象我们需要重新创建一个Stu的结构体类型。像这样我们在School的结构体类型中包含了另一个结构体的形式就叫做结构体的嵌套。那么我们利用代码来深度认识一下结构体的嵌套应该怎么表示。

#include<stdio.h>

struct Student
{
	int age;
	char name[20];
};

struct Teacher
{
	int age;
	char name[20];
};

//结构体的嵌套问题
struct School
{
	int teacherNumber;
	int studentNumber;
	struct Teacher t1;  //引用外部结构体
	struct Student s0;  //引用外部结构体
};
int main()
{
	struct School s1 = { 53,1380,{34,"王美丽"},{14,"李明"} };
	printf("%d\n%d\n%d\n%s\n%d\n%s", s1.teacherNumber,//53
								s1.studentNumber,//1380
								s1.t1.age,//34
								s1.t1.name,//王美丽
								s1.s0.age,//14
								s1.s0.name);
	return 0;
}

     ⚽在我们使用结构体的嵌套的时候我们需要将我们内部的结构体创建成一个结构体变量的形式,以便于我们可以通过 . 操作符进行查找指定元素。

    🏉结构体的自引用

    ⚽我们在上面刚讲完结构体的嵌套问题相信有的小伙伴们可能会有一个疑问:在结构中包含一个类型为该结构本身的成员是否可以呢?回答是当然可以。但是这其中有一个小小的坑——我们的结构体的自引用的形式还是像是我们结构体的嵌套那样直接在结构体的内部创建一个自己的结构体变量吗?我们可以通过一张图片梳理一下思路:

    ⚽我们会发现的是我们要是利用和我们结构体进行自嵌套的话我们的结构体就会出现死递归的情况,这样的话我们的程序就会出现问题。那么我们的结构体究竟应该怎样进行自引用呢?我先来给大家介绍一下数据结构中的一个概念——链表。链表的设计就和我们的结构体的设计如出一辙。

    ⚽在我们的链表中我们每一个想要存储的数据都分为两部分:1.数据部分   2.下一个元素的地址   我们可以通过下一个元素的地址找到我们下一个元素。我们在链表中每一个节点的设计就是利用了我们结构体的自引用。让我们来想象一下:要是我们在我们结构体中想要找到并引用自己本身,那么只需要包含我们的这个结构体的指针即可。我们之后就可以通过这个指针的解引用等操作进行再次使用我们这个结构体。 

    ⚽这样的话要是我们就可以通过自主进行控制函数的使用次数了。是不是很神奇?我们再来通过一段简单的代码进行更加深度的认识:

#include<stdio.h>

struct Node
{
	char elem1;
	int elem2;
	struct Node* s1;
};

int main()
{
	struct Node test1 = { 'a',12 };
	struct Node test2 = { 'b',18 };
	test1.s1 = &test2;
	printf("%c %d %c %d", test1.elem1,
		test1.elem2,
		test1.s1->elem1,
		test1.s1->elem2);
	return 0;
}

 

    ⚽就像我们上面的代码所展示的那样我们的结构体在自引用的时候需要创建至少两个结构体变量,并将其中一个变量的指针交给我们的结构体里另一个结构体变量中的指针,之后我们就可以通过 -> 指针查找的方式找到相应的元素内容啦!

    🎳结构体的内存大小

    ⚽为了让我们大家更加详细的了解我们结构体的执行原理,所以我们在这里补充一部分的内容——结构体的内存大小的判断方法。首先我们通过一个现象进行一步步的分析。

    ⚽看到我们得到的内存的大小肯定小伙伴们会有很多的疑问。首先我们的结构体中有三个元素类型,按道理来说总和大小应该为6个字节才对,为什么打印出来的结构体的大小没有一个是6呢?其次,我们所创建的两个结构体中的元素都是一样的但是为什么两个结构体的大小却不相同呢?为了解决我们的疑惑我先来给大家介绍一点:在结构体存储的时候存在内存对其。

    🥌结构体的内存对齐 

    ⚽那么什么是结构体的内存对齐呢?我们先来介绍一下内存对齐的规律:

            1. 第一个成员在与结构体变量偏移量为0的地址处。
            2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
                 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
                 VS中默认的值为8
            3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
            4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体                  的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

    ⚽上面的四点就是我们结构体内存存储的重要规律。我们一一来解释:我们的结构体在内存中存储的时候会开辟一块空间,这个空间的第一个地址我们把它叫做我们结构体的0偏移量处。

    ⚽因此我们的结构体总是从0偏移处开始存储的。第二点就是其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。在我们的VS中默认的对齐数为8。举一个简单的例子来说我们上面创建的第一个结构体中第一个元素为char 类型所以占一个字节,即我们的0偏移的位置,下一个元素为 int 类型所占大小就为 4 个字节,所以我们的 int 的对齐数就为 4 和我们的默认对其数进行比较:4<8 所以我们取较小值,就是4,所以我们的第二个 int 类型的元素就要对齐到对齐数为 4 的倍数的位置处。(像是4,8,12.....)最后一个 char 类型的元素同理(所占字节大小为1,1<8,所以对齐数就为1,需要对齐到偏移量为一的倍数的位置处)。也就是我们下面的情况:

    ⚽但是要是我们会发现这也不对呀?这么算不是也只有10个字节的大小吗?为什么我们第一个大小求得是12呢?因为我们还没有分析完,我们接着进行规则的分析:结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。等我们将我们结构体中的元素全部都放置到应该处于的位置的时候,我们得到的所占内存的大小还应该跟我们的最大的对齐数进行比较,我们结构体所占的内存的大小还应该使我们最大对其数的整数倍!我们看接着我们上面的例子进行分析:我们上面求到的大小为10个字节,我们上面所出现的对齐数的大小为1,4,1,那么我们最大的对齐数就是4。我们最后得出的答案就需要是 4 的整数倍。就会寻找最近的4的倍数作为结构体的内存大小。也就是我们的12。这么看来我们的结论刚好用上。那么我们来探究一下下面的结构体为什么大小为8吧!

    ⚽1.第一个元素为char在我们的0偏移处,第二个元素char在我们的1偏移处,第三个元素int从我们4偏移处开始一直到我们的7偏移处结束。最后最大对齐数为4,找到最近的4的倍数作为我们结构体的大小也就是8。是不是感觉很简单?我们对齐数的第四条规律其实是一样的解释形式。我们需要将我们嵌套在内部的结构体的大小求出来进行和8进行比较作为存储偏移量的位置。最后求得嵌套结构体在内存中所占的大小。

    ⚽但是肯定会有小伙伴好奇:那些其他的地址呢?其实那些没用到的地址都浪费掉了。那么接下来我就向大家解释一下我们这种内存的浪费意义在哪里。

    ⛳内存对齐的优点

    ⚽内存对齐的主要优点有两个:1. 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。简单一点来说就是由于我们计算机的硬件的不同所以有的计算机就不能随意的读取数据,就像我们的结构体,刚读完一个一个字节的 char 紧接着就要重新读四个字节的 int 我们的计算机硬件的配置设计起来可能会很复杂,所以降低成本就会产生内存对齐的现象,告诉计算器你把内存中的全部数据都当作最大的那个数据进行读取。这样就避免了我们内存读取的来回转换的问题。

    ⚽2. 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。我们可以通过画图的形式来理解认识我们这一点:

    ⚽我们知道的是我们的计算机现在主要分为32位机器和64位机器。32位机器一次读取只能读取四个字节。那么要是我们没有内存对齐的话要是想要读取我们第二个 int 类型的数据就需要读取两次,之后将两次读取的数据进行拼接才可以的到我们想要的数据。但是我们存在内存对齐的机器只需要读取一次就可以了。这是典型的用空间换时间的问题。可以很大程度上提高我们程序的运行效率。

    ⚽那么到上面为止我们的结构体的相关知识的详细解析也就结束了。希望大家有所收获,感谢您的观看,祝您天天开心。 

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

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

相关文章

SAP 服务器参数文件详细解析

一、SAP参数的说明 SAP参数的学习需要了解SAP参数的作用、参数的启动顺序、参数的配置&#xff1b; 1、参数的启动顺序 a) 启动Start profileb) 启动default profilec) 启动instance profile 2、参数的位置 a) 启动参数Start profile的位置&#xff1a;/usr/sap//SYS/prof…

计讯物联数字乡村解决方案赋能乡村振兴

项目背景 数字乡村是乡村振兴的战略方向&#xff0c;是推动农村现代化的重要途径。当前&#xff0c;数字乡村建设正在加速推进&#xff0c;打造乡村数字治理新模式&#xff0c;提升乡村的数字化水平&#xff0c;进一步推动乡村振兴进入高质量发展新赛道。计讯物联作为数字乡村…

机器学习的几个公式

今天看了几个公式的推演过程&#xff0c;有些推演过程还不是很明白&#xff0c;再着担心自己后面会忘记&#xff0c;特来此记下笔记。python 是由自己特定的公式符号的&#xff0c;但推演过程需要掌握&#xff0c;其实过程不过程不是重点&#xff0c;只要是要记得公式的含义&am…

SocketCAN 命名空间 VCAN VXCAN CANGW 举例

文章目录NAMESPACESocketCAN最新 can-utils 安装VCAN 举例VXCAN 举例CANGW 举例参考NAMESPACE namespaces, 命名空间, 将全局系统资源包装在抽象中, 使命名空间中的进程看起来拥有自己全局资源的独立实例. 命名空间的一个用途是实现容器. Linux 命名空间类型及隔离物(Isolate…

译文 | Kubernetes 1.26:PodDisruptionBudget 守护不健康 Pod 时所用的驱逐策略

对于 Kubernetes 集群而言&#xff0c;想要确保日常干扰不影响应用的可用性&#xff0c;不是一个简单的任务。上月发布的 Kubernetes v1.26 增加了一个新的特性&#xff1a;允许针对 PodDisruptionBudget (PDB) 指定不健康 Pod 驱逐策略&#xff0c;这有助于在节点执行管理操作…

电商云仓是如何包装发货的?

包装不时是为了维护产品&#xff0c;而它从工厂地板移动到大型仓库&#xff0c;并最终经过批发或批发店抵达消费者。但是&#xff0c;自21世纪初以来&#xff0c;消费者希望与那些不时吸收着某种情感的品牌联络在一同&#xff0c;同时央求他们在心理上对品牌中止投资&#xff0…

【Java AWT 图形界面编程】Canvas 组件中使用 Graphics 绘图 ④ ( AWT 绘图窗口闪烁问题 )

文章目录一、AWT 绘图窗口闪烁问题二、完整代码示例一、AWT 绘图窗口闪烁问题 使用 Graphics 第一次绘图 完成后 , 如果在循环中 持续调用 Canvas#repaint() 函数刷新界面 , 代码如下 : import java.awt.*;public class HelloAWT {public static void main(String[] args) thr…

MySQL进阶——存储引擎

MySQL有9种存储引擎&#xff0c;不同的引擎&#xff0c;适合不同的场景&#xff0c;我们最常用的&#xff0c;可能就是InnoDB&#xff0c;应该是从5.5开始&#xff0c;就成为了MySQL的默认存储引擎。 show engines可以查询MySQL支持的这几种存储引擎&#xff0c;从表头能看出来…

SVN工程转Git工程Github托管

SVN工程转Git工程&Github托管1. 介绍2. autoAudioTest之SVN转Github步骤Step 1 工作环境(ubuntu)Step 2 安装升级必要软件Step 3 转换脚本Step 4 检查软件运行环境Step 5 生成authors.txtStep 6 SVN转换Git格式Step 7 Github新建空工程Step 8 Git提交已有工程Step 9 Git提交…

Dubbo快速入门看这一篇文章就够了

网站用户少,流量小,抗压力差(eg: ssm)网站用户量进一步增长,流量增多,服务器不能平滑扩容(eg: 多个ssm)网站用户和流量随时间稳步升高,需要随时进行服务器扩容(eg: rpc/http) 第2节 分布式框架解决的问题 1 2 3 4 5 6随着互联网架构的越来越复杂,由原来的单一架构 ...到... 流动…

M320、M601、HD1(RTU)功能对比

M320、M601、HD1_RTU硬件 / 软件功能对比一、硬件1.HD1-RTU2.Haas506-M3203.Haas506-M6014.对比区别二、软件1.对比区别一、硬件 1.HD1-RTU 详情参考HaaS506-HD1 (RTU) - 硬件介绍 2.Haas506-M320 详情参考HaaS506-M320 - 开发板介绍 3.Haas506-M601 详情参考HaaS506…

(二十二)简单算法和Lambda表达式

目录 前言: 1.选择排序 2.二分查找 3.Lambda表达式 前言: 算法是一个程序和软件的灵魂&#xff0c;要成为一名优秀的程序员&#xff0c;只有对基础算法全面掌握&#xff0c;才能在设计程序和编写代码的过程中显得得心应手。常用的基础算法有快速排序算法、堆排序算法、归并排…

每日一问-ChapGPT-20230115-关于断舍离

文章目录每日一问-ChapGPT系列起因每日一问-ChapGPT-20230115-关于断舍离人类脑是适合专心做一件事&#xff0c;还是适合并行做多件事做事情的优先顺序怎样安排chapGPT你是怎么学到这么多知识的chapGPT你拥有智慧吗chapGPT你是实时更新自己的模型吗chapGPT你有情感吗chapGPT你有…

C++程序卡死、UI界面卡顿问题的原因分析与总结

目录 1、概述 2、软件卡死问题 2.1、死循环 2.2、死锁 3、客户端软件的UI界面卡顿问题 3.1、UI线程在频繁地写日志到文件中&#xff0c;导致UI线程时不时的卡顿 3.2、从网上拷贝的代码中调用Sleep函数&#xff0c;导致UI界面有明显的卡顿 4、总结 VC常用功能开发汇总&a…

COCO_03 制作COCO格式数据集 dataset 与 dataloader

文章目录1 引言2 pycocotools介绍3 Dataset 构建4 Dataloader 构建4.1 解决batch中tensor维度不一致的打包问题4.2 collate_fn()函数分析AppendixA. convert_coco_poly_maskB. COCO_Transform参考1 引言 在之前的文章中&#xff0c;我们认识了COCO数据集的基本格式https://blo…

【设计模式】创建型模式·工厂模式

设计模式学习之旅(四) 查看更多可关注后查看主页设计模式DayToDay专栏 一.引子 需求&#xff1a;设计一个咖啡店点餐系统。 设计一个咖啡类&#xff08;Coffee&#xff09;&#xff0c;并定义其两个子类&#xff08;美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】&…

NoSQLBooster for MongoDB 8.0.1 Crack

最智能的 MongoDB IDE NoSQLBooster 是 MongoDB Server 3.6-6.0 的跨平台 GUI 工具&#xff0c;它提供内置的 MongoDB 脚本调试器、全面的服务器监控工具、链接流畅查询、SQL 查询、查询代码生成器、任务调度、ES2020 支持和高级 IntelliSense经验。新版本 8.0 现已推出&#x…

Laravel文档阅读笔记-How to Build a Rest API with Laravel: A Beginners Guide①

随着移动端和JavaScript框架的发展&#xff0c;比如React和Vue&#xff0c;Restful风格的API越来越流行。使用Restful风格的好处就是一个后端程序可以与多个版本的前端用户界面关联。 Laravel提供了创建Rest API的环境和生态。 首先得导入依赖包比如Laravel Passport和Larave…

MySQL中给字符串字段加索引

文章目录前言一、前缀索引和普通索引二、前缀索引对覆盖索引的影响三、优化前缀索引前言 学完了MySQL索引部分&#xff0c;我们清楚的认识到给子段添加索引可以快速的进行查询&#xff0c;节约时间。但是索引有很多。那么对于字段怎么加索引&#xff0c;加什么索引。加到索引不…

linux基本功系列之useradd命令实战

文章目录一. useradd 命令介绍二. 语法格式及常用选项三. 参考案例3.1 不加任何参数创建用户3.2 创建不能登录系统且没有家目录的用户3.3 创建一个用户&#xff0c;ID为23333.4 创建一个用户并指定其附加组3.5 创建用户并账户过期时间3.6 与useradd相关的目录文件总结前言&…