前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【自定义类型详解】完结篇——枚举与联合体(共用体)详解

【自定义类型详解】完结篇——枚举与联合体(共用体)详解

作者头像
YIN_尹
发布2024-01-23 15:45:41
1630
发布2024-01-23 15:45:41
举报
文章被收录于专栏:YIN_尹的博客YIN_尹的博客

这篇文章我们继续来学习C语言中的另外两种自定义类型——枚举和联合(共用体),一起来学习吧!!!

1. 枚举

枚举也是C语言中的一种自定义类型。

1.1 什么是枚举

枚举顾名思义就是一 一列举。 把可能的取值一 一列举。 比如在我们的日常生活中:

每周的星期一到星期日都是有限的7天,可以一一列举 性别有:男、女,也可以一一列举。 月份有12个月,也可以一一列举

我们想描述这些值,就可以使用枚举。

1.2 枚举类型的定义

那枚举类型应该如果定义呢?

枚举的定义与结构体类似

代码语言:javascript
复制
enum tag
{
	xx,
};

解释一下:

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

要注意:枚举常量后面跟的是逗号’,'而不是分号,但是最后一个后面可以不加,这一点和结构体是不一样的。

举个例子:

代码语言:javascript
复制
enum Day//星期
{
	Mon,
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun
};
在这里插入图片描述
在这里插入图片描述

这就是一个枚举类型。 {}中的内容就是该枚举类型对应的所有可能取值。

1.3 枚举的使用与注意

那定义好了类型,我们就可以那这些类型来定义变量了: 比如:

代码语言:javascript
复制
enum Color//颜色
{
	RED,
	GREEN,
	BLUE
};

int main()
{
	enum Color col = RED;
}

使用enum Color这个类型定义一个变量,取值为RED。

  1. 这些大括号中的可能取值其实都是有值的,默认第一个为0,后面的依次递增1。

我们可以打印出来看一下:

代码语言:javascript
复制
int main()
{
	//enum Color col = RED;
	printf("%d\n", RED);
	printf("%d\n", GREEN);
	printf("%d\n", BLUE);
}
在这里插入图片描述
在这里插入图片描述
  1. 这些枚举类型的可能取值也叫做枚举常量,那既然是常量,就意味这不能被修改。

枚举常量不能修改:

在这里插入图片描述
在这里插入图片描述
  1. 虽然不能修改,但是我们可以在定义是给它们赋初值。
代码语言:javascript
复制
enum Color//颜色
{
	RED=5,
	GREEN,
	BLUE
};

这次我们在打印它们的值看看是多少:

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

如果只给第一个赋初值,就从该初值开始,还是依次增1。

代码语言:javascript
复制
enum Color//颜色
{
	RED,
	GREEN = 7,
	BLUE
};
在这里插入图片描述
在这里插入图片描述

如果中间某个赋初值,前面的还是默认值,后面的会依次增1。

代码语言:javascript
复制
enum Color//颜色
{
	RED = 3,
	GREEN = 7,
	BLUE = 5
};
在这里插入图片描述
在这里插入图片描述

全部赋初值,则按赋的值。

1.4 枚举的优点

学到现在,我们知道,枚举的取值就是一些常量嘛。

代码语言:javascript
复制
enum Color//颜色
{
	RED,
	GREEN,
	BLUE
};

我们这样给一个枚举,它们的默认值不就是0,1,2嘛。 那我们用#define不是也可以定义常量嘛。

代码语言:javascript
复制
#define RED 0
#define GREEN 1
#define BLUE 2

这样不是也可以嘛?

那我们为什么非要使用枚举呢? 既然我们选择使用它,就说明它是一些优点的。

那接下来,我们就来了解一下,枚举有哪些优点:

  1. 增加代码的可读性和可维护性

举个例子:

比如我们现在要写一个顺序表或者链表,我们知道,它们通常需要实现一些增删查改,打印,求长度,头插,尾插等等这些功能。 我们通常会搞一个switch语句,根据case后面不同的取值去调用不同的函数,来实现相应的功能。 最后我们还会做一个菜单,让用户在使用时根据菜单的提示做出相应的选择。 比如像这样的:

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

这样我们在看代码的时候,看到switch语句中case后面的值,可能还需要翻到上面看看它对应的是哪个功能。

但是,如果我们的程序已经完全写好之后,我们case语句对应的值,是不是就确定了,就这几个取值了。 那这个时候,我们是不是就可以考虑用一个枚举呢?

枚举的取值都是常量,而case后面的值要求的也必须是整型常量表达式。 那我们就可以这样搞:

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

每个枚举常量的默认值正好就是0到6。 现在就可以直接把这些枚举常量放在case后面了。

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

我们可以按照自己的情况把它们的名字设置成我们容易理解的,然后替换0,1,2,3,4…这些数字。 这样有时候就可以达到增强代码可读性的目的。

除此之外,还有一些其它的优点,我们一起来看一下:

  1. 和#define 定义的标识符比较枚举有类型检查,更加严谨。
  2. 防止了命名污染(封装)
  3. 便于调试
  4. 使用方便,一次可以定义多个常量

2. 联合体(共用体)

联合体也是一种特殊的自定义类型

2.1 联合类型的定义

那联合体要怎么定义呢?

与结构体一样,联合类型也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。

举个例子:

代码语言:javascript
复制
union Un
{
	char c;
	int i;
};
int main()
{
	//联合变量的定义
	union Un un;
	//计算联合变量的大小
	printf("%d\n", sizeof(un));
	return 0;
}
2.2联合的特点

那现在大家来思考一个问题,上面的联合体变量un的大小是多少?

在这里要注意上面的一句话:这些成员公用同一块空间(所以联合也叫共用体)。 联合体的成员共用一块空间。 那我们来看一下它的大小到底是多大?

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

为什么是4个字节呢?

联合体un只有两个成员,char c; int i;c 大小1个字节,i是最大的成员4个字节。 那么因为它们共用同一块空间,所以四个字节就够了。 4个字节既可以放得下c ,也能放得下i。

所以:

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。

那怎么证明它们是共用同一块空间的呢?

代码语言:javascript
复制
union Un
{
	int i;
	char c;
};
int main()
{
	union Un un;
	// 下面输出的结果是一样的吗?
	printf("%p\n", &(un.i));
	printf("%p\n", &(un.c));
	return 0;
}

我们是不是可以看一下它们的地址,如果共用一块空间,那地址肯定是一样的,对吧。 我们来看一下:

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

我们打印出来成员i和c的地址,是一样的!!! 注:我们看到联合体访问成员和结构体的方式是一样的。(un.i)

那他们共用一块内存空间意味着什么呢?

是不是意味着它们不能同时存在啊,或者说它们不能同时使用。 就拿上面的哪个union Un来说:

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

当我们使用成员 i 时,对于c来说,此时c的那一个字节里面是不是存的是 i 的内容,就相当于此时c是不存在的。

来看一段代码:

代码语言:javascript
复制
	un.i = 0x11223344;
	un.c = 0x55;
	printf("%x\n", un.i);

因为它们共用一块空间,上述代码中先给un.i赋值。 那此时un的四个字节的空间里放的应该是这样的内容: 假设左边低地址,右边高地址

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

那我们在去给un.c赋值,是不是就会改变1个字节的内容(因为un.c是1个字节)。 应该就变成这样了:

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

那我们再以printf("%x\n", un.i);(%x是以16进制形式打印)打印出来是不是就是11223355了。 来验证一下:

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

这样更好的验证了联合体的成员共用一块空间,不能同时存在。

2.3使用联合体解求机器字节序的问题

那接下来我们就使用联合体来解决一道题:

在之前的一篇文章——深度剖析整形数据在内存中的存储 中讲解过一道题:

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

大家如果忘了字节序相关的知识可以去复习一下。

当时我们是这样解的:

代码语言:javascript
复制
#include <stdio.h>
int main()
{
	int a = 1;
	if ((*(char*)&a) == 1)
		printf("小端");
	else
		printf("大端");
	return 0;
}
在这里插入图片描述
在这里插入图片描述

现在还是同样的思路,我们换一种方法,借助联合体解这道题:

代码语言:javascript
复制
int check_sys()
{
	union un
	{
		int i;
		char c;
	}u;
	u.i = 1;
	return u.c;
}
int main()
{
	int ret = check_sys();
	if (ret == 1)
		printf("小端");
	else
		printf("大端");
	return 0;
}

解释一下:

首先我们知道 i, c肯定占用的是同一块空间:

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

u.i = 1;执行之后,联合体u的4个字节里放的应该是

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

然后我们并没有给u.c赋值,而是直接返回u.c的值,那u.c只占1个字节,但是还是上面那四个字节的空间,所以返回的就是1,结果就应该是小端。

我们看一下结果:

在这里插入图片描述
在这里插入图片描述
2.4 联合体大小的计算

在联合体的特点里我们已经知道:

  1. 一个联合体的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)

我们在上面举的例子中的联合体大小就是它的最大成员的大小,但是不是所有的联合体大小都是这样呢? 不是的,联合体大小的计算其实也要考虑对齐的。

  1. 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。 举个例子:
代码语言:javascript
复制
#include <stdio.h>
union Un1
{
	char c[5];
	int i;
};
int main()
{
	printf("%d", sizeof(union Un1));
	return 0;
}

参照上面的规则,大家思考union Un1的大小是多少? 是最大成员的大小,5个字节吗?

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

是8个字节哎!为什么呢?

我们一起来算一下:

首先最大成员的大小是几? 是不是5啊,最大的成员应该是char c[5];,大小5个字节,那按照上面第二条规则,我们是不是要判断一下5是不是最大对齐数的整数倍啊。 首先char c[5];,这里要注意,它是一个数组,计算它的的对齐数的时候比的是数组一个元素的大小和默认对齐数,去它们之中的较小值。 那char c[5];的类型是char,每个元素1个字节,小于8,所以对齐数是1; 另外一个成员int i;对齐数是4,5不是4的整数倍,所以要对齐到4的整数倍,因此union Un1的大小是8个字节。

好了,讲到这里,C语言中的自定义类型就全部学习完了。总共写了3篇文章,欢迎大家指正!!! 我们一起进步!!!

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-08-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 枚举
    • 1.1 什么是枚举
      • 1.2 枚举类型的定义
        • 1.3 枚举的使用与注意
          • 1.4 枚举的优点
          • 2. 联合体(共用体)
            • 2.1 联合类型的定义
              • 2.2联合的特点
                • 2.3使用联合体解求机器字节序的问题
                  • 2.4 联合体大小的计算
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档