目录
结构体类型
首先回顾一下什么是结构体
struct Book
{
char name[20];
char author[20];
float price;
char id[13];
} b3, b4;//全局变量
struct Book b2;//全局变量
int main()
{
struct Book b1;//局部变量
return 0;
}
b1,b2,b3,b4 是用户定义的结构体变量名,不是类型名
struct是结构体类型的关键字
struct Book 是用户定义的结构体类型
name,author等 都是结构体成员名
结构体变量的创建和初始化
创建
创建一个学生结构体,存放姓名,年龄,性别,学号,如下
struct Stu
{
char name[20];//姓名
int age;//年龄
char sex[5];//性别
char id[20];//学号
//分号不能丢
};
初始化
初始化分两种,一种是按照结构体顺序,另一种是按照指定的顺序
#include<stdio.h>
struct Stu
{
char name[20];//姓名
int age;//年龄
char sex[5];//性别
char id[20];//学号
//分号不能丢
};
int main()
{
//按照结构体成员的顺序初始化
struct Stu s1 = { "zhangsan",20,"nan","232323232" };
printf("name: %s\n", s1.name);
printf("age: %d\n", s1.age);
printf("sex: %s\n", s1.sex);
printf("id: %s\n", s1.id);
//按照指定的顺序初始化
struct Stu s2 = { .age = 20,.id = "131313131",.sex = "nan",.name = "zhangsan" };
printf("name: %s\n", s2.name);
printf("age: %d\n", s2.age);
printf("sex: %s\n", s2.sex);
printf("id: %s\n", s2.id);
return 0;
}
匿名结构体类型
struct
{
char c;
int i;
double d;
}s = { 'x', 100, 3.14 };
int main()
{
printf("%c %d %lf\n", s.c, s.i, s.d);
return 0;
}
下面的代码,由于是匿名,编译器无法分辨这两个是不是同一个类型,所以会报错
struct
{
char c;
int i;
double d;
}s;
struct
{
char c;
int i;
double d;
}* ps;
int main()
{
ps = &s;
return 0;
}
但是可以用typedef对这个匿名结构体命名,不过这样很别扭
typedef struct
{
char c;
int i;
double d;
}S;
int main()
{
S s;
return 0;
}
结构体的自引用
想一下,是否可以在结构体中包含一个类型为该结构体本身的成员?
如下
struct Node
{
int data;
struct Node next;
};
如果该代码正确,那sizeof(struct Node)为多少?
这样是不合理的,如果一个结构体中再包含一个同类型的结构体变量,这样结构体变量的大小就会无穷大。
正确的自引用代码如下
struct Node
{
int data;
struct Node* next;
};
结构体中存在的内存对齐
对齐规则
1.结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
对齐数 = 编译器默认的一个对齐数 与 该成员变量大小的较小值!!!
- VS中 默认值为 8
- Linux中 gcc 没有默认对齐数,对齐数为该成员自己的大小
3.结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍
4.如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(包含嵌套结构体中成员的对齐数)的整数倍
来看如下代码
#include<stdio.h>
struct S1
{
char c1;//1
int i; //4
char c2;//1
};
int main()
{
struct S1 s = { 0 };
printf("%zd\n", sizeof(struct S1));
return 0;
}
得到的结果为12,我们看一下下面的图片,首先c1为第一个成员,放在0处,再看i,4 < 8故放在4的倍数处,也就是4~7,最后是c2 1 < 8 放在8处。
看规则3,此时最大对齐数为4,9、10、11都不是4的倍数,只有12是,故该结构体大小12
接下来
struct S2
{
char c1;
char c2;
int i;
};
int main()
{
struct S2 s2 = { 0 };
printf("%zd\n", sizeof(struct S2));
return 0;
}
c1,c2放在偏移量0,1处,i放在偏移量为4的倍数处,即4~7,则S2大小为8
struct S3
{
double d;//8
char c;
int i;
};
int main()
{
struct S3 s3 = { 0 };
printf("%zd\n", sizeof(struct S3));
return 0;
}
d为double类型,放在0~7偏移量处,c放偏移量8处,i需要放4的倍数即12~15处,那么S3的大小为16
最后是嵌套
struct S3
{
double d;//8
char c;
int i;
};
struct S4
{
char c1;
struct S3 s3;
double d;
};
int main()
{
struct S4 s4 = { 0 };
printf("%zd\n", sizeof(struct S4));
return 0;
}
c1 占1个字节,放偏移量0处,再看规则4,由于S3是嵌套,看S3的最大对齐数即8(double),所以S3对齐到8的整数倍处(嵌套的结构体,对齐到自己的成员中最大对齐数的整数倍处)即8~23处,让后double放24~31处,所以S4占32个字节
为什么有对齐规则
1.平台原因(移植原因)
不是所有的硬件平台都能访问任意地址上的任意数据的:某些硬件平台只能在某些地址处取某些特定类型的数据否则抛出硬件异常。
2.性能原因
数据结构(尤其是栈)应该尽可能地在自然边界上对交”原因在于,为了访问未对齐的内存,处理器需要作两次内存访问:而对齐的内存访问仅需要一次访问,假设一个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作来读或者写值了,否则,我们可能需要执行两次内存访问,,因为对象可能被分放在两个8字节内存块中
总体来说:结构体的内存对齐是全空向来换取时间的做法
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到既要满足对其,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起!!!
修改默认对齐数
#pragma pack(n)
#pragma pack()
第一行是将默认对齐数设置为n
第二行是将默认对齐数恢复为默认值
结构体传参
下面是传值和传参的代码对比
#include<stdio.h>
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 print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s);
print2(&s);
return 0;
}
两个哪个更好?
答案是传地址的(print2)
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大