1.1 结构体基础
1.2 结构体的声明
1.3 结构体的定义
1.4 结构体的自引用
1.5 结构体的储存
1.1 结构体基础
在结构体之前,我们已经熟知 int, char, short, long, float, double等, 用来修饰变量,但对于很多物品的属性,我们光用以上的类型修饰不了。比如 一个人的基本信息(姓名, 年龄, 身高, 体重), 一本书的基本信息 (书名, 作者, 价格)等,这时候就需要我们创建一个自定义类型: 结构体来修饰这些拥有多个属性的变量。
1.2 结构体的声明
因为结构体是我们自己创建, 使用时需要我们来进行声明,有哪些属性, 属性的类型为什么。
比如创建一个学生:形式为
struct stu
{
char name[20];// 姓名
char ID [15];// 学号
int age;// 年龄
char sex[5];// 性别
};//这里的分号不能省略
注:我们需要在使用前,进行声明
1.2.1 结构体的特殊声明
能否对声明结构体时,省略结构体的标签?
例
struct
{
char a;
int b;
float c;
};
在某种情况下,可以进行以上省略,但此代码会有缺陷
例
struct
{
char a;
int b;
float c;
}a;
struct
{
char a;
int b;
float c;
}*p;
p = &a
在这种情况下,是否合法?
警告:在这种情况下,编译器会把以上两种声明当做两个不同的个体,即使两个声明里,所有的类型一一对应,依然是非法的。
如仍要使用,只能声明一个这种结构体,并且只能使用一次。
但不建议使用这种省略结构体标签的代码。
1.3 结构体的定义
声明类型之后,该如何定义和使用结构体
struct stu
{
char name[20];
char id[15];
char sex[5];
int age;
}a; // 声明的同时,定义
// 定义结构体变量
struct stu b;
// 定义的同时, 初始化
struct stu c = {"zhangsan", "1234567", "nan", 20};
对于结构体, 可以使用嵌套定义
struct stu
{
int a;
int b;
};
struct node
{
int x;
struct stu p;
}i = {1, {3, 4}};
1.4 结构体的自引用
在结构体中, 是否能引用该结构体本身的成员?
答案是可以,但需要引用正确
struct stu
{
int a;
struct stu next;
};
// 是否可行?
如果结构体小,自引用次数少,语法是没有错误,但前面两个条件一旦数量增大,
sizeof(struct stu) 的大小为多少? 内存会被占用很大
所以不推荐上述代码
正确引用
// 正确引用
struct stu
{
int a;
struct stu *next;
};
指针的大小在不同环境下分别为 4 / 8,即使结构体大,自引用次数多,但因为是指针,自引用所用字节都为 4 / 8。
1.5 结构体的储存
我们已经知道怎么声明和定义结构体,但结构体是如何在内存中开辟空间的
#include <stdio.h>
int main()
{
struct p1
{
char a;
int b;
short c;
};
struct p2
{
int a;
char b;
short c;
};
printf("p1的大小%d\n", sizeof(struct p1));
printf("p2的大小%d\n", sizeof(struct p2));
return 0;
}
由运行结果知,对于结构体中使用了相同的类型,但空间大小不一样 。这其中有什么原因呢?
其实在这里面涉及到了结构体的内存对齐
1.5.1 结构体的内存对齐
对齐规则
1.第一个结构体成员始终在结构体变量偏移量为0的地址处。
2.其他结构体成员变量要对齐到某个数字(对齐数)的整数倍地址处。
对齐数 = 该成员的大小与编译器默认对齐数的较小值
vs中默认对齐数为 8
3.结构体大小为最大对齐数的最大整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
图示:p1
同时p2同理
这也是内存大小为什么不一样的原因
1.5.2 为什么要内存对齐
总体上:用空间换取时间
大部分参考资料表示:
1.平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特
定类型的数据,否则抛出硬件异常。
2.性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访
问。
1.5.3 修改默认对齐数
我们可以用#pragma pack( x ) (x为自己修改的对齐数)来修改
如需还原编译器默认对齐数, 只需 #pragma ()便可修改
1.6 结构体传参
有两种方式, 传值, 传址
#include <stdio.h>
struct stu
{
char name[20];
char id[15];
char sex[5];
int age;
};
void print1(struct stu p1)
{
printf("%s %s %s %d\n", p1.name, p1.id, p1.sex, p1.age);
}
void print2(struct stu* p1)
{
printf("%s %s %s %d\n", p1->name, p1->id, p1->sex, p1->age);
}
int main()
{
struct stu p1 = { "zhangsan", "1234567", "nan", 20 };
// 传值
print1(p1);
print2(&p1);
return 0;
}
print1 和 print2 均能正常编译
但在选择时, 应选择哪一种?
答案很显然, 选择print2(传址)
因为在结构体很大的时候,传值需要开辟一个相同空间大小的内存,所以结构体传值是很浪费内存的, 但传址,指针的大小永远为 4 / 8 个字节,不会占用更多的空间。
综上所述, 在结构体传参时,我们要选择传址,传指针。