结构体
目录
- 结构体类型的声明
- 结构体的自引用
- 结构体变量的定义和初始化
- 结构体内存对齐
- 结构体传参
- 结构体实现位段(位段的填充&可移植性)
结构的声明:
#include <stdio.h>
int main()
{
//描述一个学生
struct Stu
{
char name[20];
int age;
char sex[5];
char id[20];
};//分号很重要,不能丢
}
结构体的自引用(结构体的嵌套使用):
#include <stdio.h>
int main()
{
//描述一个学生
struct Stu
{
char name[20];
int age;
char sex[5];
char id[20];
struct Stu* next;//结构体的自引用
};
}
结构体变量的定义和初始化:
#include <stdio.h>
int main()
{
//描述一个学生
struct Stu
{
char name[20];
int age;
char sex[5];
char id[20];
}Stu1;//声明类型的同时定义变量 Stu1
struct Stu p2;//定义结构体变量 Stu2
struct Stu Stu3 = { "lisi",666,"male","xatu" };//结构体成员的初始化:定义变量的同时赋初值
}
结构体嵌套初始化:
#include <stdio.h>
int main()
{
struct Teacher
{
int age;
char id[10];
};
//描述一个学生
struct Stu
{
char name[20];
int age;
char sex[10];
char id[20];
struct Teacher W;
struct Stu* next;//结构体的自引用
};
struct Stu Stu1 = { "wangwu",888,"male","xatu",{88,"xatu"} };//结构体的嵌套初始化
}
结构体内存对齐(比较重要):
首先了解一下为什么要有内存对齐:
一、平台原因:不是所有硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则会抛出硬件异常;
二、性能原因:数据结构(尤其是栈)应该尽可能的在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要做两次内存访问;而对其的内存访问仅需要一次访问。
就啥意思呢,就是说,结构体的内存对齐是拿空间来换时间的做法
首先记住结构内存对齐的四大原则:
- 基本数据类型自身对齐值(下面已列出)。
- 自定义类型的自身对其值:结构体或类的成员中自身对其最大的值。
- 程序的指定对齐值:即#pragma pack(value)时的指定对齐值value,当结构对齐方式不合适的时候,我们可以自行更改默认的对齐数。
- 程序的有效对齐值:自定义类型的自身对齐值和指定对齐值中较小的值。
牢记基本数据类型的自身对其值:
char类型: 1字节
short类型: 2字节
int 和 float: 4字节
double类型:8字节
举个栗子:
#include <stdio.h>
int main()
{
//不同的编译器都有一个默认的对齐数,在这里我用的是VS2019,它的默认对齐数是8,当然你也可以自己指定对齐数 #pragma pack(value),value 即为指定对齐值
struct Teacher
{
int age; // 4 int 型占4个字节
char id; //1 + 3 char 型占1个字节
};//给char后 +3这样的话这个结构体总共占的字节数是 8 ,根据对齐原则,(1 + 3)是4的倍数,(1+3+4)= 8是8的倍数,此时达到内存对齐
由上面举的这个栗子我们可以看到,在设计结构体的时候,如果我们让占用空间小的成员尽量集中在一起的话,是不是可以既满足对齐又可以节省空间,不明白的话没关系,再来一颗栗子:
#include <stdio.h>
int main()
{
struct Teacher
{
double id; //8
char sex; //1 + 3
int age; // 4 int 型占4个字节
char id; //1 char 型占1个字节
};//一共占了17个字节
}
#include <stdio.h>
int main()
{
struct Teacher
{
char id; //1
char sex; //1 + 2
int age; //4
double id; //8
};//一共占了 16 个字节
}
我们可以看到,在成员都一样的前提下,下边的结构体的成员排序方式时比上边的更节省空间的 。
结构体传参:
#include <stdio.h>
void print1();
void print2();
struct Stu
{
int age;
char id[20];
};
struct Stu stu1 = { 19 ,"54188" };
int main()
{
print1(stu1);
print2(&stu1);
return 0;
}
void print1(struct Stu stu1)
{
printf("%d\n", stu1.age);//结构体传参
}
void print2(struct Stu *stu1)
{
printf("%d\n", stu1->age);//结构体地址传参
}
上面两种结构体传参方式都是可以的,但我们更推荐下边以传递结构体地址进行传参的方式,因为上边这种传参方式,因为函数传参时,参数是需要压栈的,有时间和空间上的系统开销。
位段
位段的声明和结构是类似的:
- 位段的成员必须是 int ,unsigned int ,或 sigend int。
- 位段的成员名后边有一个冒号和一个数字。
举个栗子:
#include <stdio.h>
#include <stdlib.h>
int main()
{
//Stu即为一个位段类型
struct Stu
{
int a : 2;
int b : 5;
int c : 10;
int d : 30;
};//Stu的大小为 8 个字节
printf("%d\n", sizeof(struct Stu));
system("pause");
}
位段和结构相比,尾端可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
枚举
枚举枚举,顾名思义,把可能的取值一一列举,被列举的元素叫做枚举常量。
枚举类型的定义:
enum Day
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
需要注意的是:1、枚举的分割用逗号;2、第一个元素不初始化默认为0, 且一次递增1。也可以在定义的始后赋初值。
枚举的使用:
enum Day
{
Mon,
Tues,
Wed=4,
Thur,
Fri,
Sat,
Sun
};
enum Day a = Thur;//枚举变量的赋值
a = 5;//重新赋值
注意:只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
联合体
联合体:一种特殊的自定义类型,这种类型定义的变量也包含一系列的成员,他的特征是这些成员公用同一块空间(所以联合体也叫共用体)。上栗子:
#include <stdio.h>
int main()
{
union A
{
char age;
int id;
};
union A a;//联合体变量的定义
printf("%d\n", sizeof(a));
//大小为4
}
大小为4,联合体的特点就是,它的成员是共用同一块内存空间的,所以一个联合体的大小,至少是最大成员的大小。再一个就是,当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
union A
{
char c[5];//5
int id; //4
};
printf("%d\n", sizeof(union A));
//联合体大小为8