结构体,看这一篇就足够了

本文详细介绍了C语言中的结构体,包括结构体的声明、自引用、定义与初始化、内存对齐以及结构体作为函数参数的传递。通过实例解析了结构体在内存布局中的特点,特别是结构体大小受内存对齐规则的影响。此外,还讨论了结构体传参时通常采用传址方式的原因。
摘要由CSDN通过智能技术生成


前言

我们为什么需要结构体,结构体有什么用?因为在现实生活中有很多复杂的事物,并不能用基本的数据类型来很好的表示出来,为了满足实际需求,才有了结构体这一自定义数据类型。


一、结构体

1.什么是结构体?

结构体是一些值的的集合,这些值被称作成员变量。结构体的每一个成员可以是不同类型的变量。

2.结构体的声明

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

例如,我们要描述一个学生,学生的信息有名字,年龄,学号。

struct Stu
{
	char name[20];
	int age;
	char id[15];
};

3.特殊结构体的声明

结构体中有一种特殊的结构体,叫作匿名结构体,该结构体在声明的时候省略了结构体的名称。

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

在使用匿名结构体的时候,我们不能再定义结构体变量,只能使用声明时已经定义的变量x来使用该结构体。
如果有两个匿名结构体,他们的成员变量相同,例如:

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

struct 
{
	int a;
	char b;
	float f;
}* p;//*p是结构体指针,p指向该结构体。

在这种情况下,我们能否用 *p = &x?
答案是不能的,虽然他们的成员变量相同,但编译器把他们当作两个不同的类型。所以是非法的。

4.结构体的自引用

如果学过C语言的数据结构链表的知识,我们就能发现,链表的结点就用到了结构体的自引用。
结构体自引用的方式有三种:

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

这三种方式都是正确的,可以在自己编译器上尝试编译一下。
但下面两种方式都是错误的:

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

这种声明是错误的,这种声明实际上是一个无限循环,成员Node是一个结构体,Node的内部还有一个结构体,循环往复。在分配内存的时候,无法确定该结构体的大小,因此是非法的。

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

这种写法看似是对的,实际上使用typedef重命名的类型名是从语句结束开始的,在结构体内部并不能使用。
我们在使用结构体自引用的时候,之所以用到结构体指针,是因为指针的大小是确定的,在32位机器上是4字节,62位机器上是8字节,所以在内存分配的时候,就有明确的大小。

5.结构体的定义和初始化

结构体的定义有多种方式,例如在声明结构体的同时,就定义了结构体变量,也可以在函数内部定义结构体变量。

struct A
{
	int a;
	char b;
	float c;
}a1,a2;//a1,a2是全局变量,定义在{}外部
struct A a3;//全局变量
int main()
{
	struct A a4,a5;//局部变量,定义在{}内部。
	return 0;
}

结构体的初始化

struct Point
{
 	int x;
 	int y; 
 }p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
struct Point p3 = {3,4};//定义变量的同时初始化。
//结构体变量的初始化也可以不按照声明的变量顺序来
p2 = {.y = 4, .x = 3};//也是可以的,可以自己去试一试。

结构体的嵌套初始化

struct Point
{
	int x;
	int y;
};
struct line
{
	struct Point p1;
	struct Point p2;
	char type[20];
};

struct line l1 = { { 2,3 } , { 7,1 } , "straight line"};

6.结构体的内存对齐

结构体内存对齐是一个及其重要的考点
下面有两个结构体,你认为它们的大小是什么?

struct A
{
	char c1;
	int a;
	char c2;
};
struct B
{
	char c1;
	char c2;
	int a;
};

可能有一些不太了解这个知识点的同学,可能就简单的认为c1是char类型占一个字节,a是int类型,占四个字节,c2是char类型占一个字节,加起来是6个字节,是这样的么,我们看一下程序运行的结果:

struct A
{
	char c1;
	int a;
	char c2;
};
struct B
{
	char c1;
	char c2;
	int a;
};

int main()
{
	printf("%d", sizeof(struct A));
	printf("%d", sizeof(struct B));
	return 0;

}

在这里插入图片描述
我们可以看到程序运行的结果,结构体A的大小是12个字节,结构体B的大小是8个字节。
这是为什么呢?
这就要引出结构体内存对齐这个知识点了。
结构体对齐有几个规则,我们把这些规则理解了,结构体的大小也就不那么难以计算了。

  1. 第一个成员在与结构体变量偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍偏移量的地址处。
    对齐数 = 编译器默认的一个对齐数 与 该成员大小(字节数)的较小值。
    VS默认是8。
  3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

了解了这几个规则,我们再来看一下上面的例子。
在这里插入图片描述
关于结构体B的字节大小,大家可以自己去尝试画一下。
下面我们来看一下存在结构体嵌套的结构体的大小该如何计算。

struct S3
{
 	double d;
 	char c;
 	int i;
};
struct S4
{
 	char c1;
 	struct S3 s3;
 	double d;
};

在这里插入图片描述

7.结构体传参

结构体传递参数,一般采用传址的方式,因为传参,如果结构体过于庞大的话,传参的时候要临时拷贝一份,会导致性能的下降。

总结

关于结构体的内存对齐,我们也可以采用#pragma这个预处理命令来改变默认对齐数,具体操作方式如下:

#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
 	char c1;
 	int i;
 	char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认

有关结构体更多的细致的知识,因为篇幅原因,就不在这里和大家一一赘述了,希望大家都能好好学习C语言,每天都能更近一步。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值