自定义类型(二)联合体和枚举

系列文章目录

自定义类型(一)结构体



前言

上节介绍自定义类型的结构体,这次我们来聊聊联合体和枚举,至此就将自定义类型一网打尽啦!


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.
枚举提高代码的可读性,联合体节省空间,用于特殊属性的描述等,结构体一般用于链表定义。指针 结构体 动态内存管理是数据结构的地基,下节我们来看动态内存管理。

  • 30
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值