详解自定义类型:结构体、枚举、联合体
前言:C语言中声明了很多的数据类型,比如:char、int、float等等几乎涵盖了所有的单一类型。但是实际运用中不难发现,我们描述一件事物的时候通常会使用多种类型的数据。比如描述一本书:我们要知道书名,页码,和价格,这里就使用了字符型、整型和浮点型数据。但是在C语言中并没有一种类型可以直接储存这三种类型的数据,因此就引入了自定义类型,由使用者自己定义想使用的类型。
一、结构体
结构体类型是不同类型数据的集合,这些数据被称做成员变量
结构体关键字:struct
struct tag//结构体标签
{
member-list;//成员列表
}variable-list;//变量列表
结构体的特殊声明
结构体在声明的时候可以进行不完全声明:
struct //缺少结构体标签
{
name[20];
}s1;
声明缺少结构体标签的结构体,被称做匿名结构体。因为缺少结构体标签,导致我们无法通过上述的结构体类型在创建新的变量。使用场景:同种类型的变量,我们只需要使用一个的情况下会使用匿名结构体,保证类型的唯一性。
结构体的自引用
创建一个结构体类型,将该结构体类型作为自身的结构体成员是否可行?
struct s1
{
int a;
struct s1 ss;
}
很显然,这种情况是不可以的。我们知道程序的运行是线性的,当程序运行到struct s1 ss这行语句时,需要知道struct s1的类型,但是struct s1类型里面包含了struct s1这个结构体类型,无限循环下去,会引起程序崩溃。
那我们怎么做才能实现结构体的自引用呢?
struct s1
{
int a;
struct s1* pss;
};
答案是使用指针,创建一个结构体指针类型。
结构体变量的定义和初始化
struct S1
{
int i;
char c;
double d;
}s1;//定义结构体类型的同时,创建一个变量s1
struct S1 s2;//创建结构体变量s2
struct S1 s3 = { 4, 'a', 3.14 };//创建结构体变量s3的同时对s3进行初始化
struct S2
{
char name[20];
int age;
struct S2* ps2;//结构体自引用
}s4 = {"zhangsan", 10, NULL};//初始化结构体类型的同时创建结构体变量s4并对其初始化
结构体内存对齐
计算结构体的大小,需要用到内存对齐:
1.将结构体中的第一个成员变量放在偏移量为0地址处
2.从第二个成员变量开始,将成员大小和成员变量的对齐数进行比较,放置在偏移量为较小数的倍数的地址上。(每个编译器都默认包含一个对齐数,vs默认对齐数是8。成员变量的对齐数:是成员变量和编译器默认对齐数相比较的较小值)
3.结构体整体的大小,要是结构体成员变量中最大的成员变量的对齐数的倍数
4.结构体嵌套结构体,被嵌套的结构体的大小,要是结构体成员的对齐数的倍数。其中嵌套的结构体成员变量的对齐数,是嵌套结构体中成员变量的最大对齐数。
因此,在创建结构体变量的时候,尽量先创建较小的结构体成员,在依次创建较大的结构体成员。避免结构体内部空间的浪费
编译器中的默认对齐数也是可以修改的:
#pragma pack(int)//int表示想要修改的对齐数
#pragma pack()//空表示恢复默认对齐数
结构体的传参
形参是实参的临时拷贝,为了避免有些结构体中的数据过于庞大,尽量选择传递结构体的指针
结构体实现位段
位段必须满足两个条件:
1、结构体成员类型必须是整型类型
2、结构体成员变量名后面必须要有 :冒号和 数字
struct S
{
char c:3;
int i:3;
};
特点:1.位段可以操作内存中的二进制位,成员名后面的数字表示他能操作的二进制位数。2.位段的出现就是为了节省空间,计算位段大小是不需要内存对齐的,但是位段开辟空间的大小是根据成员变量类型确定的,char开辟一个字节,in开辟4个字节。3、C语言中并没有规定位段开辟空间的细节,由编译器自己决定。跨平台使用涉及很多不确定因素,注重可移植的程序应该避免使用位段。
二、枚举
描述事物的时候将事物的类型一一列举出来。例如描述性别时:男性、女性。
枚举类型的声明
枚举关键字:enum
enum SEX
{
male,
unmale = 200
};
枚举的成员变量是有值的,第一个成员变量的默认值为0,其他的值依次递增1。当然我们也可以在定义枚举类型的时候,将成员变量进行初始化,但不能对已经定义的值进行修改。因此枚举也被称为枚举常量
枚举类型的优点
我们知道,可以使用#define定义常量,那么为什么还要使用枚举呢?正是因为枚举有define所不具有的一些优点
1.增加代码的可读性和可维护性
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名污染(封装)
4. 便于调试
5. 使用方便,一次可以定义多个常量
三、联合体
联合体是一种特殊的自定义类型,所有的成员变量公用一个块空间,因此也被称为共用体
联合体的定义
联合体关键字:union
union Un
{
char c;
int i;
};
联合体的特点
1.联合体共用一块空间,因此联合体的大小最小也是成员变量中最大的大小。
2.由于联合体共用一块空间,因此修改其中一个成员变量的值,另一个成员变量的值也会发生改变。这也是联合体的使用场景
联合体大小的计算
联合体的大小也存在内存对齐,当联合体的大小不是成员变量的对齐数的整数倍时,要对齐到整数倍。
例如:
union Un1
{
char c[5];
int i;
};