目录
前言:
C语言中的内置有char、shor、int、float、double等。
C语言也允许自己创建一些类型,这就是我们要说的自定义类型了。
自定义类型有:结构体、枚举、联合体。
有了这些自定义类型,我们在使用C语言就更加方便,高效了。
-
结构体
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
下面我们可以定义一个学生的类型,比如他的名字,年龄,性别,学号。
//定义学生类型
struct Stu
{
//成员变量
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
};
当然我们还可以通过结构体的类型来创建变量。就拿上边的代码来创建就好了。
我们也可以在结构体最后分号的前边来定义变量,不过在这里定义的变量是全局变量。
如果是在大括号内部定义的结构体变量就是局部变量了。
//定义学生类型
struct Stu
{
//成员变量
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}s4, s5, s6;//全局的结构体变量
int main()
{
//局部的结构体变量
struct Stu s1;
struct Stu s2;
struct Stu s3;
return 0;
}
-
结构体的初始化
当我们有了结构体后,我们就可以对结构体的变量进行初始化了。
就拿上边学生的成员变量来进行初始化好了。
初始化可以根据成员变量的顺序来,当然也可以不根据顺序来。
先来看一下顺序的吧。
//定义学生类型
struct Stu
{
//成员变量
char name[20];//名字
int age;//年龄
char sex[10];//性别
char id[20];//学号
};
int main()
{
//局部的结构体变量初始化
struct Stu s1 = { "张三", 18, "男", "1232323"};
printf("%s %d %s %s\n", s1.name, s1.age, s1.sex, s1.id);
return 0;
}
printf打印可以根据顺序来,也可以不用跟着顺序。上边的是根据顺序来的。可以看一下打印的结果。
不按照顺序的初始化
//定义学生类型
struct Stu
{
//成员变量
char name[20];//名字
int age;//年龄
char sex[10];//性别
char id[20];//学号
};
int main()
{
//局部的结构体变量初始化
struct Stu s1 = { .sex = "男", .age = 18, .name = "李四", .id = "233233"};
printf("%s %d %s %s\n", s1.sex, s1.age, s1.name, s1.id);
return 0;
}
结果:
-
结构体内存对齐
当我们了解完结构体的基本情况之后,我们再来看看计算结构体的大小。
我们先来定义三个结构体,然后分别计算他们的大小。
struct s1
{
char s1a;
int s1b;
};
struct s2
{
char s2a;
int s2b;
char s2c;
};
struct s3
{
char s3a;
int s3b;
char s3c;
char s3d;
};
int main()
{
//打印结构体大小
printf("%d\n", sizeof(struct s1));
printf("%d\n", sizeof(struct s2));
printf("%d\n", sizeof(struct s3));
return 0;
}
看完代码,我们根据结构体成员类型来猜一下,s1里有一个char 和 int ,char是一个字节,int是4个字节,那么这样来看的话s1一共就是5个字节咯。按照这样来算,s2,就是6个字节,s3就是7个字节。
当然我们也可以看一下打印的结果。
以上的代码我都是用Visual Studio2019环境来进行打印的,其他环境就可能不一样了。
看完结果在vs环境下和我们猜想的完全不一样。
为什么不一样我们慢慢来看。
先来了解下结构体的对齐规则:
- 在结构体变量中的第一个成员,永远放在偏移量为0的地址处。
- 在第二个成员开始,后面的每个成员都要对齐到某个对齐数的整数倍处。这个对齐数是:成员的自身大小和默认对齐数的较小值。
- 当全部成员放进去之后,结构体的总大小必须是,所有成员对齐数中最大对齐数的整数倍。如果空间不够,那么就要浪费空间去对齐。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
在vs环境下的默认对齐数是8
在gcc环境下是没有默认对齐数的,没有默认对齐数的情况下,成员本身就是对齐数。
了解完我们就来画图了解一下。
s2的解释图
s3的解释图就不画了,和s2的没有太大区别,就是少浪费一个空间。
这里介绍一个库函数offsetof,它可以计算结构体的偏移量。
它的头文件是<stddef.h>,用的时候记得写上。
这里我使用一下这个库函数。计算一下上面结构体s2的偏移量。
#include <stdio.h>
#include <stddef.h>
struct s2
{
char s2a;
int s2b;
char s2c;
};
int main()
{
struct s2 s;
printf("%d\n", offsetof(struct s2, s2a));
printf("%d\n", offsetof(struct s2, s2b));
printf("%d\n", offsetof(struct s2, s2c));
return 0;
}
可以看见结果和上边计算的一样。
-
为什么会存在内存对齐呢?
在大部分参考资料是这样说的:
1.平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特
定类型的数据,否则就抛出硬件异常。
2.性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访
问。
下面就用一个结构体来画图解释下对齐和未对齐的情况。
struct S1
{
char c;
int a;
};
总体来说:
结构体的内存对齐是拿空间来换取时间的做法。比如说:我打游戏每次进游戏和进游戏读图花费的时间,比较长,可能是1分钟两分钟,但是游戏占用的硬盘空间比较小。我倒是愿意用大空间来换取时间短的。游戏玩起来流畅才是比较爽的。
当然我们在设计结构体的时候,我们也可以自己设计出一些满足对齐和节省空间的结构体。
如何做到:
那就是让空间小的成员尽量集中在一起。
#include <stdio.h>
struct S1
{
char c;
char b;
int a;
};
struct S2
{
char c;
int a;
char b;
};
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}
两个同样类型的变量,位置不一样所占的空间是大不一样的。所以能省则省,不能省就随便咯。
所以让空间小的成员挤一挤,空间大的往后边靠呗。
-
修改默认对齐数
当然如果对Visual Studio的默认对齐数不满意,我们还可以进行修改。
#pragma pack(1)//设置默认对齐数为1
#pragma pack()//取消设置的默认对齐数,还原为默认
下面来看一下该如何使用
#pragma pack(8)//设置默认对齐数为8
struct S1
{
int i;
char c;
int a;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
struct S2
{
int i;
char c;
int a;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}
可以看见修改为1之后就是按照变量自身大小来计算了。
当然我们在修改对齐数的时候尽量设置为2的n次方。
-
枚举
枚举顾名思义就是一 一列举。
把可能的取值一 一列举。
比如我们现实生活中:
一周的星期一到星期日是有限的7天,可以一 一列举。
性别有:男、女、保密,也可以一 一列举。
月份有12个月,也可以一 一列举
这里给一个性别的枚举,来使用一下
enum Sex//性别
{
//这里是枚举的可能取值,从第一个开始默认是0,递增1的
MALE,
FEMALE,
SECRET
};
int main()
{
//把值FEMALE赋给s
enum Sex s = FEMALE;
printf("%d\n", s);
printf("%d\n", MALE);
printf("%d\n", FEMALE);
printf("%d\n", SECRET);
return 0;
}
可以看见打印的结果是从0开始的,第一个是把FEMALE赋值给s打印出来的。
以上定义的 enum Sex 是枚举类型。
{}中的内容是枚举类型的可能取值,也叫 枚举常量
-
枚举的优点
为什么使用枚举?
我们可以使用 #define 定义常量,为什么非要使用枚举?
枚举的优点:
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名污染(封装)
4. 便于调试
5. 使用方便,一次可以定义多个常量
-
改变枚举的初始值
枚举的初始值也是可以改变的。
enum Sex//性别
{
MALE = 2,
FEMALE = 5,
SECRET = 7
};
int main()
{
printf("%d\n", MALE);
printf("%d\n", FEMALE);
printf("%d\n", SECRET);
return 0;
}
打印结果也是根据我们给的值来定的。
如果只给第一个赋值的话,后边的只会根据1递增下去。
-
联合(共用体)
联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)
-
联合类型的定义和特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联
合至少得有能力保存最大的那个成员)
这里在使用的时候计算下这个联合体的空间大小
union un
{
char c;
int i;
};
int main()
{
union un u;
printf("%d\n", sizeof(u));
return 0;
}
这里可以看见int 和 char大小一个是4个字节。
当然我们也可以取出它们的地址看一下是不是真的共用的一块空间。还是这段联合体
union un
{
char c;
int i;
};
int main()
{
union un u;
printf("%p\n", &u);
printf("%p\n", &(u.c));
printf("%p\n", &(u.i));
return 0;
}
不出所料就是使用的同一块空间
-
联合大小的计算
联合的大小至少是最大成员的大小。
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
下面我们就来看一下到底是不是这样的。
union un
{
char arr[5];//5个字节
int i;//4个字节
};
int main()
{
printf("%d\n", sizeof(union un));
return 0;
}
char的对齐大小是1,int的对齐大小是4。根据上边来看就要对齐4的整数倍咯,所以最后是8。
再来看一个把。
union un2
{
short s[7];//大小是14
int i;//大小是4
};
int main()
{
printf("%d\n", sizeof(union un2));
return 0;
}
如果按照最大成员的大小来对齐的话那么最后就是14咯,但是要按照最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。short s要占14个字节的,为了对齐。所以最后是16,是4的整数倍。
-
结尾
结构体、枚举、联合体就介绍到这里了。
写的哪里有不对的地方,还请指出,我这里会进行更改,谢谢大家。