C语言第十三章自定义类型:联合和枚举
一.联合体
1.联合体的声明
像结构体一样,联合体也是由一个或者多个成员构成,这些成员可以是不同的类型。但是编译器只为最大的成员分配足够的内存空间。联合体的特点是所有成员共用同一块内存空间。所以联合体也叫:共用体。 给联合体其中一个成员赋值,其他成员的值也会跟着变化(因为所有成员共用一个空间)。
#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.联合体的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合体至少得有能力保存最大的那个联合体成员)。
//代码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
通过上述代码的输出结果来看:联合体的变量都公用一份空间,所有的联合体变量的内存数都一样。
//代码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.相同成员的结构体和联合体对比
struct S
{char c;int i;
};
struct S s = {0};
结构体通过内存对齐,用空间换取时间和效率。保证一次CPU的内存读取可以成功读取一个变量的数据。
union Un
{char c;int i;
};
union Un un = {0};
联合体注重空间的节省,通过将所有变量存在一个内存块中,从而达到节省空间的目的。
4.联合体大小的计算
联合的大小至少是最大成员的大小。当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
#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;
}
根据上方图片可得知:联合体并不是不浪费丝毫空间,其本身也存在内存对齐。当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。下面来一个联合体的实用场景,用于加深对知识的理解:
使用联合体是可以节省空间的,比如:学校要搞一个活动,要上线一个礼品兑换单,礼品兑换单中有三种商品:图书、杯子、衬衫。 每一种商品都有:库存量、价格、商品类型和商品类型相关的其他信息。
图书:库存量、价格、商品类型、商品类型、书名、作者、页数;
杯子:库存量、价格、商品类型、商品类型、设计;
衬衫:库存量、价格、商品类型、商品类型、设计、可选颜色、可选尺存。
下面我们来实现该代码:
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。 所以我们就可以把公共属性单独写出来,剩余属于各种商品本⾝的属性使用联合体起来,这样就可以介绍所需的内存空间,一定程度上节省了内存。
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.联合体习题
写一个程序,判断当前机器是大端?还是小端?
int check_sys()
{union{int i;char c;}un;un.i = 1;return un.c;//返回1是⼩端,返回0是⼤端
}
上述代码利用了联合体成员变量共用一份空间的特点,巧妙地通过取出联合体成员变量c的地址而得到了,整型变量c的首字节地址。
二.枚举类型
1.枚举类型的声明
枚举顾名思义就是一一列举。 把可能的取值一一列举。 比如我们现实生活中:一周的星期yiyi到星期天是有限的7天,可以一一列举;性别有:男、女,也可以一一列举;月份有12个月,也可以一一列举;三原色,也是可以一一列举。这些数据的表示就可以使用枚举类型。
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,当然在声明枚举类型的时候也可以赋初值。比如下面的代码:
enum Color//颜⾊
{RED=2,GREEN=4,BLUE=8
};
上述代码就是将枚举常量赋值为2 4 8 。当然如果只赋值第二个枚举常量为4,那么第一个枚举常量就为默认值:0;第三个枚举常量的值就是第二个枚举常量的值+1:5
2.枚举类型的优点
在C语言中,可以使用 #define 定义常量,为什么还要使用枚举呢?
枚举的优点:
1. 增加代码的可读性和可维护性,将数值赋予对应的含义,代码意义就会更加清晰。
#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代表的是什么了。
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
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++语言上这样操作。
3. 便于调试,预处理阶段会删除#define定义的符号,不便于后期的调试操作。
4. 使用方便,一次可以定义多个常量,不用一直连续写出多个#define。
5. 枚举常量是遵循作用域规则的,枚举声明在函数内,只能在函数内使用。
3.枚举类型的使用
enum Color//颜⾊
{RED=1,GREEN=2,BLUE=4
};
enum Color clr = GREEN;//使⽤枚举常量给枚举变量赋值
上述枚举类型:首先声明了枚举类型,并对其中的枚举常量进行赋值,在今后使用时,就可以将枚举常量赋值给枚举变量。