随笔——自定义类型:联合和枚举

前言

请先看我的另一篇文章: 《随笔——自定义类型:结构体》
本文章建立在该文章的基础之上。

在C语言中,除了结构体,还有两种自定义类型,它们就是联合体和枚举体。

联合体

联合体和结构体非常相似:
结构体的关键字是struct,联合体的关键字是union
结构体由一个或者多个类型不限的成员构成,联合体也是如此
结构体的成员可以通过’.‘或’->'来访问,联合体也是这样
结构体的内存开辟要以内存对齐为前提,联合体还是这样

联合体类型声明

联合体的类型声明格式如下:

union 联合体名
{
	数据类型 成员名1;
    数据类型 成员名2;
    ...
};

联合体变量创建格式

联合体变量的创建格式如下:

union 联合体名 变量名;

联合体成员访问

#include<stdio.h>

union test
{
	char _c;
	int _i;
};

int main()
{
	union test t = { 0 };
	//直接访问
	t._i = 314;
	printf("%d\n", t._i);
	//间接访问
	(&t)->_c = 'h';
	printf("%c\n", (&t)->_c);
	return 0;
}

在这里插入图片描述

联合体内存开辟

尽管联合体和结构体的内存开辟都建立在内存对齐的基础上,但具体的开辟方式还存在差异:
结构体中的成员内存空间相互独立,互不干扰;联合体中的成员内存空间存在重叠,相互联系,若其中一个成员数值被修改,其它成员数值也会改变。
还是上面的例子,若修改代码顺序,结果就会不同:

#include<stdio.h>

union test
{
	char _c;
	int _i;
};

int main()
{
	union test t = { 0 };
	t._i = 314;
	(&t)->_c = 'h';
	printf("%d\n", t._i);
	printf("%c\n", (&t)->_c);
	return 0;
}

在这里插入图片描述

随笔——自定义类型:联合和枚举——示例一

联合体内存开辟的步骤十分简单:

  1. 找出联合体中内存占用最大的成员,把它对齐到联合体的0偏移量地址处
  2. 找出联合体成员中最大的对齐数,把联合体的大小补成这个最大对齐数的整数倍

还是以上面的代码为示例:
在这里插入图片描述
在这里插入图片描述
内存开辟好了,该怎么存数呢?
所有数据都从低地址处开始存。
为了方便描述,换个量赋值:

#include<stdio.h>

union test
{
	char _c;
	int _i;
};

int main()
{
	union test t = { 0 };
	t._i = 0x44332211;
	(&t)->_c = 0x00;
	return 0;
}

在这里插入图片描述
在这里插入图片描述
上面是没有补位的例子,下面是有补位的例子:

union test
{
	char _c[5];
	int _i;
};

在这里插入图片描述
在这里插入图片描述


相同成员的结构体和联合体比较:
在这里插入图片描述
在这里插入图片描述

联合体的应用

由于联合体成员间互相影响的特点,联合体仅适用于只使用其中一个成员的场景。
比如,我们要搞⼀个活动,要上线一个礼品兑换单,礼品兑换单中有三种商品:图书、杯子、衬衫。
每⼀种商品都有:库存量、价格、商品类型和商品类型相关的其他信息。

图书:书名、作者、页数
杯子:设计
衬衫:设计、可选颜色、可选尺寸

如果之前没学过联合体,可能就只能这样写:

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;
};

比如把书名改为为"联合体设计":

#include<stdio.h>
#include<string.h>

int main()
{
	struct gift_list gl_book;
	char title[20] = { "联合体设计" };
	size_t n = sizeof(title);
	strncpy(&(gl_book.item.book._title), title, n + 1);
	printf("%s\n", gl_book.item.book._title);
	return 0;
}

在这里插入图片描述


其实这个程序我一开始是用:

int main()
{
	struct gift_list gl_book;
	gl_book.item.book._title = "联合体设计";
	return 0;
}

的形式写的,后来发现报错了,然后才意识到,在创建变量gl_book的时候编译器已经把它初始化了,所以要用strncpy拷贝过去。


另一个应用:
还记得我们在《随笔——数据在内存中的存储》写过一个判断某个机器字节序的程序吗?它是用取地址,强制类型转换实现的:

#include<stdio.h>

int main()
{
	int i = 1;
	char j = (*(char*)&i);
	if (j)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}

借助于联合体成员间相互联系,数据都从低地址出存起的特点,如今我们可以用联合体实现相同的功能:

#include<stdio.h>

int check_sys(void)
{
	union test
	{
		char c;
		int i;
	};
	union test t = { 0 };
	t.i = 1;
	if (t.c)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}
int main()
{
	int ret = check_sys();
	if (ret)
	{
		printf("小端");
	}
	else
	{
		printf("大端");
	}
	return 0;
}

枚举体

枚举体顾名思义,就是一一列举
比如在日常生活中

一周的星期一到星期日是有限的7天,可以一一列举
性别有:男、女、保密,也可以一一列举
月份有12个月,也可以一一列举
三原色,也是可以一一列举

这些数据就可以用枚举表示:

enum Day//星期
{
	Mon,
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun
};

enum Sex//性别
{
	MALE,
	FEMALE,
	SECRET
};

enum Color//颜⾊
{
	RED,
	GREEN,
	BLUE
};

其中的成员被称为枚举常量
从中我们可以发现,枚举的关键字是enum,成员后面是逗号而非分号,最后一个成员后面没有标点
以我的个人理解来说,枚举有些像错误码,其中的枚举常量背后都有一个序列号,序列号默认从0开始,这个序列号被称为枚举值:

#include<stdio.h>

int main()
{
	printf("%d\n", RED);
	printf("%d\n", GREEN);
	printf("%d\n", BLUE);
	return 0;
}

在这里插入图片描述
枚举值在枚举声明的时候可以更改,更改过后,后面的枚举值会在此基础上依次加一:

#include<stdio.h>

enum Color//颜⾊
{
	RED = 2,
	GREEN,
	BLUE
};

int main()
{
	printf("%d\n", RED);
	printf("%d\n", GREEN);
	printf("%d\n", BLUE);
	return 0;
}

在这里插入图片描述

#include<stdio.h>

enum Color//颜⾊
{
	RED,
	GREEN = 5,
	BLUE
};

int main()
{
	printf("%d\n", RED);
	printf("%d\n", GREEN);
	printf("%d\n", BLUE);
	return 0;
}

在这里插入图片描述
不过我只说过枚举常量在声明时可以改变枚举值,声明过后,由于它是常量,就不能改了。

枚举类型在内存中通常存储为整数,毕竟枚举值就是整数。

注意:枚举常量和枚举值是两个不同的概念,比如对于上面的

enum Color//颜⾊
{
	RED,
	GREEN,
	BLUE
};

其中,枚举常量是RED,GREEN,BLUE;枚举值是0,1,2。


经常有人拿#define和枚举比较:

enum Color//颜⾊
{
	RED,//0
	GREEN,//1
	BLUE//2
};

#define RED 0
#define GREEN 1
#define BLUE 2

似乎功能上是一样的,那它们有什么区别呢?

枚举的优点:

  1. 增加代码可读性;
    比如我们之前写过的计算器程序,大致结构是这样的:
void menu()
{
	printf("*************************");
	printf("**** 1.add     2.sub ****");
	printf("**** 3.mul     4.div ****");
	printf("*****    0.exit    ******");
	printf("*************************");
}

int main()
{
	int input = 0;
	printf("请选择:");
	scanf("%d",&input);
	switch (input)
	{
	case 1://略
		break;
	case 2://略
		break;
	case 3://略
		break;
	case 4://略
		break;
	default:
		break;
	}
	return 0;
}

有了枚举就可以这样写了:

void menu()
{
	printf("*************************");
	printf("**** 1.add     2.sub ****");
	printf("**** 3.mul     4.div ****");
	printf("*****    0.exit    ******");
	printf("*************************");
}

enum option
{
	exit,
	add,
	sub,
	mul,
	div
};

int main()
{
	int input;
	printf("请选择:");
	scanf_s("%d", &input);
	switch (input)
	{
	case add://略
		break;
	case sub://略
		break;
	case mul://略
		break;
	case div://略
		break;
	default:
		break;
	}
	return 0;
}
  1. 枚举有类型检查,相比#define定义的标识符,更加严谨安全;
    之前我们在 《随笔——自定义类型:结构体》说过,#define相当于内容替换,它会把遇到的所有符合的标识符替换,枚举则有一个类型检查的过程,对于语法更严谨的C++来说,这种类型检查更为明显;
    在C中,对于枚举,可以这样赋值:
#include<stdio.h>

enum Color//颜⾊
{
	RED,//0
	GREEN,//1
	BLUE//2
};

int main()
{
	enum Color color;
	color = RED;
	return 0;
}

也可以这样赋值:

#include<stdio.h>

enum Color//颜⾊
{
	RED,//0
	GREEN,//1
	BLUE//2
};

int main()
{
	enum Color color;
	color = 0;
	return 0;
}

此时还看不出#define与枚举间的区别,这两种方法#define也可以用;
换成C++就不一样了,此时#define两种方法还是都可以用,但枚举就只能用第一种方法了:
VS的错误提示:

错误(活动)	E0513	不能将 "int" 类型的值分配到 "Color" 类型的实体	

0是int,无法赋给enum Color的变量

不过这到底是优点还是缺点,严格来说,要看开发环境,比如对于嵌入式来说,这个就是优点,单片机就那么大,多个类型检查就多点负担。所以此时#define比枚举更好

  1. 与#define相比,枚举便于调试;#define在预处理阶段就会被删除替换
    如果你这样写:
#define RED 0
#define GREEN 1
#define BLUE 2

int main()
{
	enum Color color;
	color = RED;
	return 0;
}

预处理阶段后,代码实际就变成这样了:

int main()
{
	enum Color color;
	color = 0;
	return 0;
}

这会导致肉眼所见的代码和实际调试或者运行的代码不一样,非常容易误导人。枚举就不会被替换,肉眼代码和实际代码是一致的。

  1. 枚举常量是有作用域的,如果声明在函数里,作用域就是那个函数,如果声明在函数外,作用域就是那个文件;#define没有作用域,只要标识符对得上,就统统替换,它的使用范围无法被限制和控制,是不可控的,谁知道#define会不会把不该替换的替换了。
  2. 枚举可以一次定义多个常量,#define要一个一个定义。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值