在之前我们已经学习过结构体的一些基本知识,现在我们对结构体进行更深的了解
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量(注意:数组和结构体是一样的,也是一种值的集合,但是数组的每个元素的类型是相同的,而结构体的每个成员可以是不同类型的)
在使用结构体之前我们要声明一个结构体类型,这样我们就必须要用到结构体关键字struct,结构体的声明如下:其中tag是用来区分不同结构体类型的标签,struct tag是结构体类型,member-list是结构体的成员,variable-list是变量列表,可以写可不写,写了就代表你用上面所创建的结构体类型定义了一个该类型的变量,没写则表示你仅仅只创建了一个结构体类型
struct tag
{
member-list;
}variable-list;
注意:
1.若结构体声明是在mian函数之外的,那么列表后创建的结构体变量是一个全局变量
2.结构体成员变量也可以是另一个结构体类型,这种用法被称为:嵌套结构体
3.在结构体声明结束的时候,千万不要忘记{}后面还有一个;因为这是一条语句
在声明完了之后,我们就要开始定义和初始化结构体变量
例子如下:
struct s2
{
int high;//身高
double weight;//体重
};
struct student
{
char name[20];//姓名
int age;//年龄
char set[5];//性别
char id[20];//学号
struct s2;//嵌套了一个结构体
};
int main()
{
struct studet s = { "张三",20,"男","2321583",{170,65.5} };//定义和初始化
return 0;
}
如果结构体类型的名字过长,我们可以使用typedef来对结构体类型的名字进行重命名使它缩短
typedef struct student
{
char name[20];
int age;
char set[5];
char id[20];
struct s2;
}s1;
这里的s1就是对struct student进行重命名的结果
在结构体中还有一种特殊声明,就是不完全的声明,这种结构我们称为匿名结构。如下所示:
struct
{
int a;
char b;
float c;
}x;
这种声明出来的结构体类型我们只能使用一次,之后就再也不能使用它
还有一点就是两个匿名结构体类型的成员变量是一模一样的,但是在编译器看来是两个不同类型的结构体。如下所示:
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], * p;
我们都知道数据在内存中是连续存放的,那在一个结构中包含一个类型为该结构本身的成员是否可以呢?
方法1:在一个结构中包含一个类型为该结构本身的成员
struct Node
{
int date;
struct Node next;
}next;
当我们使用sizeof()来计算这个结构体的大小,又是多少呢?
struct Node
{
int date;
struct Node next;
}next;
int main()
{
printf("%d\n", sizeof(struct Node));
return 0;
}
从这里我们可以看到c语言是不支持这种方法的,因为编译器也无法得知该结构体类型的大小呀,每个结构体套一个自身,这种思想不就是函数递归操作(没有限制条件的)
方法2:在结构中包含一个指向类型为该结构自身的指针
struct Node
{
int date;
struct Node* next;
}next;
这种方法通过sizeof()计算是可行的,因为这样设计的结构体类型的第二个成员变量本质上就是一个指针,而指针是4/8个字节
在我们学了结构体之后,我们还要知道结构体在内存中是如何存储的接下来我们通过一个例子来观察
从这里我们可以看出s1和s2的成员变量的个数和类型是一样的,只是顺序不一样的情况下,计算出来的大小也是完全不一样的。这里我们就不得不说结构体在内存中的存放方式了(也就是结构体内存对齐)
结构体内存对齐的规则:
1.第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的值为8
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
在VS编译器中存在一个宏offsetof(type, member),可以用来计算一个成员在结构体类型所创建的变量中的偏移量是多少。其中type是指想要被计算的结构体类型,member是该结构体中的成员。在使用offsetof()前应该先引用一个头文件
通过上面的讲解,我们发现结构体在内存中存储的时候会浪费空间来使得结构体成员在某些边界处对齐,那为什么要存在内存对齐呢?
1.平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常
2.性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问
总体来说:
结构体的内存对齐是拿空间来换取时间的做法。
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起
在vs这个编译器中默认对齐数(8)是可以进行编译修改的,可以通过#pragma预处理指令来进行修改。
在以上代码中我们选择print2函数进行传参要好一些,因为函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,会导致性能的下降,所以结构体传参的时候,要传结构体的地址。