自定义类型:结构体 (C语言)

目录

一. 结构体类型的声明

1.1结构的声明 

1.2 结构体变量的创建和初始化

1.3 结构的特殊声明

1.4 结构的自引用

1.5  结构体内存对齐

 


一. 结构体类型的声明

在我们前面的学习过程中,我们也提到过结构体,结构体是一些值的集合,这些值称为成员变量,而且每个成员可以是不同类型的变量。

1.1结构的声明 

struct tag
{
 member-list;//成员变量
}variable-list;变量列表

假如我们来描述一个学生:

struct student
{
	char name[20];//名字
	char sex[5];//性别
	int age;//年龄
	float grade;//成绩
};

像上面这样,我们的一个简单的结构体变量就已经创建好了,下面就讲讲我们如何使用以及如何初始化我们的结构体。

1.2 结构体变量的创建和初始化

struct student
{
	char name[20];//名字
	char sex[5];//性别
	int age;//年龄
	float grade;//成绩
};
int main()
{
	struct student s1 = { "乐乐","女",18,98 };
	printf("%s", s1.name);
	printf("%s", s1.sex);
	pritnf("%d", s1.age);
	pritnf("%d", s1.grade);
	return 0;
}

 对于我们之前的学习,我们知道调用结构体的成员可以使用变量名加上. 的方式,或者示使用箭头的方法,上面的代码我们是按照结构体成员变量的顺序来写的,但是如果我们不想按照这样的顺序,可以试试这样来写:

int main()
{
	struct student s2 = { .age = 18, .name = "lisi", .grade = 98, .sex = "女" };
	printf("%s ", s2.name);
	printf("%s ", s2.sex);
	printf("%d ", s2.age);
	printf("%d ", s2.grade);
	return 0;
}

综合上面的内容,我们可以总结很多比如,在C语言中,字符串字面量可以直接初始化字符数组,但是整数类型不能用这种方式初始化,我们就直接写18、98等,另外也要注意我们的潜在的缓冲区溢出风险,就比如我们的char name[20],我们可以考虑适当增加数组的大小以避免潜在的缓冲区溢出风险。还有就是如果我们不按照结构体成员顺序来写的话,我们记得在初始化的时候要用对方式,以上就是我们结构体的简单介绍,下面我们来继续学习新的内容。 

1.3 结构的特殊声明

什么是结构体的特殊的声明?其实也很容易理解,比如:

struct
{
	int a;
	char b;
	int c;
}s1,s2;

struct
{
	int a;
	char b;
	int c;
}*pa;

上面的代码我们也很容易发现它缺少了结构体名称,也就是说我们不能再像往常一样使用结构体创建多个变量,而只能使用我们在创建结构体时候加上的变量,比如上述代码中的s1,s2,我们再设想假如这个时候我们写pa=&s1;是否正确呢,答案是不正确的,因为编译系统会认为这是两个不一样类型的内容,即使它们是一样的结构体,所以匿名的结构体类型,如果没有重新命名的话,基本上只能使用一次

1.4 结构的自引用

我们思考一个问题就是在结构体中包含一个类型为该结构本身的成员是否可以呢?
我们在学习数据结构的时候会学习链表等内容:

如上图所以,我们如果要找到每一个数字的位置,我们就需要一个链表的东西,我们能够继续找到下一个数字的位置,现在我们假设代码这样去写:

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

仔细观察上面的代码是正确的吗?如果认为正确那么sizeof(struct Node)的大小是多少呢?结果表明它是无穷大的,计算不出来,所以正确的自引用应该是使用指针的方式来找到下一个数字的位置:

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

在结构体自引用使用的过程中,夹杂了 typedef 对匿名结构体类型重命名,也容易引入问题,看看 下面的代码,可行吗?

typedef struct
{
	int data;
	Node* next;
}Node;
答案是不行的,因为Node是对前面的匿名结构体类型的重命名产生的,但是在匿名结构体内部提前使用Node类型来创建成员变量,这是不行的。解决方案如下:定义结构体不要使用匿名结构体了
typedef struct Node
{
 int data;
 struct Node* next;
}Node;

 可能我们会想不是已经给struct Node重命名为Node了吗,为什么下面还是要用struct Node,原因也是很简单的,我们认真观察我们使用Node重命名是针对整个结构体来说的,那么在这个结构体中原本就先包含的是struct Node这个整体,所以如果我们在结构体中直接使用了Node是很危险的。

1.5  结构体内存对齐

什么是结构体的内存对齐呢?在认识这个之前我们要先学习计算结构体的大小,也是很热门的一个考点就是结构体内存对齐对齐规则

1. 结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的⼀个对齐数与该成员变量大小的较小值。
比如:VS 中默认的值为 8
           Linux中 gcc 没有默认对齐数,对齐数就是成员自身的大小
3. 结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的
整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构
体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
下面给大家举出一些例子来实际操作一下:
#include <stdio.h>
struct S1
{
	char c1;
	int i;
	char c2;
};
struct S2
{
	char c1;
	char c2;
	int i;
};
int main()
{
	printf("%zd\n", sizeof(struct S1));
	printf("%zd\n", sizeof(struct S2));
	return 0;
}

在上面的代码中我们大胆先自己计算一下,那么我们自己来计算的话S1和S2的大小应该是多少呢?可能大家会想这不就是6吗?我们来看一下结果:

这就奇怪了竟然不是6,而且两个结果竟然还不一样,究竟是为什么呢?这就要说说我们的内存对齐规则了,那么究竟是怎样去理解和使用的呢?先给大家一张图,方便我的后面讲解: 在这张图的基础上我来给大家讲解,首先我们要认识一个宏——offsetof,offsetof(type,member)头文件是#include <stddef.h>它是计算结构体成员较于结构体变量起始位置的偏移量:

#include <stddef.h>
struct S2
{
	char c1;
	char c2;
	int i;
};
int main()
{
	printf("%zd\n", offsetof(struct S2,c1));
	printf("%zd\n", offsetof(struct S2, c2));
	printf("%zd\n", offsetof(struct S2, i));

	return 0;
}

 也就是说我们的c1相较于起始位置偏移量为0,c2为1,i为4,这中间有什么原理吗,其实很简单我们来看对齐规则的第一条:结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处,所以我们的c1偏移量是0,而对于c2来说我们看第二条:其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数与该成员变量大小的较小值。 比如:VS 中默认的值为 8。VS中的对齐数是8,而8和char类型的1比较,自然是1小,所以选择1,那么1的倍数可以是任何数,所以对应上面的图,也就是橙色的,我们的第二个变量c2可以直接在c1下面直接挨着,但是对于i来说,int类型的4也是较小值,但是必须对齐为4的倍数,所以要选择偏移量为4的倍数,所以橙色的整个占了8个字节,也就是我们得出的结果8,但是为什么红色的占了9个字节,但是结果确是12呢?我们来看第三条:结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍。在S1中int的类型是4个字节但是实际占了9个字节,但是不是4的倍数,所以只能是4的3倍,所以结果就是12。

 

  • 13
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值