🐵本篇文章将对结构体相关知识进行讲解
1.结构体🖥️
1.1结构体定义
结构体(struct)是用户自定义的数据类型,用于组合一个或多个不同类型的数据成员
1.2结构体的声明
这里直接以代码为例
1.3特殊的声明
不完全声明或者说匿名声明
在这种情况下,该结构体类型只能使用一次,在创建该结构体类型变量时只能如上图所示创建,以下几种情况均错误:
//1
struct
{
int age;
float score;
} a;
int main()
{
struct b;
printf("%d", b.age); //报错,因为struct类型已经在上面创建a变量时使用过一次
return 0;
}
//2
struct
{
int age;
float score;
};
int main()
{
struct b;
printf("%d", b.age);//由于是匿名结构体,编译器无法识别struct类型
return 0;
}
那如果这样写代码:
struct
{
int age;
float score;
} b;
struct
{
int age;
float score;
}* p;
int main()
{
p = &b;
return 0;
}
该代码定义了两个匿名结构体类型的变量,且两个结构体的内部成员相同,现将结构体变量b的地址赋给结构体指针p发现,编译器会报警告
也就是说编译器会将这两个结构体类型视为不同的类型,所以不建议这样写代码
在一般情况下不会使用匿名结构体,如果该结构体只使用一次可以用匿名结构体
1.4结构体变量的定义和初始化
struct Student
{
char name;
int age;
}s1; //在声明的同时定义变量,此时为全局变量
struct Student s2; //全局变量
int main()
{
struct Student s3 = { "sans",3}; //局部变量
return 0;
}
嵌套结构体的初始化和访问
struct Point
{
int x;
int y;
};
struct Node
{
int data;
struct Point p;
struct Node* next;
};
int main()
{
struct Node s3 = { 23, {2,3}, NULL };
printf("%d %d %d", s3.data, s3.p.x, s3.p.y);//先访问到结构体Point在访问其内部成员
return 0;
}
1.5结构体的内存对齐
1.5.1结构体大小的计算
接下来讲解如何计算结构体的大小,对于计算结构体的大小并非只是将其内部成员的大小加起来,而是通过结构体对齐规则来计算的:
- 1. 第一个变量在与结构体变量偏移量为0的地址(初始地址)处
这里介绍一个宏:offsetof:用来计算结构体成员相较于起始地址偏移量的
struct S1
{
char c1;
int i;
char c2;
};
int main()
{
printf("%d", offsetof(struct S1, c1));//结果为0
return 0;
}
- 2. 其他成员变量要对齐到对齐数的整数倍的地址处
对齐数 = 编译器默认的一个对齐数与该成员大小的较小值
vs的对齐数默认为8 - 3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
- 4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己内部成员的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
下面通过例题进行讲解:
struct S1
{
char c1;
int i;
char c2;
};
printf("%d\n", sizeof(struct S1));
首先c1是第一个成员,要将它放在结构体变量偏移量为0的地址处,下来i为整形,i的大小为4个字节,vs的默认对齐数是8,所以i的对齐数是8和4的较小值也就是4,要将i对齐到4的整数倍的地址处,就是下面偏移量为4的地址处,再下面是c2,c2的对齐数是1,要将c2对齐到1的整数倍的地址处,就是下面偏移量为8的地址处,此时整个结构体的大小为9个字节,规定结构体的总大小是最大对齐数的整数倍,该结构体的最大对齐数是4,所以应该是12个字节
struct S2
{
char c1;
char c2;
int i;
};
printf("%d\n", sizeof(struct S2));
c1是第一个成员,将它放在结构体变量偏移量为0的地址处,c2的对齐数是1,将它对齐到1的整数倍的地址处,i的对齐数是4,将他对齐到4的整数倍的地址处,此时整个结构体的大小是8个字节,正好也是成员最大对齐数4的整数倍,所以这个结构体的大小就是8给字节
struct S3
{
double d;
char c;
int i;
};
printf("%d\n", sizeof(struct S3));
d是第一个成员,将它放在结构体变量偏移量为0的地址处,c的对齐数是1,将它对齐到1的整数倍的地址处,i的对齐数为4,将它放在4的整数倍的地址处此时整个结构体的大小为16个字节,同时也是成员最大对齐数8的整数倍,所以结构体的大小就是16个字节
struct S4
{
char c1;
struct S3 s3;
double d;
};
printf("%d\n", sizeof(struct S4));
c1是第一个成员,要放在结构体变量偏移量为0的地址处,下来是一个嵌套的结构体,规则约定嵌套结构体要对齐到自己内部成员最大对齐数的整数倍处,其内部成员的最大对齐数是8,所以要对齐到8的整数倍的地址处,d的对齐数是8,要对齐到4的整数倍的地址处,此时结构体的大小为32个字节,正好是最大对齐数16的整数倍,所以结构体的大小就是32个字节
1.5.2结构体内存对齐的原因
1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特
定类型的数据。
2. 性能原因
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问
struct S1
{
char c;
int i;
};
上述代码中在没有对齐的情况下:
在对齐的情况下:
因此结构体的内存对齐可以理解为用空间换取时间的做法
但如果既要满足对齐又想节省时间,可以尽量将小的变量放在一起
1.5.3修改默认对齐数
结构在对齐方式不合适的时候,可以更改默认对齐数
#include<stdio.h>
#pragma pack(1) //将默认对齐数修改为1
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack() //修改后记得取消默认对齐数
struct S2
{
char c1;
int i;
char c2;
};
int main()
{
printf("%zd\n", sizeof(struct S1)); //6
printf("%zd\n", sizeof(struct S2)); //12
return 0;
}
1.6结构体传参
#include<stdio.h>
struct S
{
int data[100];
int num;
};
void print(struct S t)
{
printf("%d %d %d %d", t.data[0], t.data[1], t.data[2], t.num);
}
int main()
{
struct S s = { {1,2,3}, 10 };
print(s); //传值调用
return 0;
}
在函数调用时,形参是实参的一份临时拷贝,当我们传过去的结构体过于大时,形参也需要申请同样大的空间,除此之外还需要将结构体的数据一个一个传过去,既浪费时间也浪费空间,所以对结构体传参时通常会采用传址调用
#include<stdio.h>
struct S
{
int data[100];
int num;
};
void print(struct S* t)
{
printf("%d %d %d %d", t->data[0], t->data[1], t->data[2], t->num);
}
int main()
{
struct S s = { {1,2,3}, 10 };
print(&s); //传址调用
return 0;
}
🙉本篇对结构体的讲解结束,下一篇会对位段、枚举以及联合等相关知识进行讲解