欢迎拜访:雾里看山-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};//结构体嵌套初始化
结构体成员访问
结构体变量访问内部成员是通过点操作符 .
对内部成员进行访问。点操作符接受两个操作数。
例如:
当我们需要访问结构体内的成员name
和 age
时,可以进行如下操作
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;
}
在实际应用中,传结构体和传结构体指针哪个更好一点?
在函数传参的时候,参数是需要压栈的,如果传递结构体,当结构体太大的时候,参数压栈的系统开销会很大,结果则会导致性能的下降。
结论: 传结构体指针更优。
结构体内存对齐(重要部分)
结构体对齐的规则
- 第一个成员在与结构体变量偏移量为 0 的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = min(编译器默认对齐数, 该成员大小) - 结构体总大小为最大对齐数(每一个成员变量都有一个对齐数)的整数倍。
- 如果嵌套了结构体, 嵌套的结构体先确定自己的大小,再按照上面的规则计算。
常见结构体的计算
以在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
为什么存在内存对齐
- 平台原因(代码的可移植性):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件只能在某些地址处取某些特定类型的数据,否则则会抛出异常。
- 性能问题:数据结构(尤其是栈),应该尽可能的在自然边界上对齐原因在于,为了访问未对齐的内存,处理器需要做两次内存访问; 而对齐的内存只需要访问一次即可。
总体来说 : 结构体的内存对齐是拿空间换取时间的做法。
在设计的时候,要让占用空间小的尽量在一起,这样即满足对齐,又可以节省空间。
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语言结构体部分的一些学习后的总结, 如有错误或者需要补充的地方欢迎各位大佬私信我交流!!!