Bootstrap

C语言之结构体

欢迎拜访雾里看山-CSDN博客
本篇主题:C语言之结构体
发布时间:2025.1.11
隶属专栏C语言

在这里插入图片描述

结构体类型的声明

结构体是一些值的集合,这些值称为成员变量,结构体的每个成员可以是不同类型的变量。
结构体内部的成员可以是标量、数组、指针、也可以是其他结构体。

声明

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

例如用结构体描述一个朋友

typedef struct friend
{
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char number[20]//电话号码
}friend;//结尾分号不能丢

特殊的声明
在对结构体进行声明的时候, 可以进行不完全声明。

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

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

上面的两个结构体在声明的时候,省略了结构体的标签

若对于以上代码执行p = &x;语句,则会报错。因为编译器会认为这是两个不同的结构体。

结构体的自引用

在结构体中包含一个类型为该结构体本身的结构体成员是否可以?

struct Node
{
	int data;
	struct Node next;//包含自身
};

不可以!! 不明白的可以想一下sizeof(struct Node)的结果是多少。上述代码会在初始化结构体的时候报错。

正确的引用方式如下:

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

结构体变量的定义和初始化

struct Point
{
	int x;
	int y;
}p1;//声明类型的同时定义变量P1;
struct Point P2;//直接定义变量P2;

//初始化:定义变量的同时赋初值
struct Point P3 = { x, y};

struct Friend
{
	char name[20];
	int age;
};
struct Friend f = {"zhangsan", 20};//初始化

struct Node
{
	int data;
	struct Point P;
	struct Node* next;
}n1 = {10, {4, 5}, NULL};//结构体嵌套初始化

struct Node n2 = {20, {3, 4}, NULL};//结构体嵌套初始化

结构体成员访问

结构体变量访问内部成员是通过点操作符 . 对内部成员进行访问。点操作符接受两个操作数。
例如:
结构体f
当我们需要访问结构体内的成员nameage 时,可以进行如下操作

struct Friend f;
strcpy(f.name, "lisi");//使用.操作符访问name成员
f.age = 21;//使用.操作符访问age成员

在实际应用中,我们更多的时候是遇到结构体的指针,这时我们可以进行如下操作。

struct Friend
{
	char name[20];
	int age;
};

void Print(struct Friend *pf)
{
	printf("name = %s   age = %d\n", (*pf).name, (*pf).age);
	//使用结构体指针访问指向对象的成员
	printf("name = %s   age = %d\n", pf->name, pf->age);
}

int main()
{
	struct Friend f = {"zhangsan", 20};
	Print(&f);
	return 0;
}

结构体传参

我们仔细观察下面的代码。

struct Friend
{
	char name[20];
	int age;
};
//结构体传参
void Print1(struct Friend f)
{
	printf("name = %s   age = %d\n", f.name, f.age);
}
//结构体指针传参
void Print2(struct Friend *pf)
{
	printf("name = %s   age = %d\n", pf->name, pf->age);
}

int main()
{
	struct Friend f = {"zhangsan", 20};
	Print1(f);//传结构体
	Print2(&f);//传结构体指针
	return 0;
}

在实际应用中,传结构体和传结构体指针哪个更好一点?

在函数传参的时候,参数是需要压栈的,如果传递结构体,当结构体太大的时候,参数压栈的系统开销会很大,结果则会导致性能的下降。
结论: 传结构体指针更优。

结构体内存对齐(重要部分)

结构体对齐的规则

  1. 第一个成员在与结构体变量偏移量为 0 的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    对齐数 = min(编译器默认对齐数, 该成员大小)
  3. 结构体总大小为最大对齐数(每一个成员变量都有一个对齐数)的整数倍。
  4. 如果嵌套了结构体, 嵌套的结构体先确定自己的大小,再按照上面的规则计算。

常见结构体的计算

以在VS编译器下为例(默认对齐数是8)

struct S1
{
	char c1;//编译器默认对齐数为8, 该成员大小1,对齐数1 
	int i;//编译器默认对齐数为8, 该成员大小4,对齐数4
	char c2;//编译器默认对齐数为8, 该成员大小1,对齐数1 
};
printf("%d\n", sizeof(struct S1));//最终大小为12
struct S2
{
	int i;//编译器默认对齐数为8, 该成员大小4,对齐数4 
	char c1;//编译器默认对齐数为8, 该成员大小1,对齐数1 
	char c2;//编译器默认对齐数为8, 该成员大小1,对齐数1 
};
printf("%d\n", sizeof(struct S2));//最终大小为8
struct S3
{
	double d;//编译器默认对齐数为8, 该成员大小8,对齐数8 
 	char c;//编译器默认对齐数为8, 该成员大小1,对齐数1 
 	int i;//编译器默认对齐数为8, 该成员大小4,对齐数4 
};
printf("%d\n", sizeof(struct S3));//最终大小为16
struct S4
{
 	char c1;//编译器默认对齐数为8, 该成员大小1,对齐数1 
 	struct S3 s3;//编译器默认对齐数为8, 该成员大小16,对齐数8 
 	double d;//编译器默认对齐数为8, 该成员大小8,对齐数8 
};
printf("%d\n", sizeof(struct S4));//最终大小为32

在这里插入图片描述

为什么存在内存对齐

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

总体来说 : 结构体的内存对齐是拿空间换取时间的做法。

在设计的时候,要让占用空间小的尽量在一起,这样即满足对齐,又可以节省空间。

struct S1
{
	char c1;
	int i;
	char c2;
};
struct S2
{
	char c1;
	char c2;
 	int i;
};

S1和S2内部成员函数相同,但是S2所占的空间却小于S1。

除此之外,还可以修改默认对齐数

修改默认对齐数

在结构体对齐方式不合适的时候, 我们可以自己更改默认对齐参数。
使用#pragma这个预处理命令可以更改我们的默认对齐数。

#include<stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
struct S2
{
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int mian()
{
	printf("S1 = %d\n", sizeof(struct S1));
	printf("S2 = %d\n", sizeof(struct S1));
	return 0;
}

⚠️ 写在最后:以上是我对C语言结构体部分的一些学习后的总结, 如有错误或者需要补充的地方欢迎各位大佬私信我交流!!!

;