前言
初识C语言的码友们一定学过C语言中的基本数据类型,比如我们常见的整型,浮点型,字符型等等基本数据类型,但是对人或物这一类复杂对象的数据要如何进行存储呢?这个知识点就涉及到了本篇博客所主要讲解的用来存储复杂对象的结构体类型。
目录
1.结构体声明及结构体变量定义
结构体声明格式如下:
以人为例:
结构体的匿名类型:
//匿名结构体测试
struct {
int a;
char b;
float c;
}x;
struct{
int a;
char b;
float c;
}a[20],*p;
int main() {
//p和&x是否为同一类型,我们来测试一下
p = &x;
return 0;
}
由图可知,这两个结构体声明不为同一类型
结构体声明完毕后,可定义该结构体类型的变量,结构体变量定义:
//结构体变量定义
struct Person {
char name[20];//姓名
int age;//年龄
char address[20];//地址
}P1;//声明同时定义全局变量
struct Person P2;//单独定义全局变量
//定义同时赋初值
struct Person P3 = { "zhangsan",21,"四川" };
那嵌套结构体该如何定义和初始化呢?其实不难,定义一个结构体类型时,其中一个成员变量为另一个已经定义的结构体变量,如:
//声明并定义嵌套结构体类型
struct Room {
int Room_id;//房间号
float width;//宽
float height;//高
};
struct Library
{
char name[20];//名字
char address[20];//地址
struct Room room;//图书室
}L1 = { "图书1002","湖南",{"101",122.1f,62.1f} };//直接定义并初始化
//分开定义并初始化
struct Library L2 = { "图书1003","四川",{"102",122.1f,62.1f} };
2.结构体自引用
首先我们来举个例子,比如数据结构中的链表,每个结点都是同一结构体类型,但是怎么才能让它在逻辑上实现连续呢?所以这肯定会涉及到结构体的自引用。这里应该有些码友会想到在结构体声明中定义下一个该结构体变量,如下:
//测试结构体自引用
struct ListNode
{
int val;
struct ListNode next;//下一个结点
}L;
那结构体变量L的字节大小是多少呢?我们用sizeof(L)计算一下,结果如下:
程序发生错误,由此可见此方法不可构成链表。原因一是下一结点在调用struct ListNode此结构体类型时该结构体类型还未完全成功定义。原因二是当计算结构体变量L的字节时大小它会无限自引用,会造成溢出错误。所以这种方法不可取。我们可以这样定义(在结构体变量L中定义一个结构体变量指针指向下一个该结构体变量,这样就避免了这两种错误):
3.结构体大小计算(内存对齐->对默认对齐数进行修改)
先来考考大家嘿嘿,如下结构体的大小为多少字节?(专业型选手禁止发言):
//S1的大小
struct S1 {
char c1;
int i;
char c2;
};
//S2的大小
struct S2 {
char c1;
char c2;
int i;
};
没了解过这方面知识的小朋友肯定会认为S1,S2大小一样,c1,c2各占一个字节,i占4个字节,那S1,S2的字节大小为1+1+4=6。但是真的那么简单吗?其实不然,结构体计算大小还有一定的对齐规则,如图:
由图可知,S1的大小为12个字节,而S2的大小为8。它们的大小并不相同。那我们在这三条对齐规则的基础上再练如下结构体:
//S3的大小
struct S3 {
double d;
char c;
int i;
};
这里就不再画图了,直接根据三条规则进行计算S3的大小为8+1+3+4=16,大家实在不懂也可以直接代入我上面例题1的图。这时候大家肯定有些疑问:嵌套结构体的大小应该如何计算呢?其实还有一条关于嵌套结构体的对齐规则,如图:
以上就是结构体大小的计算方法。结构体的内存对齐就是拿空间换时间的做法,只要我们在设计结构体时让占用空间小的成员变量尽量集中在一起就能既满足对齐规则又节省空间了!(就像S2一样)不过在VS中默认对齐数是可修改的哟!#pragma pack(对齐数) 设置默认对齐数,#pragma pack()是还原默认对齐数
4.结构体传参
先请大家看看以下代码:
struct S
{
int data[1000];
int num;
};
struct S s={{1,2,3,4},1000};
void print1(struct S s)
{
printf("%d\n",s.num);
}
void printf2(struct S* ps)
{
printf("%d\n",ps->num);
}
两个函数皆实现结构体S中num的打印,但是不同的是一个是传结构体,一个是传结构体指针。那哪种函数效率更高呢?这时候大家可能会想到一个知识点,形参是实参的一个临时拷贝,等于说当我们传入的结构体大小尤其大的时候,可能会导致性能的下降,所以我们选print2更好一些。