系列文章目录
文章目录
前言
上节介绍自定义类型的结构体,这次我们来聊聊联合体和枚举,至此就将自定义类型一网打尽啦!
1. 联合体
1.1 联合体的声明、定义及特点
与结构体类似,联合体也是由一个或者多个成员构成,这些成员可以是不同的类型。
编译器只为最大的成员分配足够的内存空间
因此联合体的特点是所有成员公用同一块内存空间,联合体也叫共用体。
给联合体其中一个成员赋值,其它成员的值也跟着变化。
我们可以稍微做一下回顾,并且对比一下struct与union两种自定义类型。
#include <stdio.h>
struct S
{
char c;
int n;
};
union Un//联合体的声明
{
char c;
int n;
};
int main()
{
union Un u={0};//联合体类型的定义,使用大括号{}
printf("%zd\n", sizeof(struct S));
printf("%zd\n", sizeof(union Un));
return 0;
}
在VS2022 X64的输出结果如下所示
8
4
struct S的内存分布图如下所示
接下来我们看一下联合体在内存中的分布
1.2 联合体的内存分布
我们声明一个联合体u,并对其地址以及各成员地址进行打印。
int main()
{
union Un u = { 0 };
printf("%p\n", &u);
printf("%p\n", &u.c);
printf("%p\n", &u.n);
return 0;
}
在VS2022 X64的输出结果如下所示
int main()
{
union Un u = { 0 };
u.n = 0x11223344;
u.c = 0x55;
return 0;
}
并且对上面代码进行调试,可以看到执行u.c=0x55;
语句之后,内存中u的值变化如下。
因此我们可以推断,在内存中u的内存分布如下所示
1.3 联合体的大小计算
1.联合体的大小>=最大成员的大小
2.联合体的大小是最大对齐数的整数倍
特别数组要作特殊说明,我们结合下面的例子来看
我们接下来看联合体中的数组是如何存储的。
#include <stdio.h>
union Un2
{
char c[5];
int n;
};
int main()
{
printf("%zd\n", sizeof(union Un2));
return 0;
}
在VS2022 X64的输出结果如下所示
8
这是为什么呢?其实数组类型的数据,系统在计算对齐数的时候,会按其成员变量而不是整体数组大小来计算,联合体必需能存下所有数据,其自身大小>5,且是最大对齐数的倍数,也就是8.
1.4 联合体的使用举例
经过前面的分析,我们知道,联合体与结构体相比,节省空间。那么一般在声明特殊属性的时候会用到联合体。
如果我们要搞一个活动,上线一个礼品兑换单,其中有三种商品:图书、杯子和衬衫。每一种商品都有:库存量、价格、商品类型和其他相关的信息。
图书:书名、作者、页数
杯子:设计
衬衫:设计、可选颜色、可选尺寸
如果不考虑其他,写出下面的结构体对商品属性进行描述。
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;
};
这种结构体设计简单,使用方便,但是每一个商品信息的存储都要包含所有属性,而有些商品根本用不到一些属性,比如书籍没有“设计”属性,那么导致内存的浪费,而一旦礼品兑换单数量巨大,浪费空间就太多了。因此我们考虑,特殊属性用共用体进行存储,如下所示。三种物品特殊属性分门别类,一次只用一种。
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;
};
1.5 联合体的练习
写一个程序判断程序是大端(存储)模式,还是小端(存储)模式?
不用联合体的代码可以是下面这样的
#include <stdio.h>
int main()
{
int i = 1;
if (*(char*)&i)
printf("小端\n");
else
printf("大端\n");
return 0;
}
结合联合体封装成函数
#include <stdio.h>
int check_sys()
{
union
{
int i;
char c;
}un;
un.i = 1;
//小端 i 0x 01 00 00 00
//大端 i 0x 00 00 00 01
return un.c;
}
int main()
{
if(check_sys())
printf("小端\n");
else
printf("大端\n");
return 0;
};
2. 枚举类型
2.1 枚举类型的声明和使用
2.1.1 枚举类型的声明
枚举,把可能的取值一一列举。
比如生活中可以一一列举的有
一周的星期一到星期日是有限的7天,
性别有:男、女、保密
月份有12个月
三原色,也可以是意义列举
关键字:enum,如下代码所示,对性别的可能取值一一列举,男、女、保密。
enum Sex//性别
{
MALE,
FAMALE,
SECRET
};
定义的enum Sex是枚举类型,{}中的内容是枚举类型的可能取值,也叫枚举常量
。这些可能取值有对应的数值,
默认从0开始,依次递增1;
也可以在声明枚举类型的时候赋值,所赋值的枚举常量之后的枚举常量若没赋值,其后的枚举常量往后依次递增1。
举例如下
#include <stdio.h>
enum DAY//星期
{
Mon,
Tues,
Wed,
Thur = 4,
Fri,
Sat,
Sun
};
enum Color//颜色
{
RED,
GREEN,
BLUE
};
enum Sex
{
MALE = 3,
FAMALE = 2,
SECRET = 1
};
int main()
{
//Mon, Tues, Wed默认赋值,Fri赋值为4,之后的枚举常量依次递增1
printf("Mon =%d, Tues =%d, Wed =%d, Thur =%d, Fri =%d, Sat =%d, Sun =%d\n", Mon, Tues, Wed, Thur, Fri, Sat, Sun);
//全赋值
printf("RED =%d, GREEN =%d, BLUE =%d\n", RED, GREEN, BLUE);
//默认
printf("MALE=%d, FAMALE=%d, SECRET=%d\n", MALE, FAMALE, SECRET);
return 0;
}
在VS2022 X64的输出结果如下所示
2.1.2 枚举类型的使用
使用枚举常量给枚举变量赋值,和结构体类似,用关键字+类型名声明变量,比如enum Sex可以直接声明枚举变量、对变量进行赋值等。
如下图,调试界面s1的类型是Sex,值为MALE(3).
在C语言中可以拿整数给枚举变量赋值,但在C++中不行,C++的类型检查比较严格。
2.2 枚举类型的优点
如果单单只是定义常量的话,也可以使用#define
,那么选择使用枚举,其优点是什么呢?
枚举的优点
1.增加代码的可读性和可维护性
2.和#define定义的标识符比较,枚举有类型检查,更加严谨
3.便于调试,预处理会删除#define定义的符号
4.使用方便,一次可以定义多个变量
5.枚举常量是遵循作用域原则的,枚举声明在函数内,只能在函数内使用
2.2.1 增加代码的可读性和可维护性
当我们要实现一个可以进行加减乘除的计算器时,我们写出的代码可能是这样的
//首先实现加减乘除等函数
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
//菜单函数实现提供选择
void menu()
{
printf("**********************\n");
printf("**** 1:Add 2:Sub ****\n");
printf("**** 3:Mul 4:Div ****\n");
printf("**** 0:exit ****\n");
printf("**********************\n");
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
//do while循环结合变量input实现多次运算
do {
menu();
printf("请输入:>");
scanf("%d", &input);
//switch case语句实现不同的选择
switch (input)
{
case 1:
printf("请输入您要相加的两个值:>");
scanf("%d %d", &x, &y);
printf("%d\n", Add(x, y));
//此处直接进行打印没有计算,可能会有风险,
//也可引入局部变量ret存储计算值并进行打印,如下
//ret = Add(x,y);
//printf("%d\n", ret);
break;
case 2:
printf("请输入您要相减的两个值:>");
scanf("%d %d", &x, &y);
printf("%d\n", Sub(x, y));
break;
case 3:
printf("请输入您要相乘的两个值:>");
scanf("%d %d", &x, &y);
printf("%d\n", Mul(x, y));
break;
case 4:
printf("请输入您要相除的两个值:>");
scanf("%d %d", &x, &y);
printf("%d\n", Div(x, y));
break;
case 0:
printf("退出计算机\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
我们可以看到case 0
等case+数字的语句可读性并不是很高,那么我们可以用enum进行优化。
将函数名进行枚举
enum Option
{
EXIT,
ADD,
SUB,
MUL,
DIV
};
如上代码所示,这样ADD等本身就可以当做常量进行使用,并且菜单中的数字与其对应操作清晰明了。代码优化后如下图,增加代码可读性。
2.2.2 和#define定义的标识符比较,枚举有类型检查,更加严谨
宏在预处理的时候直接替换,不去关注具体类型。比如#define PI 3.14159
,预处理时会将所有出现的PI替换为3.14159,即宏没有类型信息,没有类型检查直接替换。
如下图,PI可以作为常量进行使用,但显示“未定义标识符”
而C++中enum Sex s = 'A';
会直接报错,不能将字符’A’赋值给enum Sex类型的变量。但是C语言还是可以正常运行的,因为C语言直接隐式类型转换,把字符转成ASCII码值了。
下图是C++的界面,报错
下图是C语言的调试界面
总结
至此,自定义类型就介绍完了,结构体的关键字是struct,联合体的关键字是union,枚举的关键字是enum.
枚举提高代码的可读性,联合体节省空间,用于特殊属性的描述等,结构体一般用于链表定义。指针 结构体 动态内存管理
是数据结构的地基,下节我们来看动态内存管理。