本篇将讲解结构体的基本知识,及其的注意点和基本使用
目录
结构体
结构体是一种自定义类型的数据,数组也是一种自定义类型,可以用数组作类比理解结构体,不过数组里的成员只能定义个数和类型,而结构体内可以定义任意类型的成员,也就是结构体内可以装任意类型的数据,并且这些数据可以是不同类型的
结构体的关键字是 struct
结构体定义
struct tag
{
member-list;
}variable-list;
tag 是自己写的一个标签(用来表示这个结构体是关于什么的)结构体类型就为 strcut tag
tmamber-list 是成员列表,括号里就是结构体里包含哪些类型的数据(注意:这里只是在定义结构体是怎么样的,只需要声明它有哪些类型的数据,不需要给这些数据创建变量)
variable-list 是在定义结构体时顺便也创建类型为这个结构体的变量,可以一次性创建多个,在这里创建是则创建的一个全局变量,当然也可以在其他地方创建
注意:结构体定义最后面的 ' ; ' 号不可以省略
特殊的声明的结构体:省略掉标签(tag),此为匿名结构体,若没有对此结构体类型重命名的话基本上只能使用一次
例:
struct student - 定义一个关于学生信息的结构体,信息包含学生的年龄、名字、学号
{
int age;
char name[50];
int ID;
}a,b,c; - 并创建三个类型为 struct student 的变量
int main()
{
struct student d; - 再创建一个局部变量 d
}
结构体变量的定义和初始化
像上面所说, 定义中 variable-lsit 就是变量的创建,且为全局变量
其他变量定义:struct tag(类型) s1(变量名) = {};放函数里即为局部变量,s1 即是一个变量,类型为 struct tag
变量的初始化分两种:默认顺序初始化 和 指定顺序初始化
例:
struct student
{
int age;
char name[50];
int ID;
}a = {17, "zhangsan", 101},b,c; - 创建全局结构体变量 a 的同时并将它按默认顺序初始化
int main()
{
struct student d = { .ID = 103, .age = 20, .name = "lisi"}; - 按我们自己指定的顺序初始化
}
结构访问操作符
点操作符:.
箭头操作符:-> (由一个减号(-) 和 大于符号(>) 组成)
作用:
直接访问:结构体成员的直接访问是通过点操作符(.)访问
点操作符接受两个操作数 —— 结构体变量 . 成员名
间接访问:结构体指针 -> 成员名
例:
struct student
{
int age;
char name[50];
int ID;
}a,b,c;
int main()
{
struct student d;
stuct student* pd = &d; - 创建指针变量 pd,并取出 d 的地址赋值给 pd
pd->age = 17; - 指针 pd 通过箭头操作符访问其指向的结构体变量的成员 age,并赋值 17
d.name = "头发化码"; - 变量 a 通过点操作符访问其成员 name,并赋值 头发化码
}
通过上面的例子,你一定对结构访问操作符有一定理解了,不过其实这个例子里面有个地方是错误的,这也是使用结构访问操作符需要注意的一个小细节,具体如下
在之前变量的定义和初始化中,我举了一个这样的例子:
struct student d = { .ID = 103, .age = 20, .name = "lisi"}; - 这段代码是没问题的
刚刚上面的例子中又有这样一段代码:
d.name = "头发化码"; - 其实这段代码是错误的
注意 .name = "lisi" 和 d.name,为什么前面一个是对的,而后面一个是错误的呢,这里并不是因为 . 前面有差异而导致错误的,而是两段代码所处位置导致含义的不同,前者是在创建结构体变量的同时将结构体的成员初始化,这时这些结构体成员里的值就是这些初始值,而后者是通过点操作符访问到 name 这个成员,而 name 是一个数组,它在这里表示的是首元素地址,首先它是指针常量,是不可以赋值的,其次就算能赋值,也不能达成我们想要的效果,因为他只是一个地址,而不是我们想要的地址内的空间
当然,这个注意点只针对数组成员,因为其他类型数据的变量名在不同地方表示的含义是相同的,只有数组的变量名是要分情况确定它的含义,想具体了解的小伙伴可以点击我看”数组名“的内容
结构体内存对齐(计算结构体大小)
结构体是存在一个叫内存对齐的规则的,它规定了结构体内的成员是如何分配空间的,并决定了整个结构体的大小
在讲解对齐规则前先补充一个对齐数的知识:
对齐数 = 编译器默认的一个对齐数与该成员变量大小的较小值(VS 中默认为 8,Linux 中 gcc 没有默认对齐数,对齐数就是成员自身大小),比如一个 int a,它的大小为 4(字节),在 VS 当中默认对齐数为 8,4 < 8,所以成员 a 的对齐数为 4
对齐规则:
1.结构体的第一个成员对齐到结构体起始位置偏移量为 0 的地址处
2.其他成员对齐到其对齐数的整数倍的地址处
3.结构体总大小为最大对齐数(这个结构体所有成员中最大的)的整数倍
4.若嵌套了结构体,这个嵌套的结构体成员的对齐数为其自己成员中的最大对齐数
这里我们举两个例子:
例一
例二
内存对齐存在原因
1.平台原因(移植原因):不是所有硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些特定地址处取某些特定类型的数据。例:只能从 4 的倍数的地址上取整数
2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐,因为对于未对齐的内存,处理器需要进行两次内存访问。例:假设一个处理器总是从内存中取 8 个字节,也就是处理器是按 8 个字节的内存一块一块处理的,若一个数据分布在两个块中,处理器则需要取出这两个块才能取出这个数据
总体来说:结构体内存对齐是拿空间换取时间的做法
修改默认对齐数
预处理命令:#pragma pack()
修改默认对齐数为括号里的数,若不填则还原为默认对齐数
最后,这里再附上其他几个自定义数据类型 :