自定义类型:联合体与枚举

目录

[1 · 联合体](#1 · 联合体)

[1 - 1 · 联合体类型的声明](#1 - 1 · 联合体类型的声明)

[1 - 2 · 联合体的特点](#1 - 2 · 联合体的特点)

[1 - 3 · 相同成员的结构体和联合体对比](#1 - 3 · 相同成员的结构体和联合体对比)

[1 - 4 · 联合体大小的计算](#1 - 4 · 联合体大小的计算)

[1 - 5 · 关于联合体的例子](#1 - 5 · 关于联合体的例子)

[1 - 6 · 使用联合体判断大小端](#1 - 6 · 使用联合体判断大小端)

[2 · 枚举](#2 · 枚举)

[2 - 1 · 枚举类型的声明](#2 - 1 · 枚举类型的声明)

[2 - 2 · 枚举类型的优点](#2 - 2 · 枚举类型的优点)

[2 - 3 · 枚举类型的使用](#2 - 3 · 枚举类型的使用)

总结


C语言支持的自定义类型还剩下联合体和枚举没有介绍了,本篇将介绍这两个。

1 · 联合体

1 - 1 · 联合体类型的声明

联合体的关键字是 union
像结构体⼀样,联合体也是由⼀个或者多个成员构成,这些成员可以不同的类型。
但是编译器只为最大的成员分配足够的内存空间。联合体的特点是所有成员共用同⼀块内存空间。所以联合体也叫:共用体。
给联合体其中⼀个成员赋值,其他成员的值也会跟着变化。
下面我们写一段代码:

复制代码
union Un
{
	//联合体的声明
	char c;
	int i;
};

这就是联合体的声明,可以有不同类型的多个成员,那么此时这个联合体的大小是多少呢?

cpp 复制代码
#include <stdio.h>

union Un
{
	//联合体的声明
	char c;
	int i;
};

int main()
{
	union Un un = { 0 };
	printf("%zd\n", sizeof(un));
	return 0;
}

运行一下试试:

char 类型的大小是1个字节,int 类型的大小是4个字节,这里我们就能看出这两个成员变量大概率是共用一片空间的。下面我们展开介绍联合体的特点,并测试一下。


1 - 2 · 联合体的特点

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

cpp 复制代码
#include <stdio.h>

union Un
{
	//联合体的声明
	char c;
	int i;
};

int main()
{
	union Un un = { 0 };
	printf("联合体的地址:   %p\n", &un);
	printf("成员变量c的地址:%p\n", &(un.c));
	printf("成员变量i的地址:%p\n", &(un.i));
	un.i = 0x11223344;
	printf("i的值为:%x\n", un.i);
	un.c = 0x55;
	printf("i的值为:%x\n", un.i);
	return 0;
}

运行一下:

便于理解,我们可以画个图:

所以在联合体中,所有成员变量共用同一块空间,从低地址开始共用,修改其中一个成员变量的值可能会影响其他成员变量,所以在使用联合体时最好只使用其中一个变量。因此,联合体的使用场景为 有一些东西,当使用其中一个的时候,就不用其他的,这样的话这些东西就可以放在联合体中。


1 - 3 · 相同成员的结构体和联合体对比

假设我们现在有一个和上面联合体相同成员变量的结构体:

cpp 复制代码
struct S
{
	char c;
	int i;
};

我们照样画张图对比一下:

结构体中 i 和 c 分别有各自独立的空间,而联合体中 c 和 i 是共用同一块空间


1 - 4 · 联合体大小的计算

可能我们会认为联合体只需要为最大的那个成员变量开辟空间就可以了,虽然在我们上面的例子中联合体的大小就是最大成员的大小,但其实这是不一定的。实际上,联合体大小至少是最大成员的大小。
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
下面我们举个例子:

cpp 复制代码
#include <stdio.h>

union Un
{
	char c[5];
	int i;
};

int main()
{
	union Un u = { 0 };
	printf("%zd\n", sizeof(u));
	return 0;
}

运行一下:

char c[5] 是一个 char 类型的数组,大小为5,占5字节,但是由于是 char 类型,所以本身的对齐数为1,VS2022 中默认对齐数是8,所以 c成员变量的对齐数是 1,i 占4字节,对齐数是4。

所以此时最大成员大小是5,但是对齐数是4,所以联合体大小要对齐到 8 。


1 - 5 · 关于联合体的例子

比如,我们要搞⼀个活动,要上线⼀个礼品兑换单,礼品兑换单中有三种商品:图书、杯子、衬衫。
每⼀种商品都有:库存量、价格、商品类型和商品类型相关的其他信息。
图书:书名、作者、页数
杯子:设计
衬衫:设计、可选颜色、可选尺寸
那我们简单思考,可以直接把这些东西全部放在一个结构体中:

复制代码
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;//尺⼨
};

上面的结构体其实设计的很简单,用起来也方便,但是结构的设计中包含了所有礼品的各种属性,这样使得结构体的大小就会偏大,比较浪费内存。因为对于礼品兑换单中的商品来说,只有部分属性信息是常用的。比如:
商品是图书,就不需要design、colors、sizes。
所以我们就可以把公共属性单独写出来,剩余属于各种商品本身的属性放进联合体,这样就可以
节省所需的内存空间。


1 - 6 · 使用联合体判断大小端

cpp 复制代码
#include <stdio.h>

union Un
{
	int i;
	char c;
};

int Check()
{
	union Un u = { 0 };
	u.i = 1;
	return u.c;
}

int main()
{
	int ret = Check();
	if (ret == 1)
	{
		printf("是小端\n");
	}
	else
	{
		printf("是大端\n");
	}
	return 0;
}

将 i 赋值为1,其实也就是 0x00 00 00 01,如果是小端,那么低位数据放在低地址,而联合体是从低地址开始共用的,所以如果 c == 1 ,说明此时是小端,如果 c == 0 ,说明此时是大端。


2 · 枚举

2 - 1 · 枚举类型的声明

枚举顾名思义就是一一列举。
把可能的取值一一列举。
比如我们现实生活中:
一周中星期一到星期日是有限的七天,可以一一列举
一年当中有12个月,可以一一列举。
那么这些数据的表示就可以使用枚举:

cpp 复制代码
enum Day//星期
{
	Mon,
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun
};

这里的 enum Day 就是枚举类型,大括号{ }中的内容是枚举类型的可能取值,也叫 枚举常量 。

每一个可能取值后面要跟逗号。

我们可以看一看:

cpp 复制代码
int main()
{
	printf("%d\n", Mon);
	printf("%d\n", Tues);
	printf("%d\n", Wed);
	printf("%d\n", Thur);
	return 0;
}

运行一下:

可以看到,这些枚举常量都是有值的,默认从0开始,以此递增1。

既然是枚举常量,那么是不可以给枚举常量赋值的 如果想要改变枚举常量的值,需要在声明时赋初值:

cpp 复制代码
#include <stdio.h>

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

int main()
{
	printf("%d\n", Mon);
	printf("%d\n", Tues);
	printf("%d\n", Wed);
	printf("%d\n", Thur);
	printf("%d\n", Fri);
	return 0;
}

运行一下:

可以看到,给一个枚举常量赋初始值,会影响后续的枚举常量的值。


2 - 2 · 枚举类型的优点

如果想要定义一个常量,我们在扫雷那一篇中是使用过 #define 的,那么枚举有什么优点呢:

  1. 增加代码的可读性和可维护性
  2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
  3. 便于调试,预处理阶段会删除 #define 定义的符号
  4. 使用方便,⼀次可以定义多个常量
  5. 枚举常量是遵循作用域规则的,枚举声明在函数内,只能在函数内使用
    比如我们之前写简易计算器用到了 switch 然后 case1 等等,如下:
cpp 复制代码
void Menu()
{
	printf("*****************\n");
	printf("*  1:Add  2:Sub *\n");
	printf("*  3:Mul  4:Div *\n");
	printf("*      0:exit   *\n");
	printf("*****************\n");
}

switch (input)
{
case 1:
	...
	break;
case 2:
	...
	break;
case 3:
	...
	break;
case 4:
	...
	break;
case 0:
	printf("成功退出\n");
	break;
default:
	printf("输入有误,请重新输入\n");
	break;
}

那么我们使用枚举,就可以这样写:

cpp 复制代码
enum Option
{
	EXIT,
	ADD,
	SUB,
	MUL,
	DIV
};

void Menu()
{
	printf("*****************\n");
	printf("*  1:Add  2:Sub *\n");
	printf("*  3:Mul  4:Div *\n");
	printf("*      0:exit   *\n");
	printf("*****************\n");
}

switch (input)
{
case ADD:
	...
	break;
case SUB:
	...
	break;
case MUL:
	...
	break;
case DIV:
	...
	break;
case EXIT:
	printf("成功退出\n");
	break;
default:
	printf("输入有误,请重新输入\n");
	break;
}

相当于给常量值起了个名字,这样代码的可读性就提高了。


2 - 3 · 枚举类型的使用

简单来说就是声明一个枚举类型,然后定义枚举变量,将枚举常数赋值给枚举变量,如下:

cpp 复制代码
enum Color//颜⾊
{
    RED=1,
    GREEN=2,
    BLUE=4
};

enum Color clr = GREEN;//使⽤枚举常量给枚举变量赋值

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


总结

以上简单介绍了联合体和枚举相关内容,关于C语言的其余内容,请期待后续更新


以上内容如有错误或不准确之处,欢迎指出,或者你有更好的想法,也欢迎交流。

相关推荐
Hello.Reader2 小时前
Windows C 盘空间告急?用 PowerShell 写一个安全可控的清理脚本
c语言·windows·安全
三品吉他手会点灯2 小时前
C语言学习笔记 - 17.C编程预备计算机专业知识 - 数据类型
c语言·笔记·学习
handler012 小时前
【Linux 笔记】GDB 调试速查手册
linux·运维·c语言·c++·笔记
我不是懒洋洋2 小时前
【数据结构】二叉树链式结构的实现(二叉树的遍历、使用二叉树的基本方法、二叉树的创建和销毁)
c语言·数据结构·c++·经验分享·算法·链表·visual studio
[J] 一坚11 小时前
嵌入式高手C
c语言·开发语言·stm32·单片机·mcu·51单片机·iot
qeen8713 小时前
【数据结构】树的基本概念及存储
c语言·数据结构·c++·学习·
pluviophile_s18 小时前
第18讲:⾃定义类型:结构体
c语言·笔记
꧁细听勿语情꧂20 小时前
向下调整算法,top - k 问题,链式结构二叉树,前中后序遍历
c语言·开发语言·数据结构·算法
SHARK_pssm20 小时前
【数据结构——顺序表】
c语言·数据结构·经验分享·笔记