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

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…...

智慧医疗能源事业线深度画像分析(上)

引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)

CSI-2 协议详细解析 (一&#xff09; 1. CSI-2层定义&#xff08;CSI-2 Layer Definitions&#xff09; 分层结构 &#xff1a;CSI-2协议分为6层&#xff1a; 物理层&#xff08;PHY Layer&#xff09; &#xff1a; 定义电气特性、时钟机制和传输介质&#xff08;导线&#…...

Go 语言接口详解

Go 语言接口详解 核心概念 接口定义 在 Go 语言中&#xff0c;接口是一种抽象类型&#xff0c;它定义了一组方法的集合&#xff1a; // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的&#xff1a; // 矩形结构体…...

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例

文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...

MySQL 8.0 OCP 英文题库解析(十三)

Oracle 为庆祝 MySQL 30 周年&#xff0c;截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始&#xff0c;将英文题库免费公布出来&#xff0c;并进行解析&#xff0c;帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...

使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度

文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...

算法岗面试经验分享-大模型篇

文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer &#xff08;1&#xff09;资源 论文&a…...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf

FTP 客服管理系统 实现kefu123登录&#xff0c;不允许匿名访问&#xff0c;kefu只能访问/data/kefu目录&#xff0c;不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...