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

【C语言】自定义类型之---结构体超详解(结构体的定义使用、指针结构体,内存对齐,......代码详解)


目录

前言:

一:结构体

1.1:什么是结构体?

1.2:结构体类型的声明

1.3:结构体变量的定义

1.4:结构体的内存对齐

1.5:结构体传参

二:位段

2.1:位段是什么?

2.2:位段的内存分配

2.3:位段在vs编译器上内存的分配和使用


前言:

        今天分享的内容是C语言中自定义类型之一的结构体。在C语言中我们知道有很多种数据类型,如 int ,char,float 等,但是我们处于社会中,那么社会中的东西能用数据来表示吗?比如,一本书能用 int 或者 char 类型所表示吗?答案是不能的。因为一本书既包含有书名,还包含有作者名,单价和出版社等信息,那么此时单纯的 int ,char 等数据类型就行不通了,这时就得根据自己的需要来自定义一种结构体来描述这本书,其中以书名,单价,出版社等表示结构体的成员列表。可见自定义结构体给我们很大的遐想空间,表述万事万物。接下来我们一起看看结构体有哪些独特的魅力。

一:结构体

1.1:什么是结构体?

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

1.2:结构体类型的声明

声明:

struct tag       // struct(关键字),tag是自定义事物的名称
{mem_list;    // 成员列表,可以一个/多个
}variable_list;  // 变量列表

1.2.1:普通声明

例如描述一个学生:

//结构体定义一个学生
struct Student
{char name[20];	// 学生姓名char sid[20];	// 学生学号char sex[5];    // 学生性别int age;		// 学生年龄
}stu1,stu2;    //注意有分号,此时创建了两个struct Student类型的变量stu1和stu2.
// stu1 和 stu2 是全局变量int main()
{struct Student stu3,stu4;    //创建了两个struct Student类型的全局变量。reutrn 0;
}

此处的 stu1和stu2是全局变量,是在声明结构体的时候顺带创建的,当然也可以不顺带创建。

//结构体定义一个学生
struct Student
{char name[20];	// 学生姓名char sid[20];	// 学生学号char sex[5];    // 学生性别int age;		// 学生年龄
};

1.2.2:特殊声明

在声明结构体的时候,可以不完全的声明,被称为匿名结构体类型。

例如:

// 匿名结构体
struct 
{char name[20];int age;
}x;    //注意:在创建匿名结构体变量的时候,只能在这里创建(x)int main()
{return 0;
}

 但是这种结构在声明的时候已经省略了结构体标签(tag),这种结构体变量只能在定义的时候创建,并且这种结构体类型只能在声明的时候用一次(只能声明一次)。

为什么只能用一次?

// 结构体1
struct 
{int a;char b;double c;
}x;// 结构体2
struct
{int a;char b;double c;
}*p;int main()
{p = &x;return 0;
}

发现运行报错:

发现 结构体指针变量p与&结构体x的类型不匹配。 

我们知道:

int a = 10;
int* p = &a;

 所以 结构体1和结构体2的类型是不同的(即类型不匹配),故此这种匿名结构体在程序中只能使用一次。

1.2.3:结构体的自应用

在结构中包含一个类型为该结构本身的成员是否可以呢?就如同数据结构之中的链表一样:

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

 不过这样的结构体形式是否正确呢?答案是错误,这是因为在这一结构体中包含有一个结构体,这样的结构体在实现的时候会一直进行下去,永远没有尽头,也就是说它只有进没有出结构体的条件。那该怎样实现结构体的自引用呢?

正确形式:

struct Node
{int data;            // 数据域struct Node* next;   // 指针域,声明一个同类型的指针
};

就是说:将一个结构体通过其内的结构体指针与另一个同类型的结构体相连接起来,之后继续连,连,连。即将多个同类型的结构体通过内部的结构体指针(next)相连接起来就是结构体的自引用。 

1.2.4:结构体的重命名

重命名的关键字:typedef

 我们发现结构体的类型写起来实在是太长了:

struct Student;
struct Node;
...

将它们重新命一下名,方便写些。

typedef struct Student
{char name[20];int age;
}Stu;    //此处的 Stu 不是全局变量了,而是此结构体被重命名之后的新名字int main()
{struct Student stu1;Stu stu2;return 0;
}

其中,变量 stu1 和变量 stu2 的类型是相同的。 

1.3:结构体变量的定义与初始化

1.3.1:声明类型的同时定义变量

struct point
{int x;int y;int z;
}p1;    // 在声明类型的同时定义结构体变量

1.3.2:先声明类型,再定义变量

// 先定义一个结构体
struct point
{int x;int y;int z;
};// 再定义一个结构体变量(全局变量)
struct point p2;    int main()
{struct point p3;  // 局部变量return 0;
}

1.3.3:结构体变量的初始化 

struct point
{int x;int y;int z;
}p1 = {1,2,3};    //对p1初始化struct point p2 = {2,3,4};   //对p2初始化 int main()
{struct point p3 = {3,4,5};     //对p3初始化return 0;
}

1.3.4:结构体嵌套结构体的定义与初始化

struct point
{int x;int y;int z;
};struct stu
{char name[20];int age;struct point a;	//结构体嵌套一个struct point类型的结构体
};int main()
{struct stu s1 = { "张三",13,{1,2,3} };    // 赋值初始化struct stu s2 = { "李四",20,{2,3,4} };printf("%-20s\t%d\t%d\t%d\t%d\n", s1.name, s1.age, s1.a.x, s1.a.y, s1.a.z);printf("%-20s\t%d\t%d\t%d\t%d\n", s2.name, s2.age, s2.a.x, s2.a.y, s2.a.z);return 0;
}

代码运行结果:

1.4:(*)结构体的内存对齐(*)

        当我们掌握了关于结构体的使用情况后,接下来我们来深思考虑一下结构体的大小有多大呢?

1.4.1:分析结构体内存大小(内存对齐)

先看一下下面两种结构体代码:

struct Stu1
{int a;char b;char c;
};struct Stu2
{char b;int a;char c;
};int main()
{printf("sizeof(struct Stu1) = %d\n", sizeof(struct Stu1));printf("sizeof(struct Stu2) = %d\n", sizeof(struct Stu2));return 0;
}//运行结果:
sizeof(struct Stu1) = 8
sizeof(struct Stu2) = 12

 结果显示 struct Stu1类型的结构体大小是8个字节,而显示 struct Stu2类型的结构体大小是8个字节。为什么它们结构体内部的类型变量都相同,只是排序不同它们的大小就不同呢?这就涉及到结构体内部的内存对齐规则了。

结构体的对齐规则:

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

该如何计算结构体的大小呢?

结构体struct Stu1 内的成员变量一共占用了6个字节,因为结构体的内存对齐规则,需要2个最大对齐数才能将其存下。所以, 结构体struct Stu1的总大小为8个字节。

同理:结构体 struct Stu2 需要3个最大对齐数才能将其存下。所以, 结构体 struct Stu2 的总大小为12个字节。

1.4.2:为什么存在内存对齐

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

        不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:

        数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

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

设计一个优秀的结构体

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

1.4.3:修改默认对齐数

#pragma    // 预处理指令--用来改变默认对齐数

#include<stdio.h>#pragma pack(8)    //设置默认对齐数为8
struct s1
{char c1;int i;char c2;
};#pragma pack() //取消设置的默认对齐数,还原为默认#pragma pack(1)	//设置默认对齐数为1
struct s2
{char c1;int i;char c2;
};#pragma pack() //取消设置的默认对齐数,还原为默认int main()
{printf("%d\n", sizeof(struct s1));printf("%d\n", sizeof(struct s2));return 0;
}// 运行结果:
12
6

1.5:结构体传参

#include<stdio.h>
struct Stu
{char name[20];int age;
};void Print1(struct Stu s)        // 传整个结构体
{printf("%s\n", s.name);
}void Print2(struct Stu* ps)    // 传结构体的指针
{printf("%s\n", ps->name);
}void test2()
{struct Stu s = { "小明",12 };Print1(s);Print2(&s);
}

共有两种结构体传参的方法。

但是哪种方法更好一些呢?

首选Print2函数。因为:

1. 函数传参的时候,参数是需要压栈的,会有时间和空间上的系统开销。

2. 如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,故会导致性能的下降。

结论:结构体传参的时候,要传结构体的地址。 

二:位段

2.1:位段是什么?

首先:位段的声明和结构体是类似的,但有两个不同:

1. 位段的成员类型必须是 int,unsigned int,signed int。

2. 位段的成员名后面有一个冒号和一个数字。

例如:

#include<stdio.h>
struct A
{int a : 2;int b : 5;int c : 10;int d : 30;
};//其中 A 就是一个位段类型。int main()
{printf("%d\n", sizeof(struct A));return 0;
}//运行结果:8

 A 就是一个位段类型,但是为什么运行结果是8呢?位段A内部有4个int类型的数据,不应该是4*4=16个字节吗,这就需要了解了解位段的内存分配问题来。

2.2:位段的内存分配

        首先我们应知道 ”位段“ 中的 ”位“ 是二进制位,所以,int a:2 表示给 a 分配 2个二进制位(bit位),同理:int b:5 表示给 b 分配 5个二进制位(bit位),int c:10 表示给 c 分配 10个二进制位(bit位),int d:30 表示给 d 分配 30个二进制位(bit位)。

位段的内存分配规则:

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

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

3. 位段涉及很多不确定性因素,位段是不跨平台的,注重可移值的程序应该避免使用位段。

以 struct A 位段分析:

 因为位段的空间是按照以4个字节( int )或者 1个字节(char)的方式来开辟的。此时当4个字节不够的时候,需要再申请4个字节来存储,直到存储完毕。

看下列代码:

#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;
}

内存分析:

我们可以知道此位段得到大小是3个字节(一次增加一个字节)。

 vs编译器中一个字节内部是按照从右往左的顺序,即从二进制低位向高位使用的。

 位段的跨平台问题:

1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

结论:

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


总结:    

        今天,我们从结构体的声明与定义开始,学习了另外一种特殊声明(匿名结构体),又了解了结构体变量的定义与初始化是怎样描述的,接着学习了本章最最重要的结构体的内存分配对其问题,最后介绍了一种特殊结构体(位段),优点是比结构体更节省空间,但是也不要忘了它的局限性。

        以上的内容若是对大家起到帮助的话,大家可以动动小手点赞,评论+收藏。大家的支持就是我进步路上的最大动力!


相关文章:

【C语言】自定义类型之---结构体超详解(结构体的定义使用、指针结构体,内存对齐,......代码详解)

目录 前言&#xff1a; 一&#xff1a;结构体 1.1&#xff1a;什么是结构体&#xff1f; 1.2&#xff1a;结构体类型的声明 1.3&#xff1a;结构体变量的定义 1.4&#xff1a;结构体的内存对齐 1.5&#xff1a;结构体传参 二&#xff1a;位段 2.1&#xff1a;位段是什…...

【完美恢复】修复计算机中丢失emp.dll的多个详细方法

最近&#xff0c;在尝试运行某款游戏时&#xff0c;我遭遇了一个令人头痛的问题——“emp.dll文件丢失”。这个错误通常意味着游戏的某个关键文件没有被正确加载或已损坏。以下是我解决问题的步骤和一些心得体会&#xff0c;希望对遇到类似问题的玩家们有所帮助。 emp.dll是一…...

暗黑4可以搬砖吗?暗黑4怎么搬砖 搬砖攻略

暗黑4可以搬砖吗&#xff1f;暗黑4怎么搬砖 搬砖攻略 暗黑破坏神4属于是暴雪旗下一款经典游戏IP&#xff0c;在全世界有着广泛的玩家群体&#xff0c;更是在今年暴雪国服宣布回归之后&#xff0c;吸引了一大批新玩家加入。今天小编就为大家带来暗黑4的详细搬砖教程。 现在我们…...

WLAN技术

冲突域&#xff1a;连接在同一传输线缆上的所有工作站的集合&#xff0c;或者说是同一物理网段上所有节点的集合共同竞争网络资源形成的域叫冲突域。 在OSI模型中&#xff0c;冲突域被看作是第一层的概念&#xff0c;连接同一冲突域的设备有中继器、集线器&#xff08;hub&…...

维修AB罗克韦尔工控机 PanelView 900 2711-T9C8 SER C 触摸屏人机界面

可视化和 HMI 解决方案可帮助您满足生产力、创新和全球化需求。为电子操作员界面终端、分布式客户端/服务器 HMI 和信息软件提供了一致的外观和感觉。编程工具和高级软件应用程序包括远程访问和数据分析&#xff0c;可加速开发并提高效率。 图形终端 图形终端提供各种尺寸、操…...

334_C++_std::bind中使用shared_from_this()

std::bind(&HttpClient::getPwd, shared_from_this(), std::placeholders::_1, std::placeholders::_2);[ HttpClient继承自NetObj,NetObj是父类,NetObj受到std::shared_pt...

【Python】防御性编程入门

1. 前言 防御性编程指的是为了防止代码泄露后被竞品公司窃取技术&#xff0c;使用一种较高级的明文加密编程方式。也可以当做一种带解密性质的时间胶囊&#xff0c;锻炼程序员自己的记忆能力、读代码能力等。 2. 案例分析 2.1 import Import里面可以多取一些喜欢的名字&#…...

无线麦克风哪个品牌音质最好?热门无线麦克风品牌推荐

这段时间短视频行业兴起&#xff0c;很多人都开始尝试步入自媒体&#xff0c;不过想要自己的视频内容更出色、更吸引人&#xff0c;好的音频设备肯定是必不可少的&#xff0c;而麦克风就是其中的一种。麦克风的好坏也将决定了一个视频的质量与完整性等等&#xff0c;如果我们作…...

粒子奇观:用Processing创造宇宙级的动态效果

前言: 👋 今天,我们将一起探索宇宙的奥秘,不是在星空下,而是在Processing的代码世界中。这是我们的第八篇文章,我们将深入粒子系统的神奇领域,学习如何创造出令人惊叹的动态效果。 粒子系统:构建动态世界的基石 🔨 粒子系统是计算机图形学中用于模拟复杂自然现象…...

Filesystem Fragmentation on Modern Storage Systems——论文泛读

TOCS 2023 Paper 论文阅读笔记整理 问题 文件系统碎片是计算机系统随着时间的推移而变慢的主要原因之一。以前认为&#xff0c;碎片化对硬盘驱动器&#xff08;HDD&#xff09;等旋转存储设备有害&#xff0c;但不影响固态驱动器&#xff08;SSD&#xff09;&#xff0c;因为…...

如何同步管理1000个设备的VLAN数据?

什么是VLAN&#xff1f; VLAN&#xff0c;也就是虚拟局域网&#xff0c;是通过为子网提供数据链路连接来抽象出局域网的概念。在企业网中&#xff0c;一个企业级交换机一般是24口或者是48口&#xff0c;连接这些接口的终端在物理上形成一个广播域。广播域过大&#xff0c;就会导…...

【谷粒商城】01-环境准备

1.下载和安装VirtualBox 地址&#xff1a;https://www.virtualbox.org/wiki/Downloads 傻瓜式安装VirtualBox 2.下载和安装Vagrant官方镜像 地址&#xff1a;https://app.vagrantup.com/boxes/search 傻瓜式安装 验证是否安装成功 打开CMD,输入vagrant命令&#xff0c;是否…...

2024深圳杯数学建模C题参考论文24页+完整代码数据解题

一、问题研究 24页参考论文&#xff1a; 【编译器识别】2024深圳杯C题24页参考论文1-3小问完整解题代码https://www.jdmm.cc/file/2710545/ 为了回答这些问题&#xff0c;我们需要进行一系列的编译实验、分析编译结果&#xff0c;并构建判别函数。以下是对这些问题的初步分析…...

用go语言写一个代码,加班就自动给老婆发信息,下班自动提醒的代码

文章推荐 1 作为程序员&#xff0c;开发用过最好用的AI工具有哪些&#xff1f; 2 Github Copilot正版的激活成功&#xff0c;终于可以chat了 3 idea,pycharm等的ai assistant已成功激活 4 新手如何拿捏 Github Copilot AI助手&#xff0c;帮助你提高写代码效率 5 Jetbrains的a…...

Spring-Cloud 微服务

1. 微服务架构 1.1 单体应用架构---内部项目【OA WMS等】 将项目所有模块(功能)打成jar或者war&#xff0c;然后部署一个进程 优点: 1:部署简单:由于是完整的结构体&#xff0c;可以直接部署在一个服务器上即可。 2:技术单一:项目不需要复杂的技术栈&#xff0c;往往一套熟悉的…...

python数据分析——数据可视化(图形绘制基础)

数据可视化&#xff08;图形绘制基础&#xff09; 前言一、图形绘制基础Matplotlib简介使用过程sin函数示例 二、常用图形绘制折线图的绘制plot示例 散点图的绘制plot示例 柱状图的绘制bar示例 箱型图绘制plot.box示例 饼状图的绘制pie示例 三、图形绘制的组合情况多个折线图的…...

必背!!2024年软考中级——网络工程师考前冲刺几页纸

距离软考考试的时间越来越近了&#xff0c;趁着这两周赶紧准备起来 今天给大家整理了——软考网络工程师考前冲刺几页纸&#xff0c;都是核心重点&#xff0c;有PDF版&#xff0c;可打印下来&#xff0c;每天背一点。 计算机总线分类 ①总线的分类&#xff1a;数据总线、地址总…...

html+js光标操作

光标设置id为username的字段 window.addEventListener("load", function() {document.getElementById("username").focus(); }); 光标在username的时候点击enter回车键的时候光标移动到id为password的input里面 document.getElementById("username…...

Cannot read properties of undefined (reading ‘init‘)报错

出现这个报错是印象项目没有引echarts包 npm i echarts 下包 然后在main.js中引入 import echarts from echarts Vue.prototype.$echarts echarts 如果还不行 import * as echarts from echarts; 更改一下引入方式 ok了...

golang html/template模板中使用自定义函数/方法的2种方法总结

在golang的html/template模板库中我们如果希望在视图文件中调用自定义的函数 或者方法可以通过以下2种方法实现&#xff1a; 1. 调用自定义函数 可通过将自定义的函数加入到 template.FuncMap中&#xff0c;然后再使用 template.New("xxx.html").Funcs(funcMap)来在…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力

引言&#xff1a; 在人工智能快速发展的浪潮中&#xff0c;快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型&#xff08;LLM&#xff09;。该模型代表着该领域的重大突破&#xff0c;通过独特方式融合思考与非思考…...

Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器

第一章 引言&#xff1a;语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域&#xff0c;文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量&#xff0c;支撑着搜索引擎、推荐系统、…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

数据库分批入库

今天在工作中&#xff0c;遇到一个问题&#xff0c;就是分批查询的时候&#xff0c;由于批次过大导致出现了一些问题&#xff0c;一下是问题描述和解决方案&#xff1a; 示例&#xff1a; // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2

每日一言 今天的每一份坚持&#xff0c;都是在为未来积攒底气。 案例&#xff1a;OLED显示一个A 这边观察到一个点&#xff0c;怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 &#xff1a; 如果代码里信号切换太快&#xff08;比如 SDA 刚变&#xff0c;SCL 立刻变&#…...

多模态图像修复系统:基于深度学习的图片修复实现

多模态图像修复系统:基于深度学习的图片修复实现 1. 系统概述 本系统使用多模态大模型(Stable Diffusion Inpainting)实现图像修复功能,结合文本描述和图片输入,对指定区域进行内容修复。系统包含完整的数据处理、模型训练、推理部署流程。 import torch import numpy …...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

android RelativeLayout布局

<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:gravity&…...

五子棋测试用例

一.项目背景 1.1 项目简介 传统棋类文化的推广 五子棋是一种古老的棋类游戏&#xff0c;有着深厚的文化底蕴。通过将五子棋制作成网页游戏&#xff0c;可以让更多的人了解和接触到这一传统棋类文化。无论是国内还是国外的玩家&#xff0c;都可以通过网页五子棋感受到东方棋类…...