自定义类型有三种:结构体,联合体,枚举。
结构体已经讲解完了,接下来我们学习联合体和枚举。
一.联合体
像结构体一样,联合体也是由一个或者多个成员构成,这些成员可以是不同的类型。
结构体使用struct,而联合体使用union。
那么结构体和联合体由什么关系呢?
结构体 | 联合体 |
struct | union |
多个成员 | 多个成员 |
每个成员都有自己独立的空间 | 所有成员公用同一块内存空间 |
所以联合体也可以叫做:共用体。
union Un
{
char c;//1
int i; //4
};
假设有这样一个联合体,那么思考一下,这个联合体的大小是多少呢?
union Un u = {0};
printf("%zd\n", sizeof(u));//4
printf("%p\n", &u);
printf("%p\n", &(u.i));
printf("%p\n", &(u.c));
答案是4。
而且联合体u,和i,c的地址是一模一样的,说明i和c的空间是由重叠的。
这是因为联合体的所有成员是共用同一块内存空间的,所以编译器只会为最大的成员分配足够的内存空间。也正是因为他们公用同一个空间,给其中一个成员进行赋值,是会改变其他成员的值的。
#include<stdio.h>
union Un
{
char c;
int i;
};
int main()
{
//联合变量的定义
union Un un = { 0 };
un.i = 0x11223344;
un.c = 0x55;
printf("%x\n", un.i);
return 0;
}
i和c公用一个空间,c是占据在i的第一个字节的。un.i = 0x11223344.0x开头的数字是十六进制的。根据小端存储,i的第一个字节到第四个字节存的分别是44,33,22,11.un.c = 0x55,所以也就是将第一个字节的改成55.%x是以十六进制打印数字。所以打印出来就是0x11223355.
联合体大小的计算
- 联合体的大小至少是最大成员的大小。
- 当最大成员的大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍处。
#include <stdio.h>
union Un1
{
char c[5];
int i;
};
union Un2
{
short c[7];
int i;
};
int main()
{
//下⾯输出的结果是什么?
printf("%zd\n", sizeof(union Un1));
printf("%zd\n", sizeof(union Un2));
return 0;
}
union Un1的最大成员大小是5个字节,但是最大对齐数是4,要对齐到4的整数倍处,所以输出结果是8.
union Un2的最大成员大小是2 * 7 = 14个字节,但是最大对齐数是4,要对齐到4的整数倍处,所以输出结果是16.
在union Un1 中,i和c的地址是一样的。同理在union Un2 中也是如此。
联合体的举例使用
比如,我们要搞一个活动,要上线一个礼品兑换单,礼品兑换单中有三个商品:图书,杯子,衬衫。
每一种商品都有:库存量,价格,商品类型和商品类型相关的其他信息。
- 图书:书名,作者,页数。
- 杯子:设计。
- 衬衫:设计,可选颜色,可选尺寸。
联合体的特性是,在使用其中一种成员变量的时候,其他的成员变量是不能使用的,不然数据就会被覆盖。
假如我们使用结构体来定义这个礼品兑换单,我们就会发现有些成员变量是用不到的,这样的话就会造成内存浪费。
所以我们可以使用结构体。
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];//设计
}mug;
//衬衫特有
struct
{
char design[30];//设计
int colors;//颜⾊
int sizes;//尺⼨
}shirt;
}item;
};
联合体的一个练习
写一个程序,判断当前的机器是大端字节序存储还是小端字节序存储。
int check_sys()
{
union
{
int i;
char c;
}un;
un.i = 1;//0x00 00 00 01
//c占据的一个字节就是i的第一个字节
//如果返回值是1,小端字节序。
//如果返回值是0,大端字节序。
return un.c;
}
二.枚举
枚举顾名思义就是一一列举。把可能的值一一列举。
比如我们现实生活中:
一周的星期一到星期天,可以一一列举。
月份也可以一一列举。
这样的数据的表示都可以一一列举。
enum Week{Mon,Tues,Wednes,Thur,Fri,Satur,Sun};
enum Color {RED,GREEN,BLUE};
以上定义的enum Week,enum Color均是枚举类型。{}中的内容都是枚举类型的可能取值,也叫做枚举常量。
这些枚举常量都是有值的
这些枚举常量都是有值的,在默认情况下,第一个枚举常量的值是0,从左向右依次加1.
当然这些枚举常量在声明的情况下也是可以赋初值的。
在赋值后,右边的值也会跟着改变,Wednes变成了6,右边的值是是其左边的数的值加1,这是默认不变的。
为什么要使用枚举呢?
我们可以使用#define来定义常量,为什么还要去使用枚举呢?
枚举的优点:
- 增加代码的可读性和可维护性
- 和#define相比,有类型的检查,更加严谨
- 便于调试,预处理阶段会删除#define定义的符号,将其全部替换
- 方便使用,一次性可定义多个常量
- 枚举常量是遵循作用域规则的,枚举的声明在函数内的话,就只能在函数内部使用。
#include<stdio.h>
int main() {
enum Week { Mon = 1, Tues = 5, Wednes, Thur, Fri, Satur, Sun };
enum Week day = Mon;
printf("%d ", day);
day = 2;
printf("%d ",day);
}
在C语言中,是可以拿整数给枚举变量进行赋值的,但是在C++中是不行的,C++的类型检查比较严格。