【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. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

结论:

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


总结:    

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

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


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

docker镜像容器常用命令

常用基础命令1、docker info #查看docker版本等信息 2、docker search jenkins #搜索jenkins镜像 3、docker history nginx #查看镜像中各层内容及大小,每层对应的dockerfile中的一条指令。 4、docker network ls #显示当前主机上的所有网络 5、docker logs nginx …

2024MySQL8安装与绿色版Navicat连接【提供安装包】数据库

视频教程面向人群和使用方法&#xff1a; 1&#xff1a;大学生【解决老师作业或自己兴趣学习需要】; 2&#xff1a;第一次需要安装MySQL的开发者【需要简单使用&#xff0c;因为项目会用到】 3&#xff1a;老手二倍速&#xff0c;新手老老实实按照教程一倍速模仿视频操作&am…

【虚拟机】深入理解java虚拟机【内存溢出实例】

目录 一、问题解析 二、粉丝福利 一、问题解析 通过简单的小例子程序&#xff0c;演示java虚拟机各部分内存溢出情况&#xff1a; (1).java堆溢出&#xff1a; Java堆用于存储实例对象&#xff0c;只要不断创建对象&#xff0c;并且保证GC Roots到对象之间有引用的可达&am…

[数据集][目标检测]卡车抓斗卸车检测数据集VOC+YOLO格式213张2类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;213 标注数量(xml文件个数)&#xff1a;213 标注数量(txt文件个数)&#xff1a;213 标注类别…

ROS从入门到精通4-3:制作Docker镜像文件Dockerfile

目录 0 专栏介绍1 为什么需要Dockerfile&#xff1f;2 Dockerfile书写原则3 Dockerfile常用指令3.1 FROM3.2 MAINTAINER3.3 RUN3.4 ADD3.5 COPY3.6 CMD3.7 ENV3.8 EXPOSE3.9 WORKDIR3.10 ARG 4 Dockerfile构建ROS工程实例 0 专栏介绍 本专栏旨在通过对ROS的系统学习&#xff0…

数据结构与算法学习笔记一---顺序表的静态存储表示和实现(C++)

目录 前言 1.什么是顺序表 2.顺序表的静态存储表示 1.初始化 2.长度 3.数据元素 4.长度 5.获取元素下标 6.前驱节点 7.后继节点 8.插入 9.删除 10.遍历 11.测试代码 前言 这篇文章讲的是顺序表的两种实现方式。 1.什么是顺序表 线性表的顺序表示指的是用一组地址…

(论文笔记)TABDDPM:使用扩散模型对表格数据进行建模

了解diffusion model&#xff1a;什么是diffusion model? 它为什么好用&#xff1f; - 知乎 摘要 去噪扩散概率模型目前正成为许多重要数据模式生成建模的主要范式。扩散模型在计算机视觉社区中最为流行&#xff0c;最近也在其他领域引起了一些关注&#xff0c;包括语音、NLP…

LangChain搭建Agent | 使用initialize_agent

1.create_tool_calling_agent 构建agent&#xff0c;这个方法是过时了吗&#xff1f;官方文档也没更新&#xff0c;官方示例也运行错误 from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import ConfigurableField from langchain_core…

医院污水一体化处理设备有哪些

医院污水一体化处理设备通常包括以下几个主要组件&#xff1a; 预处理单元&#xff1a;用于去除污水中的固体悬浮物、颗粒物、油脂等&#xff0c;常见的预处理单元包括格栅、沉砂池、油水分离器等。生物处理单元&#xff1a;用于降解有机物质和去除氮、磷等营养物质。常见的生物…

基坑监测识别摄像机

基坑是建筑施工中的一个重要环节&#xff0c;它对整个建筑工程的安全和稳定性起着至关重要的作用。为了监测基坑的状态和确保施工的安全进行&#xff0c;基坑监测识别摄像机被广泛应用于建筑工程中。这种摄像机可以实时监测基坑施工的情况&#xff0c;识别可能存在的问题并提供…

如何在Spring启动的时候执行一些操作

如何在Spring启动的时候执行一些操作 在Spring启动的时候执行一些操作有多种方式。你可以通过实现ApplicationRunner或者CommandLineRunner接口&#xff0c;在Spring Boot应用程序启动后执行特定操作。另外&#xff0c;你也可以使用PostConstruct注解&#xff0c;在Spring Bea…

圆片/圆盘测厚设备 HW01-SG系列单点测厚仪

关键字:圆片测厚仪圆盘测厚仪, 圆形测厚仪, 单点测厚仪, 汽车工件测厚仪, 产品简介&#xff1a; 测厚仪采用上下两个对射的激光位移传感器测量圆盘状物体边缘的厚度。圆盘放置在由步进电机驱动的托盘上&#xff0c;点按测量按钮托盘旋转一周&#xff0c;可测量被测物整个圆周上…

立即注册 | 线上讲座:基于 NGINX 为现代应用构筑三大安全防线

原文作者&#xff1a;NGINX 原文链接&#xff1a;立即注册 | 线上讲座&#xff1a;基于 NGINX 为现代应用构筑三大安全防线 转载来源&#xff1a;NGINX 开源社区 NGINX 唯一中文官方社区 &#xff0c;尽在 nginx.org.cn 基本信息 课程主题&#xff1a;基于 NGINX 为现代应用构…

大模型算法(零) - Transformer中的细节与实现

讲transformer的文章已经铺天盖地了&#xff0c;但是大部分都是从原理的角度出发的文章&#xff0c;原理与实现之间的这部分讲解的较少&#xff0c;想要了解实现细节&#xff0c;还是要去看代码才行。记录一下自己学习过程中遇见的细节问题和实现问题。 Transformer整体架构 图…

Android面试题之Kotlin的几种常见的类

本文首发于公众号“AntDream”&#xff0c;欢迎微信搜索“AntDream”或扫描文章底部二维码关注&#xff0c;和我一起每天进步一点点 初始化的顺序 主构造函数里声明的属性 类级别的属性赋值 init初始化块里的属性赋值和函数调用 次构造函数里的属性赋值和函数调用 延迟初始…

Chirpstack配合网关与lora设备通信

之前的章节讲过chirpstack的下载和安装部署&#xff0c;这节算是后续&#xff0c;利用chirpstack和lora设备做通信&#xff0c; 首先开启chirpstack&#xff0c;并登录&#xff0c;登录完成之后需要添加网关和设备&#xff0c;添加网关也就是Gatway&#xff0c;所以点开左侧的G…

搜索二维矩阵 - LeetCode 热题 64

大家好&#xff01;我是曾续缘&#x1f9e1; 今天是《LeetCode 热题 100》系列 发车第 64 天 二分查找第 2 题 ❤️点赞 &#x1f44d; 收藏 ⭐再看&#xff0c;养成习惯 搜索二维矩阵 给你一个满足下述两条属性的 m x n 整数矩阵&#xff1a; 每行中的整数从左到右按非严格递增…

激光切割机哪家可靠?

激光切割机哪家可靠&#xff1f;市面上的激光切割机牌子很多&#xff0c;具体什么牌子好&#xff0c;建议综合考虑一下企业成立时间、技术实力、设备工艺做工、售后服务&#xff0c;一般成立时间长&#xff0c;设备装配经验丰富&#xff0c;售后服务完善的企业&#xff0c;能够…

深度学习之卷积神经网络理论基础

深度学习之卷积神经网络理论基础 卷积层的操作&#xff08;Convolutional layer&#xff09; 在提出卷积层的概念之前首先引入图像识别的特点 图像识别的特点 特征具有局部性&#xff1a;老虎重要特征“王字”仅出现在头部区域特征可能出现在任何位置下采样图像&#xff0c…

银行业数据分析专家视角:业务场景中的深度解析与应用

一、引言 在数字化和大数据时代的浪潮下&#xff0c;银行业正经历着前所未有的变革。作为数据分析领域的资深专家&#xff0c;我深知数据分析在银行业务发展中的重要性和价值。本文将从银行业数据分析的角度出发&#xff0c;深入探讨相关业务场景下的数据分析应用&#xff0c;…