结构体出现的意义
当我们形容人时要创建变量,仅仅靠一个int类型,或者char类型是不够,这个时候出现了结构体!
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量,
我们就可以定义多个变量来表示人的各种特征。
结构体的声明
tag是我们的结构体名称,也就是我们所谓的模板名称,mem-list是结构体里面的成员,variable-list是我们通过结构体声明出来的变量,这是一般的命名方式
struct tag
{
member-list;
}variable-list;
若要对学生使用结构体命名,如下:>
struct Student
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}; //记住';'一定不能丢
不完全的声明(特殊的命名)
命名的时候,可以不打出结构体名称,如:
struct
{
int a;
char b;
float c;
}x;
想这样的没有结构体名称,就是不完全声明(匿名声明)
结构体重命名
防止结构体命名名称过长,可以用typedef关键字对结构体重命名,例如:
typedef struct Student
{
char name[20];
int age;
char sex[5];
char id[20];
}stu;
虽然之后可以用stu x代替struct student x,简洁代码,却降低了代码的可读性,对于stu自己知道,但是他人看不懂,建议尽量少用typedef。
结构体变量的定义和初始化
接下来该怎么定义结构体变量和如何初始化结构体
struct point
{
int x;
int y;
}p1; //声明类型时同时定义变量p1
struct point p2;//定义结构体变量p2
struct point p3 = { 10, 20 };//定义结构体变量p3时同时初始化
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = { 10, {4,5}, NULL };//对于结构体嵌套性的初始化
方法主要有两种一种是在声明结构体的时候就对其定义并且初始化,还有一种就是我们单独对其进行定义和初始化。
结构体大小的计算
struct S1
{
char c1;
int x;
char c2;
}
会不会天真的认为S1的存储内存为6个字节,那要结构体大小计算这么简单的话,就没必要讲下去了,想要搞清楚结构体内存的容量问题,需要先了解结构体的内存对齐问题
结构体内存对齐
- 规则 :>
- 第一个成员在与结构体变量偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的值为8 - 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
现在来讲解规则(在vs环境对齐数为8)
规则1:> 对于S1的c1的变量存储方式
规则2:> int 对齐数为4,4<8,即int x的对齐数为4
因为下一个成员偏移量要是该成员的对齐数的整数倍,变量x内存就要对齐为4的偏移量,导致1到3的偏移量出现浪费。
规则3:>
在这里储存c2后,结构体大小总为9字节不是最大对齐数的整数倍,所以要继续向后申请内存空间达到最大对齐数的整数倍,该结构体最终总大小为12字节。
结构体嵌套形的内存计算
struct S3
{
double d;
char c;
int i;
};
//结构体嵌套
struct S4
{
char c1;
struct S3 s3;
double d;
};
- 3条规则之外,还有1条面对结构体嵌套的情况:
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
利用三条规则可以算出S3的总大小为16字节,那么我们S4的结构体大小该如何计算呢?
就要用到第四条规则。char 的对齐数是1,然后就是结构体的对齐数了根据第四点我们知道结构体的对齐数就是嵌套结构体的最大对齐数也就是8,所以就是8+16了又因为double的对齐数是8,同时占8个字节,也就是8+16+8=32了,又因为32是最大对齐数8的整数倍所以该结构体的大小就是32。
修改默认对齐数
可以手动修改对齐数,通过#pragma这个预处理指令改变对齐数。
#pragma pack(1)//设置默认对齐数为1
struct S2
{
char c1;
int i;
char c2;
};
算出来的结构体总大小为6字节
结构体传参
struct point
{
int x;
int y;
};
struct point s = { 2, 1 };
//传实参
void print1(struct point s)
{
printf("%d\n", s.x);
printf("%d\n", s.y);
}
//传形参
void print2(struct point* ps)
{
printf("%d\n", ps->x);
printf("%d\n", ps->y);
}
int main()
{
printf1(s);
printf2(&s);
return 0;
}
虽然看起来打印出来都一样,而第一种当面对结构体比较大的时候,会传整个结构体,这将会开辟很多的内存,空间上会造成浪费,而采用传地址的方式,能大大的节省空间。