一.联合体
1.联合体的声明
像结构体一样,联合体也是由一个或者多个成员构成,这些成员可以是不同的类型。但是编译器只为最大的成员分配足够的内存空间。联合体的特点是所有成员共用同一块内存空间。所以联合体也叫:共用体。 给联合体其中一个成员赋值,其他成员的值也会跟着变化(因为所有成员共用一个空间)。
cpp
#include <stdio.h>
//联合类型的声明
union Un
{
char c;
int i;
};
int main()
{
//联合变量的定义
union Un un = {0};
//计算连个变量的⼤⼩
printf("%d\n", sizeof(un));
return 0;
}
上述代码的具体解释:首先进行了联合体的声明,两个联合体成员变量c和i共处一个空间中。其次进入main函数,创建了联合体变量un,并初始化为0。最后利用操作符sizeof来计算联合体变量un的大小。
上述代码输出的结果:4,为什么呢?请继续阅读下面的相关解释:
2.联合体的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合体至少得有能力保存最大的那个联合体成员)。
cpp
//代码1
#include <stdio.h>
//联合类型的声明
union Un
{
char c;
int i;
};
int main()
{
//联合变量的定义
union Un un = {0};
// 下⾯输出的结果是⼀样的吗?
printf("%p\n", &(un.i));
printf("%p\n", &(un.c));
printf("%p\n", &un);
return 0;
}
001AF85C
001AF85C
001AF85C
通过上述代码的输出结果来看:联合体的变量都公用一份空间,所有的联合体变量的内存数都一样。
cpp
//代码2
#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;
}
11223355
通过上述代码的输出情况可以看出:联合体的所有成员公用一块内存,当一个变量的值发生变化时,其余变量也会因此而受影响。
代码1输出的三个地址一模一样,代码2的输出,我们发现将i的第4个字节的内容修改为55。我们仔细分析就可以画出,un的内存布局图。

3.相同成员的结构体和联合体对比
cpp
struct S
{
char c;
int i;
};
struct S s = {0};

结构体通过内存对齐,用空间换取时间和效率。保证一次CPU的内存读取可以成功读取一个变量的数据。
cpp
union Un
{
char c;
int i;
};
union Un un = {0};

联合体注重空间的节省,通过将所有变量存在一个内存块中,从而达到节省空间的目的。
4.联合体大小的计算
联合的大小至少是最大成员的大小。当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
cpp
#include <stdio.h>
union Un1
{
char c[5];
int i;
};
union Un2
{
short c[7];
int i;
};
int main()
{
//下⾯输出的结果是什么?
printf("%d\n", sizeof(union Un1));
printf("%d\n", sizeof(union Un2));
return 0;
}


根据上方图片可得知:联合体并不是不浪费丝毫空间,其本身也存在内存对齐。当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。下面来一个联合体的实用场景,用于加深对知识的理解:
使用联合体是可以节省空间的,比如:学校要搞一个活动,要上线一个礼品兑换单,礼品兑换单中有三种商品:图书、杯子、衬衫。 每一种商品都有:库存量、价格、商品类型和商品类型相关的其他信息。
图书:库存量、价格、商品类型、商品类型、书名、作者、页数;
杯子:库存量、价格、商品类型、商品类型、设计;
衬衫:库存量、价格、商品类型、商品类型、设计、可选颜色、可选尺存。
下面我们来实现该代码:
cpp
struct gift_list
{
//公共属性
int stock_number;//库存量
double price; //定价
int item_type;//商品类型
//特殊属性
char title[20];//书名
char author[20];//作者
int num_pages;//⻚数
char design[30];//设计
int colors;//颜⾊
int sizes;//尺⼨
}
上述的结构其实设计的很简单,用起来也方便,就是将所有的属性均放在结构体中,但是结构的设计中包含了所有礼品的各种属性,这样使得结构体的大小就会偏大,比较浪费内存。因为对于礼品兑换单中的商品来说,只有部分属性信息是常用的。比如: 商品是图书,就不需要design、colors、sizes。 所以我们就可以把公共属性单独写出来,剩余属于各种商品本⾝的属性使用联合体起来,这样就可以介绍所需的内存空间,一定程度上节省了内存。
cpp
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;
}
上述代码首先将共有的属性充当结构体的成员,当需要某些特殊属性时,则将联合体定义在结构体中,将每个特殊属性的组合新分配到小结构体中作为联合体成员。这也满足了联合体一次只能得到一个成员变量的特点。
上述代码充分利用了结构体和联合体的特点,将可以一同取出数据的结构体成员定义为共同属性;将一次只能得到一个成员的联合体成员定义为特殊属性。
5.联合体习题
写一个程序,判断当前机器是大端?还是小端?
cpp
int check_sys()
{
union
{
int i;
char c;
}un;
un.i = 1;
return un.c;//返回1是⼩端,返回0是⼤端
}
上述代码利用了联合体成员变量共用一份空间的特点,巧妙地通过取出联合体成员变量c的地址而得到了,整型变量c的首字节地址。
二.枚举类型
1.枚举类型的声明
枚举顾名思义就是一一列举。 把可能的取值一一列举。 比如我们现实生活中:一周的星期yiyi到星期天是有限的7天,可以一一列举;性别有:男、女,也可以一一列举;月份有12个月,也可以一一列举;三原色,也是可以一一列举。这些数据的表示就可以使用枚举类型。
cpp
enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Sex//性别
{
MALE,
FEMALE
};
enum Color//三原色
{
RED,
GREEN,
BLUE
};
以上定义的 enum Day , enum Sex , enum Color 都是枚举类型。 { } 中的内容是枚举类型的可能取值,也叫枚举常量。这些枚举常量都是有值的,默认从0开始,依次递增1,当然在声明枚举类型的时候也可以赋初值。比如下面的代码:
cpp
enum Color//颜⾊
{
RED=2,
GREEN=4,
BLUE=8
};
上述代码就是将枚举常量赋值为2 4 8 。当然如果只赋值第二个枚举常量为4,那么第一个枚举常量就为默认值:0;第三个枚举常量的值就是第二个枚举常量的值+1:5
2.枚举类型的优点
在C语言中,可以使用 #define 定义常量,为什么还要使用枚举呢?
枚举的优点:
- 增加代码的可读性和可维护性,将数值赋予对应的含义,代码意义就会更加清晰。
cpp
#include <stdio.h>
void menu()
{
printf("******1.jia*******2.jian*********\n");
printf("******3.cheng*****4.chu**********\n");
printf("******0.tuichu*******************\n");
printf("*********** ********************\n");
}
int jia1(int x, int y)
{
return x + y;
}
int jian1(int x, int y)
{
return x - y;
}
int cheng1(int x, int y)
{
return x * y;
}
int chu1(int x, int y)
{
return x / y;
}
enum caozuo
{
tuichu,
jia,
jian,
cheng,
chu
};
int main()
{
int input=0;
do
{
menu();
printf("请选择你需要的操作");
scanf_s("%d", &input);
int a, b,ret=0;
printf("请输入需要参加运算的两个整数");
scanf_s("%d %d", &a, &b);
switch (input)
{
case 1: //case jia:
ret = jia1(a, b);
printf("计算结果为:%d\n", ret);
break;
case 2: //case jian:
ret = jian1(a, b);
printf("计算结果为:%d\n", ret);
break;
case 3: //case cheng:
ret = cheng1(a, b);
printf("计算结果为:%d\n", ret);
break;
case 4: //case chu:
ret = chu1(a, b);
printf("计算结果为:%d\n", ret);
break;
case 0: //case tuichu:
printf("成功退出\n");
break;
default:
printf("输入错误\n");
break;
}
} while (input);
return 0;
}
如上述代码所示:switch语句在使用过程中,情况case语句可以写成后面的注释样子。那样可以增强代码的阅读性,在下次阅读代码时,就不需要再查看input代表的是什么了。
- 和#define定义的标识符比较枚举有类型检查,更加严谨。
cpp
enum Color
{
RED,
GREEN,
BLUE
};
Color c = RED; // 类型正确
c = 10; // 可能触发警告或错误,因为10不是Color类型
上述代码创建了三原色的枚举类型,默认情况下RED=0, GREEN=1, BLUE=2。接下来创建了枚举变量c,初始化为RED,但是最后却将c赋值为10,这明显是错误的。10并不是枚举常量的数值。这样会引起编译器的报错。
这种用无关于枚举常量数值为枚举变量赋值的做法,仅仅只限于C语言,并不能在C++语言上这样操作。
-
便于调试,预处理阶段会删除#define定义的符号,不便于后期的调试操作。
-
使用方便,一次可以定义多个常量,不用一直连续写出多个#define。
-
枚举常量是遵循作用域规则的,枚举声明在函数内,只能在函数内使用。
3.枚举类型的使用
cpp
enum Color//颜⾊
{
RED=1,
GREEN=2,
BLUE=4
};
enum Color clr = GREEN;//使⽤枚举常量给枚举变量赋值
上述枚举类型:首先声明了枚举类型,并对其中的枚举常量进行赋值,在今后使用时,就可以将枚举常量赋值给枚举变量。