什么是结构体【详解】

这篇具有很好参考价值的文章主要介绍了什么是结构体【详解】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

结构体,数据结构,算法,链表,c语言,c++

本期介绍🍖

主要介绍:什么是结构体,结构体的声明、定义、初始化、以及传参,匿名结构体类型,如何通过结构体来实现链表数据结构,结构体在内存中是如何存储的(即:结构体内存对齐),什么是内存对齐👀。



  我们知道C语言本身就存在着一些语法类型,也就是内置类型譬如:char、short、int、long、float、double)。但在实际应用中仅仅只有这些类型是不够用的,因为我们无法单一的用内置类型来描述一个复杂的对象,就譬如:一个人、一本书等等。所以C语言就给出了除内置类型之外的一种类型:自定义类型,有了这种类型我们就能够自己来构建类型了。而比较常用的自定义类型有:结构体联合体枚举


一、什么是结构体🍖

  结构体是C语言中一种重要的数据类型,该数据类型由一组称为成员的数据所组成,其中每个成员可以是不同类型的。结构体通常用来表示类型不同但是又相关的若干数据。(注意:数组与结构体一样,也是一种值的集合,只不过数组的每个元素都要是相同类型的,而结构体的每个成员可以是不同类型的


二、结构体的声明、定义、初始化🍖

  首先,要使用结构体就必须声明(创建) 一个结构体类型,就必须用到结构体关键字struct。如何声明一个结构体类型,如下所示。其中tag结构体标签,用来区分不同的结构体类型。struct tag表示结构体类型,声明完结构体后就可以用该类型来创建变量了。member-list;成员列表,它是结构体所包含的基本的结构类型。variable-list;表示变量列表,这个列表可写可不写,写了就代表你用上面所创建的结构体类型定义了一个该类型的变量,没写则表示你仅仅只创建了一个结构体类型。

struct tag
{
	member-list;
}variable-list;

注意:
 1.若结构体声明是在mian函数之外的,那么列表后创建的结构体变量是一个全局变量
 2.结构体成员变量也可以是另一个结构体类型,这种用法被称为:嵌套结构体
 3.在结构体声明结束的时候,千万不要忘记{}后面是有一个的。


  声明完结构体类型,就可以用它来定义初始化结构体变量了。注意:结构体初始化与数组一样需要用{}。举个例子,如下图所示:

struct grade
{
	double math;
	double english;
};
struct student
{
	char name[20];//姓名
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
	struct grade;//成绩(这是一个嵌套结构体类型)
};
int main()
{
	//定义、初始化结构体类型
	struct student ly = {"张三", 23, "男", "2117305789", {98.5, 66.0}};
	return 0;
}

  不知道大家在用结构体类型定义一个变量时,有没有觉得结构体的类型名太长了。其实可以通过typedef来重定义结构体的类型名,如下所示:

typedef struct student
{
	char name[20];//姓名
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
	struct grade;//成绩(这是一个嵌套结构体类型)
}stu;

  注意这里的stu并不是创建的变量,而是对于struct student的重命名。之后我们既可以用stu来创建变量也可以用struct student来创建。


三、匿名结构体类型🍖

  我们在声明结构的时候,可以不完全的声明,也就是不写结构体标签,所以称之为匿名。如下所示:

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

  这种语法结构C语言是支持的,但不被我们所喜,因为这种声明出来的结构体类型我们只能用其定义一次变量,之后想用都用不了了。为什么呢?难道我们之后要用struct类型来创建变量吗?别扯淡了,struct可是结构体关键字啊!总之,除非你只想用一次你创建的结构体类型,或者不想该结构体类型被别人使用,这时可以用匿名结构体类型。

  还值得注意的一点是,就算两个匿名结构体类型的成员变量完全一样,但在编译器看来它们两个仍然是两个完全不同的类型。如下举例所示:

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

struct
{
	 int a;
	 char b;
	 float c;
}a[20], *p;

int main()
{
	p = &x;
	return 0;
}

结构体,数据结构,算法,链表,c语言,c++


四、结构体自引用🍖

  我们知道在数据在内存中有许多存储的方式,就譬如:顺序表链表等等。其中顺序表是在内存中连续存放的,而链表是打乱着存放的,其并不连续。如下图所示:

结构体,数据结构,算法,链表,c语言,c++

  我们在读取数据的时候顺序表只要知道起始位置,就可以依次找到各个数据。但链表却不行啊,因为它在内存中并不是连续的,而是乱序存放的。问题来了,那链表该怎么往外取数据呢? 其实也不难,是不是只要1可以找到2,2可以找到3,3可以找到4,4可以找到5,不就可以把存在不同内存位置中的数据全部依次的取出来了嘛,数据就如同被一个链条串起来了一样,故称为:链表结构存储。那该怎么实现链表呢?


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

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

  其实我们只需要在结构体中包含一个类型为该结构体本身的成员,这样就可以像套娃一样,在一个数据中找到另一个数据,在另一个数据中又找到另另一个数据,以此下去。这样不就可以实现链表结构了嘛!假设这个想法可以吧,那再仔细思考一下,当使用sizeof()来计算该类型的结构体大小时,结果又是多少?

#include<stdio.h>
struct Node
{
 int data;
 struct Node next;
};

int main()
{
	printf("%d\n",sizeof(struct Node));
}

结构体,数据结构,算法,链表,c语言,c++

  可见,C语言是不支持结构体中包含一个类型为该结构体本身的这种语法,为什么呢?因为编译器也无法得知该结构体类型的大小呀,每个结构体套一个自身,这种思想不就是函数递归操作(没有限制条件的)嘛,理论上可以套无数个该类型的结构体,你说编译器能计算出它的大小吗?这压根就是一个错误的写法。所以我们无法通过结构体中包含下一个结构体这种写法来实现链表的,我们需要另辟思路了。


  想法二: 在结构中包含一个指向类型为该结构自身的指针

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

  稍加思索,不难得出这个想法其实是可行的。如下图所示,我们只要就把2节点的地址赋给1节点,把3节点的地址赋给2节点,把4节点的地址赋给3节点,把5节点的地址赋给4节点,最后给5节点赋上一个NULL(表示不指向任何位置)。如此不就可以通过一个节点能够找到下一个节点这种方式逐个找遍所有节点了嘛,不就实现链表了嘛。

结构体,数据结构,算法,链表,c语言,c++
  而且该想法必不会像先前的想法那样,在计算类型所占内存大小时无法被获知。因为,这样设计的结构体类型的第二个成员变量本质上就是一个指针,而指针无非就是4/8个字节嘛,怎么可能会出现无法计算的情况呢?指针又不套娃!!!


五、结构体内存对齐🍖

  不知道大家在学习结构体的时候有没有思考过结构体它在内存中到底是如何存放的结构体多占内存真的是所有成员变量大小的总和吗?会与数组一样随着下标的增长地址由低到高变化吗?大家先来猜一猜,下边两个结构体S1和S2在内存中会占用多少空间:

#include<stdio.h>
struct S1
{
	char a;
	int b;
	char c;
};
struct S2
{
	char a;
	char b;
	int c;
};
int main()
{
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	return 0;
}

  这两个结构体所含的成员变量个数和类型都一样啊,都是由一个int和两个char类型构成的。那么按照一贯的思维,我们必然会得出这两个结构体理应都占用6个字节的内存空间啊。但事实真的是这样吗?

结构体,数据结构,算法,链表,c语言,c++

  答案当然是否定的,从上图就可以看出结果完全与我们所思所想的不同。有人就要问了:不因该啊,为什么会结果会得出大于结构体成员变量大小总和这种情况啊?这样做不就浪费内存空间了嘛! 而且还可以看出S1和S2两个结构体的成员仅仅只是顺序不同,最后得出的结果也是不一样的!那这到底是怎么回事呢?这就不得不好好说一说结构体在内存中的存放方式了(也就是结构体内存对齐)了。


5.1 结构体对齐规则🍖

  结构体对齐规则:

  1. 第一个成员在相对于结构体变量起始位置偏移量为0的地址处
    (通俗点来说,就是第一个成员变量的地址与结构体起始位置的地址是相同的)如下图所示:
    结构体,数据结构,算法,链表,c语言,c++
  2. 其他成员变量要对齐到<对齐数>的整数倍处
    (对齐数 = 编译器默认对齐数与该成员变量大小的较小值),vs编译器默认对齐数为8,gcc没有默认对齐数这一说。
    结构体,数据结构,算法,链表,c语言,c++
  3. 结构体的总大小为最大对齐数(每个成员的都有一个对齐数)的整数倍
    结构体,数据结构,算法,链表,c语言,c++
  4. 如果是嵌套结构体的情况,嵌套结构体的对齐数就是其自身的最大对齐数。(同理数组的对齐数就是其元素的对齐数)
    结构体,数据结构,算法,链表,c语言,c++

  在VS编译器中存在一个宏offsetof(type, member),可以用来计算一个成员在结构体类型所创建的变量中的偏移量是多少。其中type是指想要被计算的结构体类型,member是该结构体中的成员。注意:在使用offsetof()前应该先引用一个头文件<stddef.h>

#include<stdio.h>
#include<stddef.h>

struct S1
{
	char a;
	int b;
	char c;
};

int main()
{
	printf("%d\n", offsetof(struct S1, a));
	printf("%d\n", offsetof(struct S1, b));
	printf("%d\n", offsetof(struct S1, c));
	return 0;
}

结构体,数据结构,算法,链表,c语言,c++

  这与我们之前所述的情况几乎是一摸一样。


5.2 为什么存在内存对齐🍖

  通过上面的讲解,我们发现结构体在内存中存储时,会故意浪费掉一些空间,来使得结构体成员在某些边界处对齐。那为什么要这么做呢?原因有两个:

  1. 平台原因(移植原因):
    不是所有的硬件平台都能够访问任意地址处的任意数据的,某些硬件平台只能在某些地址处取特定类型的数据,否则就会抛出硬件异常。
  2. 性能原因:
    数据结构(尤其是栈)因该尽可能的在自然边界上对齐。原因在于,为了访问未对齐的数据,处理器需要做两次内存访问,而对齐的内存只需要一次访问就够了。
    结构体,数据结构,算法,链表,c语言,c++

  总体上来说,结构体的内存对齐就是一种用空间换取时间的做法,浪费空间来换取性能上的提升。那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:让占用空间小的成员尽量集中到一起,就可以降低一些空间的浪费了举个例子:
结构体,数据结构,算法,链表,c语言,c++


5.3 修改默认对齐数🍖

  其实VS编译器中的默认对齐数(也就是8)是可以进行编译修改的,通过#pragma预处理指令来进行修改。举个例子:

#include<stdio.h>
#pragma pack(1)//设置默认对齐数为1
struct S1
{
	char a;
	int b;
	char c;
};

int main()
{
	printf("%d\n", sizeof(struct S1));
	return 0;
}
#pragma pack()//取消设置的默认对齐数,还原为默认

结构体,数据结构,算法,链表,c语言,c++

  结构在对齐方式不合适的时候,我么可以自己更改默认对齐数。


5.4 成员在内存中在存储顺序🍖

  由下图可知,结构体成员变量在内存中存放的顺序是这样的:

结构体,数据结构,算法,链表,c语言,c++


六、结构体传参🍖

  结构体与普通内置类型一样也有两种传参方式:1. 传值2. 传址。那我们到底该选择哪一种传参方式更好呢?其实吧,选择传递结构体变量的地址更好,为什么呢?如果是传值函数在传参的时候参数是需要压栈的,若此时结构体过大,参数压栈时系统的开销也就较大,会导致性能的下降。可传址就不同了,不管你结构体有多大我传递的地址永远是4/8个字节,开销远比传值小的多。而且传值和传址都能够实现对结构体变量的调用,故首选传址调用。下面是两个不同的传参方法:

struct S
{
 int data[1000];
 int num;
};

//结构体传参
void print1(struct S s)
{
 	printf("%d\n", s.num);
}

//结构体地址传参
void print2(struct S* ps)
{
	 printf("%d\n", ps->num);
}

int main()
{
	 struct S s = {{1,2,3,4}, 1000};
	 print1(s);  //传结构体
	 print2(&s); //传地址
	 return 0;
}

结构体,数据结构,算法,链表,c语言,c++

  注意:如果是结构体变量地址,我们需要用->来访问结构体的成员;而如果是结构体变量名,我们需要用.来访问结构体成员。

  注意:相同类型的结构体变量,我们可以直接通过=来进行赋值操作,不需要像数组那样访问到每一个元素然后才能进行赋值,因为结构体是一个类型啊。


结构体,数据结构,算法,链表,c语言,c++

这份博客👍如果对你有帮助,给博主一个免费的点赞以示鼓励欢迎各位🔎点赞👍评论收藏⭐️,谢谢!!!
如果有什么疑问或不同的见解,欢迎评论区留言欧👀。文章来源地址https://www.toymoban.com/news/detail-779479.html

到了这里,关于什么是结构体【详解】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【算法与数据结构】 C语言实现单链表队列详解

    前面我们学习了队列的顺序表的实现,本节将用单链表实现队列。 队列也可以数组和链表的结构实现, 使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低 。下面我们先复习一下队列的基本概念: 队列:只允许在一端进行插入

    2024年04月11日
    浏览(43)
  • 数据结构与算法教程,数据结构C语言版教程!(第五部分、数组和广义表详解)三

    数组和广义表,都用于存储逻辑关系为“一对一”的数据。 数组存储结构,99% 的编程语言都包含的存储结构,用于存储不可再分的单一数据;而广义表不同,它还可以存储子广义表。 本章重点从矩阵的角度讨论二维数组的存储,同时讲解广义表的存储结构以及有关其广度和

    2024年01月21日
    浏览(40)
  • 数据结构与算法教程,数据结构C语言版教程!(第五部分、数组和广义表详解)五

    数组和广义表,都用于存储逻辑关系为“一对一”的数据。 数组存储结构,99% 的编程语言都包含的存储结构,用于存储不可再分的单一数据;而广义表不同,它还可以存储子广义表。 本章重点从矩阵的角度讨论二维数组的存储,同时讲解广义表的存储结构以及有关其广度和

    2024年01月23日
    浏览(37)
  • 【数据结构】详解链表结构

    上篇博客已经介绍了顺序表的实现:【数据结构】详解顺序表。最后在里面也谈及了顺序表结构的缺陷,即 效率低,空间浪费 等等问题,那么为了解决这些问题,于是乎我们引入了链表的概念,下面将对链表结构进行讲解 首先肯定会问,到底什么是链表? 链表的概念 : 链

    2024年02月05日
    浏览(33)
  • C语言数据结构--链表

    顺序表的问题及思考 问题: 1. 中间/头部的插入删除,时间复杂度为O(N) 2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。 3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到 200,我们再继续插入了5个数据,后面没有

    2024年02月05日
    浏览(35)
  • C语言数据结构——链表

    目录 前言 一、什么是链表 1.1链表的结构和概念 1.2 链表的分类 二、无头单向非循环链表 2.1 创建结构体 2.2 动态申请一个节点 2.3 单链表打印 2.4 单链表尾插/尾删 2.4.1 单链表尾插  2.4.2 单链表尾删 2.5 单链表头插/头删 2.5.1 头插 2.5.2 头删 2.6 单链表查找 2.7 单链表中间插入/中

    2024年02月16日
    浏览(40)
  • 【算法与数据结构】链表

    链表是由一串节点串联在一起的,链表的每个节点存储两个信息:数据+下一个节点的地址 分清楚两个概念:什么是内存内部,什么是程序内部 内存内部: 信息存储在内存空间里的 程序内部: 通过什么信息,去操作结构 如果想操作链表的话,我们依靠的是程序内部的信息,

    2024年02月03日
    浏览(37)
  • 【数据结构与算法】链表

    对于顺序表存在一些缺陷: 中间/头部的插入删除,时间复杂度为O(N) 。头部插入需要挪动后面的元素 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插

    2023年04月09日
    浏览(33)
  • 【数据结构】链表 详解

    我们不废话,直入正题。 什么是链表? 来看看百度怎么说: 链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包

    2023年04月19日
    浏览(26)
  • 数据结构——链表详解

    new一个奶黄包:没关系,这条路我陪你走到底 单链表结构图 带头单循环链表结构图 双向循环链表结构图 带头双向循环链表结构图 链表特点 单链表在内存中,并不是连续存储的(逻辑上连续)。 不支持随机访问 插入时只需要改变指针指向 没有容量的概念 可以高效的在任意

    2024年02月12日
    浏览(26)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包