细解结构体

本文详细介绍了C语言中的结构体创建、变量定义、typedef重命名以及结构体成员的访问方式。通过实例展示了结构体在函数参数传递中的两种方式,强调了指针传参的效率优势。此外,文章还深入探讨了内存对齐的规则,包括无嵌套结构体和含有嵌套结构体的情况,以及如何利用offsetof和sizeof宏进行验证。
摘要由CSDN通过智能技术生成

结构体创建

作用

在前面的类型学习中我们学习了浮点数,整形,字符型,数组……等类型
虽然这些在我们日常生活中已经够用了,但是在有一些情况中无法使用,那就是需要用到各个类型的整合
比如:我们的手机通讯录就是这个类型
在这里插入图片描述
在这个通讯录中有多个号码,而每个号码都有联系人和号码,发现号码是多个类型的整合。
于是我们就需要创建一个变量,具有字符串类型和数组类型。

定义方法

定义类型

根据上面的号码需求,我们需要创建一个号码变量

struct phone
{
	char name[11];
	int number[11];
};

这个phone就是我们自定义的结构体
按照我们的要求,成员是字符串类型和数组类型组成。

这里我们创建了关于这个号码的变量,这个时候就可以研究如何命名号码1.2.3

变量创建

当我们创建这个类型的号码时,有两种方法,一种是在结构体类型创建后立马跟上变量名

演示:

```c
struct phone
{
	char name[11];
	int number[11];
}闰土,迅儿;

我们可以见到在花括号后,跟着闰土和迅儿两个变量名
此时我们
已经创建好两个具有名字和号码数字的两个联系人了。


第二种方法则是在结构体外创建,这里我们可以发现
一个在主函数内创建
还有一个在主函数外创建

struct phone
{
	char name[11];
	int number[11];
};
struct  phone lalala = { "迅儿",15867831733 };

#include<stdio.h>
int main()
{

	struct phone lala = { "闰土",13177835676 };

	return 0;
}

重命名(补充)

int main()
{

	struct phone lala = { "闰土",13177835676 };

	return 0;
}

我们发现每次创建新变量时,都要加上struct这个前缀,未免有一些繁琐,这个时候我们就可以运用到typedef
这个能大大方便我们对结构体的使用

typedef struct phone
{
	char name[11];
	int number[11];
}sb;
struct  phone lalala = { "迅儿",15867831733 };

#include<stdio.h>
int main()
{

	sb lala = { "闰土",13177835676 };

	return 0;
}

这里面我们将struct phone重命名为了sb变量
既方便又好记,可谓一举两得。

应用

访问结构体成员

这里我们直接上方法:

->只用于结构体指针访问成员;
.点只用于结构体名访问成员

. 访问成员方式

typedef struct phone
{
	char name[11];
	int number[11];
}sb;
#include<stdio.h>
int main()
{

	sb lala = { "闰土",13177835676 };
	sb lalala = { "迅儿",15867831733 };
	printf("%s", lala.name);
	return 0;
}

我们在这上面通过lalala这个结构体名访问到了其中的成员内容。

->的访问方式

typedef struct phone
{
	char name[11];
	int number[11];
}sb;
#include<stdio.h>
int main()
{

	sb lala = { "闰土",13177835676 };
	sb lalala = { "迅儿",15867831733 };
	sb* z = &lala;
	printf("%s", z->name);
	return 0;
}

这里我们创建了struct phone的指针变量z,并且把lala的地址存入了z中,这里我们看到->能访问结构体指针的成员们

传参

当我们自定义需要结构体变量传参时,我们这时就有两个传参方式。

结构体传参:

typedef struct phone
{
	char name[11];
	int number[11];
}sb;
#include<stdio.h>
void test(sb sss)
{
	printf("%s", sss.name);

}
int main()
{
	sb lala = { "闰土",13177835676 };
	sb lalala = { "迅儿",15867831733 };
	test(lala);
	return 0;
}

这里我们直接将sss这个结构体直接传入至函数中。
所以访问时直接用.进行访问

结构体指针传参:

typedef struct phone
{
	char name[11];
	int number[11];
}sb;
#include<stdio.h>
void test(sb* sss)
{
	printf("%s", sss->name);

}
int main()
{
	sb lala = { "闰土",13177835676 };
	sb lalala = { "迅儿",15867831733 };
	test(&lala);
	return 0;
}

这里我们将指针传参传了过去。
所以访问成员用->

这两种传参方式推荐用第二种,将整个结构体放入参数中大小不可控,但是将指针传参传入,就4个字节,将整个结构体掌握手中,岂不美哉?

内存对齐

无结构体嵌套

内存对齐是结构体中最重要的考点,通过结构体内存的成员类型来判断整个结构体在内存中占用几个字节。
在无结构体嵌套时,内存对其通过以下原则

1:结构体第一个成员,对齐到结构体在内存中存放位置的0偏移处。
2:第二个成员开始,每个成员都要对齐到(一个对齐数)的整数倍处
对齐数:结构体成员自身大小和默认对齐数的较小值
VS:默认对齐数 8
Linux gcc:没有默认对齐数,对齐数就是结构体成员自身大小。
3: 结构体的总大小必须是所有成员的对齐数中最大对齐数的整数倍。

就比如:

struct test
{
char a;
int b;
char c;
char name[10]};

这里我们可以使用结构体内存对齐的原则来对结构体的大小进行计算。
首先我们来假设
在这里插入图片描述

方格假设为划分的内存,而零则是开始放入结构体的地方

首先是原则一
:放入第一个元素时,直接(结构体的放置内存的开始处)零处直接放入

接下来就让我们落实到图上
在这里插入图片描述
黄色处就是char变量a存入的地址。

接下来是则是引用法则二:
第二个成员开始,每个成员都要对齐到(一个对齐数)的整数倍处 对齐数:结构体成员自身大小和默认对齐数的较小值
博主这里用的是vs编译器,默认对齐数是8,就用vs环境下和大家讲解了。
在这里插入图片描述
b是int类型,字节大小为4,相对于默认对齐数8较小,所以对齐数是4/4的倍数
所以这里将b从4开始放入,存储至字节7处。

char c的对齐数是1.所以1的倍数即可。因此直接放在内存8处即可。

在这里插入图片描述

接下来我们看到char数组中
数组的对齐数不是看数组总大小,而是看数组中的元素大小
再有了这个法则后
我们直接将char数组放入即可,因为每个对齐数都是1的倍数
在这里插入图片描述
这个时候就需要用到最后一个法则
结构体的总大小必须是所有成员的对齐数中最大对齐数的整数倍
这里面我们重新看图
在这里插入图片描述
就会发现其对齐数为: 1,4,1,1
发现最大对齐数为4
因此总的结构体大小的对齐数应该为4的倍数。
整个结构体的大小在19处结束,所以最符合的4的倍数为20

最后我们可以用宏offsetof(查看结构体每个成员在内存中的偏移量)和sizeof进行判断

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

struct foo
{
	char a;
	int b;
	char c;
	char name[10];
};

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

在这里插入图片描述

这里发现和我们的推算一样。

结构体嵌套

接下来就是比较复杂的情况了,就是结构体中有其他结构体嵌套
还是老套路,先讲法则:

如果结构体中嵌套了结构体成员,要将嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍。
结构体的总大小必须是最大对齐数的整数倍
这里的最大对齐数是:包含嵌套结构体成员中的对齐数

typedef struct zzz
{
	double f;
}z;
struct foo
{
	char a;
	int b;
	char c;
	z;
};

在这里插入图片描述

前面的内容我们已经分析过了,接下来就着重对结构体的部分进行分析
首先分析这个结构体的成员的最大对齐数:可见是double的8

如果结构体中嵌套了结构体成员,要将嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍。

从这个原则就可以知道,结构体z的对齐数应是16,(8被占用,8的最小倍数是16)
我们知道结构体的放入位置后,通过我们上面的学习可以知道这个结构体总大小为8,接下来放置即可

在这里插入图片描述
接下来就剩最后一个步骤了
确定总大小,让我们来看最后一条原则

结构体的总大小必须是最大对齐数的整数倍
这里的最大对齐数是:包含嵌套结构体成员中的对齐数

这里我们看到最大的对齐数是double的8.
内存占有到了24,而8的对齐数也是8/8的倍数
所以我们可以确定这个结构体总大小为24
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

想学c啊啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值