【C语言】自定义类型:联合体和枚举

在这里插入图片描述

文章目录

  • 一、联合体(共同体)
    • 1.联合体类型的声明
    • 2.联合体的特点
      • 测试1
      • 测试2
    • 3.联合体大小的计算
      • 例1
      • 例2
    • 4.联合体小练习
    • 5.结构体和联合体内存占用的对比
    • 6.联合体的应用
  • 二、枚举
    • 1.枚举类型的声明
    • 2.枚举类型的优点
    • 3.枚举类型的使用

一、联合体(共同体)

1.联合体类型的声明

   像结构体⼀样,联合体也是由⼀个或者多个成员构成,这些成员可以是不同的类型
   联合体的特点是所有成员共⽤同⼀块内存空间,所以联合体也叫共同体,由于所有成员共用一块空间,所以编译器只为最⼤的成员分配足够的内存空间 ,并且当给联合体其中⼀个成员赋值时,其他成员的值也跟着变化,我们后面也会讲到
   现在我们从联合体类型的声明开始学习,它的声明也和结构体的声明相似,结构体声明时使用struct关键字,而联合体声明时使用union关键字,如下:

union un
{
	char c;
	int i;
};

   它创建变量的方式和结构体都是类似的,如下:

union un
{
	char c;
	int i;
};

int main()
{
	union un s;
	return 0;
}

   其它语法知识点和结构体都是类似的,这里就不再多讲了,接着学习联合体的特点,也可以说是结构体和联合体的区别之处
   如果还没有学习结构体,可以参考文章:【C语言】自定义类型:结构体

2.联合体的特点

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

测试1

   现在我们来做个测试,测试一下联合体成员的地址是否相同,以及联合体本身的地址和它成员地址的关系,如下代码:

#include <stdio.h>

union Un
{
	char c;
	int i;
};
int main()
{
	union Un un = { 0 };
	printf("%p\n", &(un.i));
	printf("%p\n", &(un.c));
	printf("%p\n", &un);
	return 0;
}

   接着我们运行它,来看看它们三个地址的关系:
在这里插入图片描述
   可以看到,联合体成员的地址是相同的,并且联合体本身也和联合体成员的地址相同,基本上就可以说明,联合体开辟空间时,所有成员共用一块空间

测试2

   接下来我们再举一个例子来测试联合体的空间是否是共用的,如下:

#include <stdio.h>

union Un
{
	char c;
	int i;
};

int main()
{
	union Un un = { 0 };
	un.i = 0x11223344;
	un.c = 0x55;
	printf("%x\n", un.i);
	return 0;
}

   试着分析这个代码最后的运行结果,一定要先自己试着分析,然后再看答案
我们来看分析:首先前面声明了一个联合体类型Un,然后使用它创建了一个联合体变量un,随后将其初始化为了0
   然后进行了两次赋值,给i赋值了16进制数11223344,把c赋值为了16进制数55,如果i和c共用相同的空间,那么我们在更改c的时候,i应该也会跟着改变,我们来看看内存里的存储:
   这是初始化完i时,结构体内存的存储:
在这里插入图片描述
   可以看到,这里VS用小端字节序的方式将i存放到了内存中,如果不知道什么是小端字节序,可以参考该文章:【C语言】数据在内存中的存储(万字解析)
   现在已经把i存放进内存了,代码下一步就是将c改成16进制数55,那我们看看i会不会跟着一起改变:
在这里插入图片描述
   可以看到内存中i的第一个字节被修改为了55,说明更改c确实连带着把我们的i更改了,我们可以画一个图来更清楚的阐述这个变化过程:
在这里插入图片描述
   所以在这个联合体中,我们可以分析得到:整型变量i和字符变量c占据同一块空间,而c占据的就是i的第一个字节,当我们对c进行修改时,也就是对i第一个字节的修改,所以当我们把c修改为0x55时,i也就跟着改变了
   我们来看看代码的运行结果:
在这里插入图片描述

   可以看到,程序的运行结果与我们分析的一致,所以根据这个例子,我们再一次证明了联合体的成员共用一段相同的空间

3.联合体大小的计算

   我们首先来看联合体在存储时的两个规则:

  1. 联合体的大小至少是最⼤成员的大小,确保联合体的大小可以装下每一个单一成员
  2. 当最⼤成员大小不是最大对齐数的整数倍的时候,就要对⻬到最⼤对⻬数的整数倍

例1

   还是老方法,实践出真知,我们举一个例子来说明这两条规则:

#include <stdio.h>

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

int main()
{
	//下⾯输出的结果是什么?
	printf("%d\n", sizeof(union Un1));
	return 0;
}

   我们首先看第一条规则:联合体的大小至少是最大成员的大小,而我们这里的联合体Un1它最大的成员是c,是一个字符数组,大小是5个字节,所以根据第一条规则,这个联合体的大小至少是5个字节
   然后就是第二条规则,这条规则需要查看这个联合体中的最大对齐数,联合体的大小必须是最大对齐数的倍数,而联合体成员c的对齐数为1,成员i的对齐数为4,所以可以知道该联合体最大对齐数为4
   根据第一条规则我们知道了联合体Un1的大小至少是5个字节,根据第二条规则我们知道了Un1的大小必须是4的倍数,所以综和这两点,答案已经呼之欲出:结构体Un1的大小是8个字节
   最后我们来看看代码运行结果:
在这里插入图片描述

例2

#include <stdio.h>

union Un2
{
	short c[7];
	int i;
};

int main()
{
	//下⾯输出的结果是什么?
	printf("%d\n", sizeof(union Un2));
	return 0;
}

   通过例1,我们已经知道两个规则具体的作用了,现在我们做这个题应该是比较简单的,但是我们还是像例1那样仔细分析一下:
   首先根据第一条规则,我们要看成员中,谁最大,很明显最大的就是c数组,占据14个字节,所以联合体Un2至少都有14个字节
   然后来看第二个规则,我们要看成员中的最大对齐数,第一个成员c的对齐数是2,第二个成员的对齐数是4,所以联合体Un2的最大对齐数是4,它的大小应该是4的倍数
   所以综上两个规则,联合体Un2的大小至少14个字节,还要是4的倍数,所以联合体Un2的大小为16个字节,我们来看看运行结果:
在这里插入图片描述

4.联合体小练习

   使用联合体写⼀个程序,判断当前机器是大端字节序还是小端字节序
   我们先复习一下之前采用的方法,方便我们思考这道题,是创建一个整型变量,赋值为1,然后将它的地址强制转换为字符类型存放起来,然后通过这个字符指针去访问整型变量的第一个字节,看看拿到的是否是1
   如果是1说明是小端字节序,是0就是大端字节序,这里再放一下它的代码,如下:

#include <stdio.h>

int main()
{
	int a = 1;
	char* p = (char*)&a;
	if (*p == 1)
		printf("小端字节序\n");
	else
		printf("大端字节序\n");
	return 0;
}

运行结果:
在这里插入图片描述
   接下来我们就来看如何使用联合体实现这个功能
   其实很简单,上面案例的本质就是利用指针来访问整型a的第一个字节,而我们的联合体本身就可以做到这一点
   只要在联合体中创建一个整型成员a,创建一个字符型成员c,由于共用空间,那么c就可以直接访问a的第一个字节
   接着我们就照着上面那个案例的思路,用联合体实现一下,如下:

#include <stdio.h>

union Un
{
	int a;
	char c;
};

int main()
{
	union Un un;
	un.a = 1;
	if (un.c == 1)
		printf("小端字节序\n");
	else
		printf("大端字节序\n");
	return 0;
}

运行结果:
在这里插入图片描述

5.结构体和联合体内存占用的对比

   我们来简单对比一下同样的成员下,结构体和联合体内存占用的情况,如下例:

struct S
{
 char c;
 int i;
};

union Un
{
 char c;
 int i;
};

   我们来画图看看它们在内存中的占用情况:
在这里插入图片描述
   可以看到,和结构体对比,联合体非常节省空间,那么联合体改一个成员另一个成员跟着变了,到底该用在什么时候呢?我们继续学习

6.联合体的应用

   联合体在使用时可以节省空间,所以我们要学习什么情况下使用联合体,而不是使用结构体
   由于它的特性,所以我们应该也能想到它的应用,那就是应用在整个联合体一次性只会出现一个成员的情况下,只要我们在同一时刻只使用一个成员,那么就算把其它成员改变了也没有影响
   ⽐如,我们要搞⼀个活动,要上线⼀个礼品兑换单,礼品兑换单中有三种商品:图书、杯⼦、衬衫。每⼀种商品都有:库存量、价格、商品类型和商品类型相关的其他信息,如下:

  • 图书:书名、作者、⻚数
  • 杯⼦:设计
  • 衬衫:设计、可选颜⾊、可选尺寸

   在不思考的情况下,我们可以直接写出以下结构:

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,所以我们就可以把公共属性单独写出来,剩余属于各种商品本⾝的属性使⽤联合体起来,这样就可以介绍所需的内存空间,⼀定程度上节省了内存,如下:


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.枚举类型的声明

   枚举顾名思义就是⼀⼀列举,可以把所有可能的取值⼀⼀列举出来,⽐如我们现实⽣活中:

  • ⼀周的星期⼀到星期⽇是有限的7天,可以⼀⼀列举
  • 性别有:男、⼥,也可以⼀⼀列举
  • 三原⾊,也是可以一一列举

   所以枚举也就是一一列举的意思,而枚举类型的声明和结构体以及联合体的声明相似,但是关键字是enum,接下来我们就来把我们举出的枚举例子一一实现出来,如下:

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

enum gender//性别
{
    MALE,
    FEMALE,
}enum Color//三原色
{
   RED,
   GREEN,
   BLUE
};

   以上定义的 enum Day , enum Sex , enum Color 都是枚举类型,{}中的内容是枚举类型的可能取值,也叫 枚举常量
   这些枚举常量都是有值的,默认从0开始,依次递增1,我们可以打印出来看看:

#include <stdio.h>

enum color
{
	RED,
	GREEN,
	BLUE
};

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

运行结果:
在这里插入图片描述
那么我们能不能在开始的时候就给它赋值呢?当然可以,如下:

enum Color
{
	RED = 2,
	GREEN = 4,
	BLUE = 8
};

接着我们再来打印一下它们的值
在这里插入图片描述
   这就是自定义结构:枚举,里面的成员又叫枚举常量,是无法更改的,一般用来将这些值赋值给其它变量

2.枚举类型的优点

   为什么使⽤枚举?我们可以使⽤ #define 定义常量,为什么非要使用枚举来定义枚举常量?我们可以来看看枚举的优点:

  1. 增加代码的可读性和可维护性,比如我们想用数字0表示男,数字1表示女,那么写出来就不好理解,如果用枚举中MALE表示男,同时底层代表0,就具有更高的可读性和可维护性
  2. 和#define定义的标识符比较枚举有类型检查,更加严谨
  3. 便于调试,预处理阶段会删除 #define 定义的符号,这个在后面的预处理详解我们会讲到
  4. 使⽤⽅便,⼀次可以定义多个常量
  5. 枚举常量是遵循作⽤域规则的,枚举声明在函数内,只能在函数内使用,而#define定义的常量是全局变量

所以枚举也是有它自己的优点的

3.枚举类型的使用

   在使用枚举时,我们会创建一个枚举变量,然后用枚举类型中的枚举常量给它赋值,如下:

#include <stdio.h>

enum Color
{
	RED = 1,
	GREEN = 2,
	BLUE = 4
};

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

   我们今天要学习的联合体(共同体)以及枚举就到此结束了,是否是有很大收获呢?如果文章内容有误请及时联系我,非常感谢
   当然如果有什么关于这篇博客的疑问,可以在评论区或者私信提问,一定会及时回答
   那么今天就到这里,bye~

上一篇:网络攻击原理与攻击方法


下一篇:Android 如何实现远程网页控制售卖机出商品:RabbitMQ的对接,如何使用?如何断网重连?连接不上后台的MQ有哪些方面的原因