前言

开始学数据结构了,但是今天写单链表的时候用到结构体的自引用的时候直接干麻了,然后出了一大堆错误,然后你得一个个解决,这个时候不如来复习一下结构体,那我就写一篇结构体的博客来复习一下结构体

路线

我们现在以这样的路线来分析结构体
路线

结构体

结构体的本质和使用

本质

结构体的本质就是一个库,里面可以放各种数据类型来着

  • 要注意一点就是:结构体也是一种数据类型!!!,那也就是说int,char这种基本数据类型能干的它也能干,比如创建一个结构体数组,来一个结构体指针啊啥的,甚至结构体里面拿一个结构体变量来当成员变量都没问题

代码

我们先声明一个结构体(<用到struct关键字>注意一定得是声明,那其实也就是说这个其实是写到.h头文件中的 )

  • 也就是说下面这些代码就像一个图纸一样告诉你这个东西应该是什么样子
struct person
{
	char name[10];
	int age;
	char sex[6];
	double height;
};  
  • 注意这里结束之后要加一个分号

那上述这些代码应该处在一个什么位置呢?答案其实和函数差不多了,你可以像放函数一样用它,那我直接给它放到头文件里面了,然后在我的.c文件里面创建结构体,代码如下

#include"struct.h"
int main()
{
	struct person p1;
	struct person p2 = { "张三",18,"男",188 };

	return 0;
}

你可以不给参数,也可以像上面一样给参数,用大括号给参数 ,这是结构体最基本的用法。

结构体变量的创建

碎碎念

除了刚刚我们直接用struct关键字来定义结构体在main函数创建结构体变量之外,结构体还有别的创建变量方法

代码

直接上代码

用typedef定义

这个的和刚刚那种的区别就是这次用typedef定义,但还是main函数实现

  • 这里直接放定义
typedef struct person1
{
	char name[10];
	int age;
	char sex[6];
	double height;
}pe1;
  • typedef这个关键字的作用就是给类型(比如基础类型int char等等,自定义类型如struct等等)起小名,在main函数里面我们就可以直接通过pe1来创建结构体变量了
#include<stdio.h>
struct person
{
	char name[10];
	int age;
	char sex[6];
	double height;
};
typedef struct person1
{
	char name[10];
	int age;
	char sex[6];
	double height;
}pe1;
int main()
{
	struct person p1;
	struct person p2 = { "张三",18,"男",188 };
	pe1 p3;
	return 0;
}

就像这样,上面两个结构体的定义都看过了,只看main函数就行,我们会发现一个点就是说typedef关键字的优点很明显,那就是不需要写struct这么多字了

全局创建

一看全局闯进啊,那他肯定和全局变量有着一样的缺点,包括不限于什么可读性差,耦合性差,可维护性差,一个全局变量出问题全遭殃那种,但是我们来看看它的创建方式。

struct person2
{
	char name[10];
	int age;
	char sex[6];
	double height;
}p4,p5;//在这里创建了全局的p4,p5
int main()
{
	p4.age = 10;
	printf("%d", p4.age);//打印p4的年龄
	return 0;
}

这里我们只使用一个p4,发现可以使用,创建成功

匿名结构体

匿名结构体就是结构体名是空的结构体

//匿名结构体
struct
{
	char name[10];
	int age;
	char sex[6];
	double height;
}p6;
struct
{
	char name[10];
	int age;
	char sex[6];
	double height;
}p7;

匿名结构体的特点是只能用一次 ,这里的只能用一次就是说这里直接就定义了p6和p7(也是全局的<如果在函数外定义的>),不能借助这个结构体继续定义了,你能使用p6和p7,像我们刚刚的person2虽然也定义了全局变量,但是它可以继续创建局部变量

struct person2 p8 = { "张三",18,"男",188 };
printf("%s", p8.name);

不过上面的p6,p7可以随便使用的,然后另外一个点就是p6,p7是属于不同类型的,相当于编译器为匿名结构体创建了两个随机类型

总结

感觉结构体的定义方式你只要会写一个就行,能看懂其他的也就够了,就算不会写直接开查

结构体的内存对齐

这个其实是挺重要的内容,是判断结构体占用的多少字节的内容

对齐规则

理论

结构体的偏移量就是当前位置的地址和首地址的距离
结构体的对齐规则
这个确实不好看,我们来一个实战,不如判断判断我们刚刚写的person占多少个字节
图片解析
double所占的字节你们来判断一下吧,最后实际上应该是32个字节(0~31偏移所对的字节)<这里正好是最大对齐数8的整数倍,不要忘了结构体大小是最大对齐数的整数倍>

代码实战
//这里依然是结构体的定义
struct person
{
	char name[10];
	int age;
	char sex[6];
	double height;
};
//这个函数的目的就是求结构体的大小
void test04()
{
	struct person p1;
	printf("person结构体的大小是""%zu", sizeof(p1));
}
//主函数运行
int main()
{
	test04();
	return 0;
}

以上的代码运行一下就有了,这个结构体的大小就是32字节

这个规则带来的缺点

但是你会发现中间空出了好多字节,难道这些字节也是结构体的?
答案就是确实是的,简单回答就是通过这个对齐规则来访问结构体的成员的速度很快,但是确实会出现这种内存缺陷,所以说这个是典型的空间换时间的做法
对齐的缺点

这个确实主播也忘了,主播只记得空间换时间了,豆包确实讲的很清楚,如果不对齐相当于给这个数据掰成两半了,然后重新拼接起来也需要浪费时间

pragma back(数字)

这个就是修改默认对齐数的代码,vs里面可能有,但是好多平台没有

总结

结构体的内存对齐策略挺重要的,到结构体的嵌套使用的时候,我们来分析一下嵌套结构体的大小,如果想要省一些内存我们就在设计结构体的时候我们就少让他留空。

结构体的自引用

我就是写单链表的时候栽到这里了才回来写这篇博客的

前言

我在写链表的时候写了这么一个代码,虽然当时已经感觉不对劲,但是看vs没报错就没放在心上,最后爆无数红字

typedef int Typename
typedef struct linkedlist
{
	Typename things;
	lkl* next;
}lkl;

好,可以看到我们给linkedlist起了一个别名lkl,然后我们里面有两个成员Typename(也就是int) things,lkl*next,我这个代码有什么问题。
思考
实际上这个代码出现了一个问题——先有蛋还是先有鸡,到底是我的别名lkl先声明还是lkl*next被创建,所以错了,这就是我犯错的写链表的问题

正确做法

那正确的结构体的自引用该如何写呢?上代码

struct linkedlist
{
	int things;
	struct linkedlist* next;
};

这里的第二个成员一定得是指针,你试试写成这样

struct linkedlist
{
	int things;
	struct linkedlist next;
};

爆红字先不说,你可以想象一下你给它一个sizeof(linkedlist),你再printf一下,你猜猜最后得多少。答案就是无限套娃

总结

OKOK,不废话了,还有一个嵌套使用然后分析一下结构体大小还有一个结构体的引用就结束了。

结构体的嵌套使用

嵌套结构体的内存对齐

顾名思义,就是结构体套结构体了。废话不多说,直接上代码
我定义这样一个结构体,那下面的person4占多少个字节,能不能分析一下它的内存

struct person3
{
	char name[10];
	int age;
	double height;
};
struct person4
{
	char name[10];
	int age;
	struct person3 p8;
};

person4的对齐细节
这里给你们放验证代码

//结构体定义
struct person4
{
	char name[10];
	int age;
	struct person3 p8;
};
//创建结构体并且得知其大小
void test05()
{
	struct person4 p9;
	printf("person4结构体的大小是""%zu", sizeof(p9));
}
int main()
{
	test05();
	return 0;
}

验证下来我们就是对的

写法

其实你会发现就是在结构体里面创建一个嵌套结构体变量了。

结构体的引用

想使用结构体需要用到两个操作符,叫做成员访问操作符

->.

结构体一共有两种访问方式:指针访问和直接访问

直接访问

实际上我刚刚偷偷用过,在上面的全局创建里面打印p4年龄的变量

//结构体的定义
struct person4
{
	char name[10];
	int age;
	struct person3 p8;
};
//创建变量并且使用
void test06()
{
	struct person4 p9;
	p9.age = 10;
	printf("p9的年龄是""%d", p9.age);
}
int main()
{
	test06();
	return 0;
}

我们只需要

  1. 先创建结构体变量
  2. 用结构体变量.结构成员这个形式就可以直接访问到结构体中的成员

指针访问

继续上代码

//结构体的定义
typedef struct person1
{
	char name[10];
	int age;
	char sex[6];
	double height;
}pe1;
//改变年龄的函数
void changeage(pe1*p11)
{
	p11->age = 10;
	printf("%d\n", p11->age);
	(*p11).age = 11;
	printf("%d\n", (*p11).age);
}
//调用函数并且创建变量的代码
void test07()
{
	pe1 p10;
	changeage(&p10);
	printf("%d\n", p10.age);
}
int main()
{
	test07();
	return 0;
}

这也就是结构体指针的使用方法,使用这个来访问成员

p10->age

其实就等价于我上面写的(先解引用再进行调用)

(*p10).age

总结

结构体的引用其实和那些数据类型差不了多少了,也就那样了一般般洒洒水了。

结构体传参

原则就是结构体比较大就尽量用指针
结构体比较小无所谓,是形参还是指针变量都无所谓

总结

结构体没有那么难,等到学cpp天天写class创建对象,然后就熟了,他会跟你讲struct也能写对象

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐