C语言笔记第13篇:自定义类型(联合union和枚举enum)

1、联合体

1.1 联合体类型的声明

像结构体一样,联合体也是由一个或多个成员构成,这些成员可以是不同的类型。

但是编译器只为最大的成员分配足够的内存空间,联合体的特点是所有成员共用一块内存空间,所以联合体也叫:共用体

struct 是结构体类型前缀,union是联合体类型前缀。

和结构体一样,联合体类型的声明也是这样的:

cpp 复制代码
#include <stdio.h>
union U
{
    char c;
    int i;
};
int main()
{
    union U u = {0};
    printf("%d\n",sizeof(u));
    return 0;
}

结果为4个字节;不对啊!一个int类型成员是4字节,还有一个char类型的成员,加起来怎么说也得有5个字节,为什么只有4个字节呢?这就是联合体的特点。

看下面的代码:

cpp 复制代码
#include <stdio.h>
union U
{
    char c;
    int i;
};
int main()
{
    union U u = { 0 };
    printf("%p\n", &u);
    printf("%p\n", &(u.i));
    printf("%p\n", &(u.c));
    return 0;
}

运行结果:

三个地址还是一样,我们可以来分析一下为什么。如果三个地址一样可以说明这个联合体变量只有一块4个字节的空间,所以联合体变量本身的地址就是这块空间的首字节地址,然后就是2个成员,2个成员的地址相同说明什么,说明它们共用一块空间,这就是联合体的特点。

1.2 联合体的特点

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

知道了联合体的特点,那来看一下以下代码会打印什么:

cpp 复制代码
#include <stdio.h>
union U
{
    char c;
    int i;
};
int main()
{
    union U u = { 0 };
    u.i = 0x11223344;
    u.c = 0x55;
    printf("%#x\n", u.i);
    return 0;
}

运行结果:

1.3 相同成员的结构体和联合体对比

我们再对比一下相同成员的结构体和联合体的内存布局情况。

cpp 复制代码
struct S
{
     char c;
     int i;
};
struct S s = {0};
cpp 复制代码
union Un
{
    char c;
    int i;
};
union Un un = {0};
1.4 联合体大小的计算

联合体的大小至少是最大成员的大小。

当最大成员的大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

联合体的最终大小也是要对齐到最大对齐数的整数倍的,既然知道了联合体大小的计算,那来计算一下这个代码的结果:

cpp 复制代码
#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;
}

运算结果:

最大对齐数还是4,因为成员i是最大对齐数,千万不要以为数组的整体大小才算对齐数,其实数组的对齐数就是数组每个成员类型的大小。

1.5 联合体的应用场景

知道了联合体是什么,什么特点以及怎么使用,那联合体的应用场景是什么?

先举个例子:比如我想写一个游戏,需要有一个架构来保存角色的不同职业信息。那有人可能会写出这样的代码:

cpp 复制代码
struct Game
{
	//角色基础信息
	char name[20];//名字
	char sex[5];//性别
	enum color c;//角色头发颜色
	
	//剑士
	int l1;//攻击
	struct K k;//剑士技能

	//刺客
	int j1;//机敏
	struct C c;//刺客技能
};

当我选择剑士时,只使用给剑士数据开辟的空间,当我选择刺客时,只使用给刺客数据开辟的空间。虽然只选择一个职业时,只给一个职业的内存存入数据。由于是结构体,另一个未选择的职业也是有开辟空间的。这就导致了开辟了多余的空间但却空着不使用,从而造成了空间浪费。这时候联合体union就派上了用处,如果只想给架构中一部分变量的内存存入数据,并保证另一部分不占用空余的空间就使用联合体。相当于两个不同角色职业的数据可以存储在同一个内存空间,但并不是两个一块存储,而是有一方需要存入数据时保证另一方不占用多余空间,而使用另一方存入数据时保证这一方不会占用多余的空间,这就是联合体的作用

cpp 复制代码
struct Game
{
	//角色基础信息
	char name[20];//名字
	char sex[5];//性别
	enum color;//角色头发颜色

    //职业数据
	union{  //如果在内部创建只使用一次,创建一次内部可以无限调用,所以可以在结构体内部创建匿名联合体或结构体
		//剑士
		struct{
			int l1;//攻击
			struct K Sdm;//剑士技能
		}Swordsman;
		
		//刺客
		struct{
			int j1;//机敏
			struct C Asin;//刺客技能
		}assassin;
	}un;
};

这下应该知道union联合体的作用了吧!

**union联合体的应用场景:**当有两个或多个相同类型的数据需要一个结构来集成在一起,但是每次使用只使用一个类型的空间,我们可以将这多个类型的全部集成一个联合体,每个类型的地址都是一块空间,相当于共用一块,使用一个类型也保证了其他类型不额外占用多余空间。

联合体练习:

我们也可以通过联合体来判断当前场景为大端还是小端:

cpp 复制代码
#include <stdio.h>
union Un
{
	int i;
	char c;
};//因为是共用4个字节,并且两个成员的地址都是首字节低地址处
int main()
{
	union Un un = { 0 };
	un.i = 1;//将里面的i赋值为1,小端会将1的低位字节放在低地址处,大端会将低位字节放在高地址处
	if (un.c == 1)//成员c本身就是这块空间的低地址,只需要判断低地址处的是1还是0
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}

2、枚举类型

2.1 枚举类型的声明

没枚举顾名思义就是一一列举。

把可能的取值一一列举。

比如我们现实生活中:

一周的星期一到星期日是有限的7天,可以一一列举

性别有:男、女、保密,也可以一一列举

月份有12月,也可以一一列举

三原色,也是可以一一列举

这些数据的表示就可以使用枚举了。

cpp 复制代码
enum Day//星期
{
	Mon,
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun
};
enum Sex//性别
{
	MALE,
	FAMALE,
	SECRET
};
enum color//颜色
{
	RED,
	GREEN,
	BLUE
};

这里枚举里的常量都是列出的枚举类型的可能取值

这些列出的可能取值被称为:枚举常量

每个枚举里的常量,从第一个默认都是0,依次向下增长的常量集合。

cpp 复制代码
#include <stdio.h>
enum Day//星期
{
	Mon,
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun
};
int main()
{
	printf("%d %d %d %d %d %d %d\n", Mon, Tues, Wed, Thur, Fri, Sat, Sun);
	return 0;
}

运行结果:

从这里我们可以看出,枚举和(联合、结构体)的格式是不相同的,枚举里的不是成员,而是标识符常量,定义了这些标识符我们就可以直接使用该标识符来打印对应的常量,不用再额外创建该枚举类型变量再访问该标识符。所以简单来说枚举类型就是一堆标识符常量的集合类型。

如果不想默认从0开始打印我们就可以更改第一个标识符赋值一个值,后面的标识符的值则是该值依次增长所得到的值。

cpp 复制代码
#include <stdio.h>
enum Day//星期
{
	Mon=5,
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun
};
int main()
{
	printf("%d %d %d %d %d %d %d\n", Mon, Tues, Wed, Thur, Fri, Sat, Sun);
	return 0;
}

运行结果:

注意:只有在声明枚举常量时里面的标识符可以被赋予一个初始值,但是声明好后在去给枚举里的标识符常量赋值是会报错的,原因是该标识符是常量,不能被更改。

cpp 复制代码
enum Day//星期
{
	Mon=5,
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun
};
int main()
{
	Mon = 10;//error
	return 0;
}
2.2 枚举类型的优点

为什么使用枚举呢?

我们可以使用#define定义常量,为什么非要使用枚举?

枚举的优点:

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

2. 和#define定义的标识符比较枚举有类型检查,更加严谨

**3.**便于调试,预处理阶段会删除#define定义的符号

**4.**使用方便,一次可以定义多个常量

**5.**枚举常量是遵循作用域规则的,枚举声明在函数内,只能在函数内使用

2.3 枚举类型的使用
cpp 复制代码
enum Color
{
	RED = 1,
	GREEN = 2,
	BLUE = 3
};
enum Color clr = GREEN;//使用枚举常量给枚举变量赋值

那是否可以拿整数给枚举变量赋值呢?在C语言中是可以的,但是在C++是不行的,C++的类型检查比较严格。

本篇博客到这里也就结束了,再见

相关推荐
一个很帅的帅哥6 分钟前
JavaScript事件循环
开发语言·前端·javascript
驰羽7 分钟前
[GO]gin框架:ShouldBindJSON与其他常见绑定方法
开发语言·golang·gin
摇滚侠7 分钟前
Spring Boot 3零基础教程,WEB 开发 自定义静态资源目录 笔记31
spring boot·笔记·后端·spring
摇滚侠8 分钟前
Spring Boot 3零基础教程,WEB 开发 Thymeleaf 遍历 笔记40
spring boot·笔记·thymeleaf
程序员大雄学编程13 分钟前
「用Python来学微积分」5. 曲线的极坐标方程
开发语言·python·微积分
Code小翊18 分钟前
C语言bsearch的使用
java·c语言·前端
Jose_lz1 小时前
C#开发学习杂笔(更新中)
开发语言·学习·c#
Chloeis Syntax1 小时前
接10月12日---队列笔记
java·数据结构·笔记·队列
QT 小鲜肉1 小时前
【个人成长笔记】Qt 中 SkipEmptyParts 编译错误解决方案及版本兼容性指南
数据库·c++·笔记·qt·学习·学习方法
一位代码1 小时前
python | requests爬虫如何正确获取网页编码?
开发语言·爬虫·python