今天就先直奔主题…
结构体:是一些值的集合,这些值称为成员变量。结构体的每个成员可以是不同类型的变量。
声明:(举个结构体名为student的例子)
struct student
{
char sna[10];//姓名
char sex[6];
int sno;//学号
};
特殊的声明(匿名结构体)。声明的时候,可以不完全的声明(省略了结构体名,如此例的student)。
例如:
struct
{
char sna[10];//姓名
char sex[6];
int sno;//学号
}a;
struct
{
char sna[10];//姓名
char sex[6];
int sno;//学号
}a[30],*p;
因为编译器会把上面两个声明当成两个完全不同的类型,所以如果用第二个结构体变量的p指向第一个结构体的a的话是错误的(p=&a;×)。
结构体的成员
结构体的成员可以是变量,数组,指针,也可以是结构体。
结构体成员的访问
结构体变量访问成员通常通过点操作符(.)访问的。
如:
struct student a;
a.sno=12345;
当然,如果是指针的话,可以使用操作符(->)来访问成员。
如:
struct student *p;
p->sno=4567;
结构体的自引用:在结构体中包含一个类型为该结构体本身的成员。
例如:
struct student
{
char sna[10];//姓名
char sex[6];
int sno;//学号
struct student *p;
};
但是
struct student
{
char sna[10];//姓名
char sex[6];
int sno;//学号
struct student p;
};
这样是错误的,为什么呢?想一下如果这么定义了,student的第三个成员p是student类型的,p内有三个成员,第三个成员是struct student p; p又是什么呢?是student类型,有三个成员,第三个成员还是struct student p; 这样会无限循环下去,到底要分配多少空间呢,我们不知道,编译器更不知道。因此编译器不会让我们这样定义的。而第一个结构体用指针定义是没问题的,指针占4个字节,所占空间的固定的。
额外提示下,结构体第三个成员切勿为了省略直接写为student *p; 应该写全为 struct student *p;如果省略了struct,student是未声明的标识符。
关于结构体变量的定义和初始化
struct student
{
char sna[10];//姓名
char sex[6];
int sno;//学号
};
struct student a = { "张三","男",1111 };
也可以
struct student
{
char sna[10];//姓名
char sex[6];
int sno;//学号
}struct student a = { "张三","男",1111 };
嵌套初始化
struct student
{
char sna[10];//姓名
char sex[6];
int sno;//学号
};
struct teacher
{
char tna[10];//姓名
char sex[6];
int tno;//学号
struct student a;
};
struct teacher a= { "张老师","男",111, { "张三", "男", 123 } };
接下来就是结构体部分的重中之重------内存对齐
在讲之前,当然要先说明一下内存对其规则啦。
- 第一个成员在结构体变量偏移量为0的地址处,即在首地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数与该成员类型大小的较小值。 VS中默认的值为8,Linux中的默认值为4。
- 结构体总大小为最大对齐数(每个成员变量除了第一个成员都有一个对齐数)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自身的所有成员最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
为什么存在内存对齐? - 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取
某些特定类型的数据,否则抛出硬件异常。 - 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说:结构体的内存对齐是拿空间来换取时间的做法。
因此在设计结构体时让占用空间小的成员尽量集中在一起,这样就能做到既满足内存对齐,又能节省空间。
举例:
struct student
{
char sna[10];//姓名
char sex[6];
int sno;//学号
};
struct student
{
char sna[10];//姓名
int sno;//学号
char sex[6];
};
两个结构体内容相同,只是第二个成员和第三个成员交换了下位置,但它们所占内存大小确是不相同的。为两个结构体定义变量,并使用sizeof对变量求值,就可发现第一个为20字节,而第二个为24字节。
对于第一个结构体
sno[10]从0位置处开始存储,占了10个字节大小。而sex[6]是char类型的,char类型大小是1,而编译器默认是8,看内存对齐第二个规则,因此选择较小值1。
sex[6]要对齐到对齐数整数倍处,地址10处是1的倍数,所以sex[6]从10这里开始占6个字节。
而sno是int型,自身大小是4,和系统默认对齐数相比,较小值是4,此时地址到了16处,16是4的整数倍,因此sno从16这里开始存储,占4个字节,地址到了20。
这三个变量最大对齐数是4,20是4的倍数,满足内存对齐的第三个规则,所以总大小为20字节。
对于第二个结构体
sna[10]在存储完后,地址到了10。
第二变量是sno,是int型,对齐数是4。看内存对齐规则,地址10不是4的倍数,要找到4的倍数,向后找到12,是4的整数倍,所以sno从第12开始存储,占四个字节到了16。
sex[6]是char类型,地址16满足规则,所以sex从16开始存储,占6个字节。此时地址到了22,但22不是最大对齐数4的整数倍,向后找到24满足,因此该结构体占内存24字节。
在第二个结构体的地址10到12,22到24并没有存储内容,为了内存对齐,可以说是浪费掉了,但这样做所带来的好处却是系统对内存存取数据效率的提高,相信大家对内存对齐所带来的好处和结构体成员尽量将小的放在一起有了更深的了解了。
为了验证自己是否真的了解,请问ret的值…
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int ret;
struct student
{
char sna[10];//姓名
int sno;//学号
char sex[6];
};
struct teacher
{
char tna[10];//姓名
struct student a;
char sex[6];
int tno;//学号
}var;
ret = sizeof(var);
return 0;
}
答案:ret=48,你做对了吗?
关于结构体传参:尽量传递结构体变量的地址,函数用指针接收。因为函数传参在栈上开辟空间,如果直接传递结构体,在栈上就要开辟对应的空间,若结构体过大的话,大大加重了系统的负担。而传递地址的话,就节省了内存,加快了系统运行速度。
##最后,此文章有什么不足之处,欢迎各位在评论区指出错误,提出宝贵的意见。当然,有什么不懂之处,欢迎提问,私信给我…每天都在线的呦!