本章内容
结构体
- 结构体类型的声明
- 结构体的自引用
- 结构体变量的定义和初始化
- 结构体内存对齐
- 结构体传参
正文开始
结构体
结构体的声明
结构的基础知识
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。(数组是相同数据类型的变量集合)
结构体的声明
声明一个结构体类型:声明一个学生类型,是想通过学生类型来创建学生变量(对象)
描述学生:属性--名字、电话、性别、年龄。
特殊的声明
在声明结构体的时候,可以不完全的声明。
比如
#include<stdio.h>
//匿名结构体类型
struct
{
char sex[10];//性别
int age;
}x;
//匿名结构体指针
struct
{
char sex[10];//性别
int age;
}* psa;
结构体的自引用
正确的自引用方式:
//用代码表示上图数据结构
struct Node
{
int data; //数据域
struct Node* next; //定义相同数据类型的结构体用指针来表示下一个结构体
};
定义结构体,重命名结构体不能用匿名
结构体变量的定义和初始化
结果
案例:结构体里面访问另一个结构体里面的变量
结果
结构体内存对齐
考点如何计算?首先得掌握结构体的对齐规则
(重点就是确定对齐数首位置放在那里)
1.第一个成员在:与结构体变量偏移量为0的地址处。
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数=编译器默认的第一个对齐数与该成员大小的较小值。
Vs中默认的对齐数值是8。
3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
举例:
图解
struct S3 这里浪费了三个字节,10-12位置处
结构体Struct s4
为什么存在内存对齐?
1.平台原因(移植原因),不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于为了访问未对齐的内存。处理器需要做两次内存访问;而对齐的内存访问仅需要一次访问。
总的来说
结构体的内存对齐是拿空间来换取时间的做法,原因就是你在访问的时候可以直接顺着读,不用再返回去重新查找。
那在设计结构体的时候,我们既要满足对齐又要节省空间,如何做到呢?
答:让占用空间小的成员变量尽量集中在一起。
如图代码所示
//S1和S2类型的成员变量一样,但是S1和S2所占空间大小不同
//显然通过分析S1更节省内存
#include<stdio.h>
struct S1
{
char c1;
char c2;
int i;
};
struct S2
{
char c1;
int i;
char c2;
};
int main()
{
struct S1 s1;
struct S2 s2;
printf("%d\n",sizeof(s1));
printf("%d\n",sizeof(s2));
return 0;
}
运行结果
修改默认对齐数
#pragma pack(1) //设置默认对齐数为1
#pragma pack(2) //设置默认对齐数为2
....
#pragma pack() //取消设置的默认对齐数为
案例:
如果不修改时浪费7个字节
#include<stdio.h>
struct S1
{
char c1;
//浪费 7字节
double i;
};
int main()
{
struct S1 s1;
printf("%d\n",sizeof(s1));
return 0;
}
运行结果
修改默认对齐数后
#include<stdio.h>
#pragma pack(1) //设置默认对齐数为1
struct S1
{
char c1;
double i;
};
//#pragma pack() //取消设置的默认对齐数为
int main()
{
struct S1 s1;
printf("%d\n",sizeof(s1));
return 0;
}
结果
结构体传参
在主函数里面传参
案例1
#include<stdio.h>
//创建结构体 S
struct S
{
int a;
char c;
double d;
};
int main()
{
//创建结构体变量
struct S su={0}; //初始化
su.a=10;
su.c ='a';
su.d = 3.13;
printf("%d %c %lf\n",su.a,su.c,su.d);
return 0;
}
调用函数传参,按值传,按地址传
案例2
//结构体传参
#include<stdio.h>
//创建结构体 S
struct S
{
int a;
char c;
double d;
};
//初始化函数按址传递(最好按址传递,不要按值传递)
void Init(struct S* ps)
{
ps->a=100;
ps->c='w';
ps->d=9.98;
};
//传值
void Print1(struct S tmp)
{
tmp.a=20;
tmp.c='q';
tmp.d=5.33;
printf("%d %c %lf\n",tmp.a,tmp.c,tmp.d);
};
//传址
void Print2(struct S* ps)
{
ps->a=7;
ps->c='h';
ps->d=1.2;
printf("%d %c %lf\n",ps->a,ps->c,ps->d);
};
int main()
{
//创建结构体变量
struct S su={0}; //初始化
Init(&su);
Print1(su); //传结构体
Print2(&su); //传地址
return 0;
}
运行结果
上面的案例2 print1和print2函数哪个好些?
答案,是首选print2。原因是
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销.
如果传递一个结构体对象的时候,结构体扩大,参数压线的系数开销比较大,所以会导致性能的下降。
结论:结构体传参的时候,要传结构体的地址。