我们知道,C语言中有一些内置类型比如:char(字符)类型、int(整型)类型、float(单精度浮点型)类型和double(双精度浮点型)。但是其实在C语言中还存在着一种数据类型叫做自定义类型,自定义类型包括:struct(结构体)类型、union(联合体)类型和enum(枚举)类型,这篇博客其实就是介绍C语言中的自定义类型。
由于自定义类型涉及的内容有点多且偏,在这里我们只介绍自定义类型中比较重要的知识。
结构体类型
结构体类型其实和我们之前学的C语言中的内置类型是一样的,都是表示一个变量的类型。
结构体类型的创建和初始化
结构体类型的创建
结构体是一些值的集合,这些值都叫做成员变量。结构中的每一个成员都可以是不同的变量。以下是一个简单的结构体类型的实现:
//这里是定义一个名字为学生的结构体
struct Student//这个student是这个结构体类型的名字,可以自己指定
{
//这里是结构体中的成员
char name[20];//学生的名字
int age; //学生的年龄
float grade; //学生的成绩
} s4, s5;//这里的s4、s5是全局变量
//以下是一个简单的结构体变量的创建
int main()
{
struct Student s1, s2, s3;//这里的s1、s2、s3是局部变量
return 0;
}
以上就是一个简单的结构体类型的创建。
当然,在写代码的过程中我们可能还会见到一种这样创建的结构体类型:
struct
{
char name[20];
int age;
float grade;
} A ;
我们发现,这个结构体类型在创建的过程中没有名字,我们把这种没有名字的结构体类型叫做匿名结构体类型,这种结构体类型由于是没有名字的,所以在我们写程序的过程中它只能出现一次。
结构体类型的初始化
结构体类型和我们之前学习的C语言的内置类型一样,都是可以进行初始化操作的。以下就是C语言中结构体初始化的示例:
//这里是定义一个名字为学生的结构体
struct Student
{
char name[20];
int age;
float grade;
};
int main()
{
// 这里是我们按照我们定义的结构体类型进行的一个按顺序的初始化
struct Student s1 = {"zhangsan", 18, 97.5};
//下面这种初始化方式是我们不按照结构体类型的顺序进行初始化
struct Student s2 = {.age = 18, .name = "zhangsan", .grade = 97.5};
return 0;
}
结构体的内存对齐
我们通过上面的文章可以知道,在结构体中,我们可以存在多个变量,而这些变量都是需要占用一定的空间大小的,所以我们的结构体也会占用一块空间。那么我们应该如何计算结构体的空间大小呢?请看以下代码示例:
struct Student
{
char name;
int age;
};
有些人可能会觉得这个太简单了,这个结构体的空间大小不就是5个字节吗?char类型占一个字节,int类型占四个字节。但是实际上,这个结构体的空间大小是8个字节,这里就是我们的内存对齐所造成的结果。以下是验证结果:
有些人可能会觉得奇怪,内存对齐明明会导致占用的内存变多,为什么还要存在内存对齐呢?接下来博主就来为大家解答一下内存对齐的优点是什么。
内存对齐的优点:在cpu读取我们这个结构体里面的变量的时候一次会读取4个字节,但是我们的char类型只有一个字节,也就是说在cpu读取我们的name变量的时候会再读取age变量的前三个字节。这就会导致我们的cpu还得继续做一个处理,就是把刚刚读取的三个字节还给age变量,再重新读取。这个时候就会浪费一定的时间去读取数据。但是如果我们让char类型的数据占用四个字节呢?那么cpu只要读取两次就可以取出所有数据并且不需要做任何处理,这就是内存对齐的优点。
内存对齐的规则
以上我们介绍了内存对齐的优点,现在我们来介绍内存对齐的规则是什么。以下就是内存对齐的规则:
- 第一个成员在与结构体偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 - 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
内存对齐会导致空间的浪费,那么为了尽可能减少空间的消耗我们有没有些应对措施呢?当然有,结构体的位段就是其中一个应对措施,但是由于结构体的位段在结构体中并不属于比较重要的内容,所以在此博主便不再赘述。好奇的小伙伴可以去搜索位段的相关知识。回到正题,除了结构体的位段,我们还有没有节省内存对齐浪费的空间的办法呢?结果是肯定的。请看以下代码:
有些人可能会觉得这两个结构体占用的空间大小不是一样的吗?这不会节省空间啊。但是其实你如果运用了上面内存对齐的规则的话会发现,其实第一个结构体占用的空间大小是12,第二个结构体占用的空间大小是8。以下是代码运行后的结果:
由此我们可以得出:在结构体变量中创建的成员尽量把占用内存空间小的变量放前面,这样子可以尽可能地减少内存空间的消耗。以上就是结构体相关的介绍。
联合体类型
联合体类型其实也是一种自定义类型,不过它和结构体不同的是:联合体里面的成员变量使用的都是同一块空间,也就是至少是最大的成员变量的空间。
以下是代码示例:
下面是验证的方法:
我们发现不管是name还是age这两个成员变量指向的都是同一个地址,也就是联合体u的地址。所以联合体中的成员变量的地址是同一块内存空间,只是随着成员变量所能访问的内存空间的大小的不同来进行区分。
和结构体一样,我们的联合体也有空间大小,计算联合体的空间大小其实和计算结构体的空间大小非常相似。所以在此不再赘述。以上就是关于联合体类型的介绍。
枚举类型
枚举类型,顾名思义就是把可能的取值列举出来,请看以下代码示例:
enum Sex
{
//枚举常量
MALE,
FEMALE,
SECRET
};
int main()
{
printf("%d %d %d\n", MALE, FEMALE, SECRET);
return 0;
}
运行示例如下:
从上面这段代码我们可以看出如果我们没有给枚举常量赋值,编译器给枚举常量赋值会自动赋成0/1/2/3···
枚举类型的优点
有些人可能会问我们可以用#define来定义常量,为什么我们一定要用枚举类型来定义常量呢?以下是枚举常量的一点优点:
- 枚举类型可以增加代码的可读性和可维护性,因为我们把我们需要定义的常量给它放到一块去了,简单易懂。
- 和#define定义的标识符比较,枚举有类型的检查,更加严谨。
- 便于调试 ,预处理阶段会删除#define定义的符号。
- 使用方便,一次可以定义多个常量。
- 枚举常量是遵循作用域和规则的,枚举声明在函数内,只能在函数内使用。
以上就是对于自定义类型的枚举类型的介绍。