上篇博客中,我们通过学习了解了C语言中一种自定义类型结构体的相关知识,那么该语言中是否还拥有相似的自定义类型呢?这将是我们今天学习的目标。
1.联合体
联合体其实跟结构体类似,也是由一个或多个成员构成,这些成员可以是不同类型。
以下是联合体的定义:
在C语言中,联合体(Union)是一种特殊的数据结构,允许在同一内存位置存储不同的数据类型。
1.1联合体的声明与创建
union un {
int n;
char ch;
};
int main()
{
//声明
union un u1;
union un* pu = &u1;
u1.ch = 'w';
printf("%c\n", u1.ch);
printf("%c\n", pu->ch);
u1.n = 5;
printf("%d\n", u1.n);
printf("%d\n", pu->n);
return 0;
}
我们在上面的代码中可以看到,联合体和结构体有非常多类似的地方,创建,声明,访问成员变量都很相似。
1.2联合体在内存中的存储
编译器在为联合体分配内存时,并不会像结构体一样为每个成员变量都分配内存,它只会分配在内存中占用最大空间的成员变量的空间,其他变量将会于该变量共用这一块空间,所以联合体又叫共用体。所以有一点要注意:给联合体其中一个变量赋值,其他成员的值往往也会跟着变化。
从第一个代码的结果我们可以发现:联合体本身和联合体的成员变量的首个字节地址时相同的。第二个代码我们分别给变量 i 和 c 赋值,发现 i 的结果会被 c 的赋值给覆盖掉,画出它们的内存布局图:
1.3相同成员的结构体与内存比较
struct S
{
char c;
int i;
};
struct S s = { 0 };
union Un
{
char c;
int i;
};
union Un un = { 0 };
我们画出它们的内存空间图进行比较:
我们可以看到,结构体的每个成员都有自己的内存空间,而联合体中 i 和 c 共工用一块空间的。
1.4联合体内存空间大小的计算
在前面我们说过,编译器只会分配联合体在内存中占用最大空间的成员变量的空间,其实这是不全面的,他还有以下两条规则:
- 联合体的大小至少是最大成员的大小
- 当最大成员大小不是最大对齐数的整数倍的时候,要对齐到最大对齐数的整数倍。
我们看下面代码:
第一个联合体un1内部有两个变量,一个是char型数组,一个是 int 类型,计算字符型数组的对齐数时,我们并不会将它的对齐数看作5,而是将其与char类型一样看待,所以他的对齐数是1,而整型类型的对齐数是4,所以un1的对齐数为4,但是该联合体最大成员大小是5,所以该联合体大小为8。
第一个联合体un2内部也是两个变量,一个是short类型数组,一个是int类型,计算short型数组的对齐数时,也是一样的规则,将其与short类型一样看待,所以他的对齐数是2,而整型类型的对齐数是4,所以un1的对齐数为4,但是该联合体最大成员大小是34,所以该联合体大小为36。
1.5联合体的应用
使用联合体时,因为给它分配的内存空间要少于同样类型结构体,所以使用联合体是要更节省空间的,我们来句一个例子。
我们要搞⼀个活动,要上线⼀个礼品兑换单,礼品兑换单中有三种商品:图书、杯⼦、衬衫。 每⼀种商品都有:库存量、价格、商品类型和商品类型相关的其他信息。
图书:书名、作者、⻚数
杯⼦:设计
衬衫:设计、可选颜⾊、可选尺⼨
我们不加思索,直接写出结构体:
struct gift_list
{
int stock_number;//库存量
double price;//价格
int item_type;//商品类型
char title[20];//书名
char author[20];//作者
int num_pages;//页数
char design[30];//设计
int color;//颜色
int szie;//尺寸
};
上述的结构其实设计的很简单,⽤起来也⽅便,但是结构的设计中包含了所有礼品的各种属性,这样使得结构体的⼤⼩就会偏⼤,⽐较浪费内存。因为对于礼品兑换单中的商品来说,只有部分属性信息是常⽤的。⽐如: 商品是图书,就不需要design、colors、sizes。
所以我们就可以把公共属性单独写出来,剩余属于各种商品本⾝的属性使⽤联合体起来,这样就可以介绍所需的内存空间,⼀定程度上节省了内存。
struct gift_list
{
int stock_number;//库存量
double price;//价格
int item_type;//商品类型
union
{
struct
{
char title[20];//书名
char author[20];//作者
int num_pages;//页数
}book;
struct
{
char design[30];//设计
}cup;
struct
{
char design[30];//设计
int color;//颜色
int szie;//尺寸
}shirt;
}item;
};
下面我们再来做一个熟悉的练习,写一个程序,判断当前机器是大端还是小端?为什么说熟悉呢?因为我们在之前的博客里已经讲过这道题了,之前我们时这样做的:
int main()
{
int n = 1;
int* ps = &n;
if (*((char*)ps) == 1)
printf("小端");
else
printf("大端");
return 0;
}
之前我们是用了强制转换来这道题的,学习了联合体后,我们有没有其他的思路呢?看下面代码:
union
{
int i;
char c;
}un;
int main()
{
un.i = 1;
if (un.i == 1)
printf("小端");
else
printf("大端");
return 0;
}
这个代码利用了联合体内变量共用同一块内存,如果是小端存储模式,将i赋值为1,那么c也会被赋值为1,如果没有,则证明是大端存储模式。
2.枚举类型
2.1枚举变量的声明
枚举顾名思义就是⼀⼀列举。把可能的取值⼀⼀列举。
enum Month
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Sex
{
male,
female,
secret,
};
enum Color
{
red,
green,
blue
};
{}中的内容是枚举类型的可能取值,也叫 枚举常量 。这些可能取值都是有值的,默认从0开始,依次递增1,当然在声明枚举类型的时候也可以赋初值。
//默认
enum Color
{
red = 0,
green = 1,
blue = 2
};
enum Color
{
red = 1,
green = 5,
blue = 9
};
2.2枚举变量的优点
我们可以使⽤ #define 定义常量,为什么⾮要使⽤枚举?枚举的优点:1. 增加代码的可读性和可维护性2. 和#define定义的标识符⽐较枚举有类型检查,更加严谨。3. 便于调试,预处理阶段会删除 #define 定义的符号4. 使⽤⽅便,⼀次可以定义多个常量5. 枚举常量是遵循作⽤域规则的,枚举声明在函数内,只能在函数内使⽤
2.3枚举的使用
enum Color//颜⾊
{
RED=1,
GREEN=2,
BLUE=4
};
enum Color clr = GREEN;//使⽤枚举常量给枚举变量赋值