【C语言】自定义类型:结构体,联合,枚举(下)

前言;上一期我们侧重讲了一个非常重要的自定义类型结构体,这一期我们来说说另外两种自定义类型:联合,和枚举。

传送门:自定义类型:结构体,联合,枚举(上)

文章目录

一,联合体

1,什么是联合体

在前面我们已经学过一种自定义类型结构体,那么另外两种自定义类型也必然与结构体有相似之处所以我们类比来学习。

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

那到底联合体与结构体有什么本质上的区别呢?我们通过一个例子来观察:

c 复制代码
#include <stdio.h> 
//联合类型的声明 
union Un 
{ 
	char c; 
	int i; 
};
int main() 
{ 
	//联合变量的定义 
	union Un un = {0}; 
	//计算连个变量的⼤⼩ 
	printf("%d\n", sizeof(un)); 
	return 0; 
}

由于联合体的定义,创建,初始化均与前面的结构体相同在这里就不再赘述!

各位读者觉得运行结果会是什么呢?

我们这里直接给出运行结果:

结果为什么是4呢?

在联合体内部定义了一个char和一个int类型的数据内存再怎么说也因该是5吧,可这里打印出来的大小为什么是4呢?

带着这个疑问我们将联合体nu的地址打印出来看看能不能回答我们的疑问:

c 复制代码
#include <stdio.h> 
//联合类型的声明 
union Un 
{ 
	char c; 
	int i; 
};
int main() 
{ 
	//联合变量的定义 
	union Un un = {0}; 
	//计算连个变量的⼤⼩ 
	printf("%d\n", sizeof(un)); 
	printf("%p\n",&un.c);
	printf("%p\n",&un.i);
	printf("%p\n",&un);
	return 0; 
}

我们将结果打印出来:

不看不知道一看吓一跳,无论是&un.c成员的地址还是&un.i的地址以及&un的地址都是一样的,这已经很明显的说明了一个问题就是,联合体内部的成员共用一块内存空间!

可能会有人疑惑说为什么他们3个的地址一样就能说明他们是共用同一块内存空间呢?

首先计算机将一个大内存分成若干个内存单元,每一个内存单元就是一个字节。而在指针(1)我们谈到计算机是需要进行编址的,如何进行编址呢? 是以每一个字节,(注意是字节)来进行编址。所以每一个字节的空间都有它自己的地址编号。
回头看上面的代码,无论是&un.c成员的地址还是&un.i的地址以及&un的地址都相同,这就说明了他们位于同一个字节的空间里面。如下图

这就可以解决我们上面的疑惑了,联合体不像结构体一样每个成员都有自己独立的空间,联合体每个成员是共用一块内存空间,所以联合体也叫共用体。

2,联合体的特点

联合体有什么特点呢?我们也通过一段代码来直观的感受:

c 复制代码
#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.c赋值时,会修改un.i低地址处的空间,我们画出示意图:

当我们修改联合体内部的某一个成员时,另一个成员的值也会发生改变这就是联合体的特点。

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

先来分析两段代码:

c 复制代码
#include<stdio.h>
struct S 
{ 
	char c;
	int i; 
};
int main()
{
	struct S s = {0};
	return 0;
}

以上是结构体我们也仿照写一个联合体:

c 复制代码
#include<stdio.h>
union Un
{ 
	char c;
	int i; 
};
int main()
{
	union  Un un = {0};
	return 0;
}

我们来分析一下他们内存的占用情况:

通过上面对比我们也发现联合体相较于结构体能节省一定的空间。这时又会有人疑惑了说那联合体的大小怎么去计算呢?下面我们就来看看联合体是怎么计算大小的。

4,联合体大小的计算

1.首先联合的大小至少是最大成员的大小。
2. 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

我们来看代码:

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

我们给出运行结果:

那为什么是8和16呢?

这就要说到内存对齐了,其实联合体也是有内存对齐的,在Un1这个结构体中我们首先定义了一个char类型的数组大小为5,再加上一个int类型的i所以该联合体的大小至少也是5个字节,但是联合体的总大小必须是最大对齐数的整数倍,也就是4的整数倍所以将浪费3个字节变成8,此时8就为4的整数倍了。

有关于内存对齐的知识在上一篇已经详细解读过了,这里也不再过多的赘述!

传送门:自定义类型:结构体,联合,枚举(上)

同样在联合体Un2中内存至少为14个字节即7个short类型的数据,但是由于14不是最大对齐数(int类型4个字节)的整数倍,所以将浪费2个空间变成16,16就为4的整数倍了。

看到这有人就会疑惑了,说联合体到底有什么优势,有什么用处呢?

我们举个例子大家就明白了:

比如我们想要搞一个活动,需要一个礼品兑换清单,礼品兑换清单中包括三种商品,图书,杯子,衬衫。且每一件商品都有相同的的描述商品的信息库存量,价格,和其他特有信息比如:

  1. 图书------书名,页数,作者
  2. 杯子------设计
  3. 衬衫------设计,颜色,尺寸

那要如何来设计呢?

c 复制代码
struct gift_list
{
	//相同的属性
	int tock_number;//商品存量
	double price;//价格

	//图书的属性
	char title[10];//书名
	int page;//页数
	char auther[10];//作者

	//杯子的属性
	char design[20];//设计
	//衬衫的属性
	char design[20];//设计
	char colors;//颜色
	int size;//尺寸
};

上面我们创建了一个结构体,结构体包含了这些信息在使用的时候也非常的方便只需要创建一个结构体变量,在用.操作符访问每个成员即可,但有一点就是我们在结构体写的其实都是大家的属性,比如我们在描述图书的时候就不需要用到尺寸,颜色这些属性,那么这些空间就浪费了。
如何节省空间呢?我们不如将他们各自特有的属性放在一个联合体里面,让他们占用同一块空间。

c 复制代码
struct gift_list 
{ 
	int stock_number;//库存量 
	double price; //定价 
	int item_type;//商品类型 
	union item//描述物品各自特有属性的联合体
	{ 
		struct book//描述图书特有属性的结构体
		{ 
			char title[20];//书名 
			char author[20];//作者 
			int num_pages;//⻚数 
		}; 
		struct mug//描述杯子的特有属性
		{ 
			char design[30];//设计 
		}; 
		struct shirt//描述衬衫的特有属性
		{ 
			char design[30];//设计 
			int colors;//颜⾊ 
			int sizes;//尺⼨ 
		}; 
	};
};

上面的代码这样写有什么好处呢?其实我们将这些商品的特有属性放在一个联合体里面之后他们就占用的同一个空间,在描述图书的时候另外两个商品的性质也会被改变,同时又节省了空间。这就是联合体的优势

5,判断大小端存储

写一个程序来判断当前编译器是大端存储还是,小端存储。

  1. 大端存储是将数据的高位字节存储在内存的低地址处。
  2. 小端存储是将数据的低位字节存储在内存的低地址处。

以1为例,首先我们来分析一下大端存储1和小端存储1有什么区别:

通过画图观察我们发现大端和小端存储1的区别就是其第一个字节存放的是0还是1,所以我们只要拿到第一个字节的数据,看看是0还是1即可判断是大端存储还是小端存储:

c 复制代码
#include<stdio.h>
union Un
{
	char a;
	int i;
};
int main()
{
	union Un un={0};
	un.i=1;//将整型i里面放入数据1
	if(un.a==1)//拿出第一个字节看看是1还是0
	{
		printf("小端");//是1说明低位放在地址 小端
	}
	else
	{
		printf("大端");//是0说明高位放到低地址 大端
	}
	return 0;
}

通过运行结果可知vs中的数据都是通过小端来存储的。

以上就是有关联合的内存,下面来介绍枚举类型

二,枚举类型

1,什么是枚举

枚举顾名思义就是一一列举,就像是我们在中学里学概论的时候碰到一些简单的题会将每一种可能情况都一一列举出来,这其实就是枚举。

那什么值可以一一列举呢?比如

一周的星期可以列举

一年的月份可以列举

这些数据就可以使用枚举来列举:

c 复制代码
enum Day//eunm为枚举类型的关键字 这里以列举星期举例 
{
	Mon, 
	Tues, 
	Wed, 
	Thur=4,//赋初值 初始化
	Fri=5, //赋初值 初始化
	Sat=6, 
	Sun   //最后一个成员是没有逗号的这里要注意
};
int main()
{
	enum Day day1=Mon;
	enum Day day2=Tues;
	enum Day day3=Wed;
	printf("%d %d %d",day1,day2,day3);
}

上面定义的Day就是枚举类型,{}中的内容是枚举类型的可能取值,也叫枚举常量 。

这些可能取值都是有值的,默认从0开始,依次递增1,当然在声明枚举类型的时候也可以赋初值。
但有一点要注意就是,整数是否可以给枚举变量赋值呢?在C语言中是可以的,但是在C++是不行的的,C++的类型检查比较严格。我们换成c++给大家看看就知道了:

我们看到报错说无法从int转成Day说明类型不匹配是不能赋值的。

这时可能也会有人问说枚举类型到底有什么优点呢?或者我们使用#define就可以了为什么要使用枚举类型呢?

  1. 增加代码的可读性和可维护性 。
  2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
  3. 便于调试,预处理阶段会删除 #define 定义的符号。
  4. 使用方便,一次可以定义多个常量 。
  5. 枚举常量是遵循作用域规则的,枚举声明在函数内,只能在函数内使用。

好了以上就是本章的全部内容啦!

最后感谢能够看到这里的读者,如果我的文章能够帮到你那我甚是荣幸,文章有任何问题都欢迎指出!制作不易还望给一个免费的三连,你们的支持就是我最大的动力!

相关推荐
曲幽2 小时前
Termux里的二进制和脚本,到底怎么运行才不踩坑?Termux-service 保活妙招!
android·termux·nohup·services·wake-lock
plainGeekDev3 小时前
单例模式 → object 声明
android·java·kotlin
程序员陆业聪3 小时前
读者点单·03|Compose 与传统 View 混用的 12 个真实坑
android
程序员陆业聪3 小时前
读者点单·02|Android 启动优化实战:Trace 抓取→Application 编排→冷启动全流程拆解
android
Coffeeee3 小时前
帮你快速理解AI Agent之我想招个Android实习生
android·人工智能·agent
用户298698530144 小时前
Java 实现 Word 文档文本与图片提取的方法
java·后端
SimonKing4 小时前
铁子,IntelliJ IDEA 2026.1.3来了,升不升?
java·后端·程序员
恋猫de小郭5 小时前
苹果 AirPods 协议,Android 也可以使用完整版 AirPods 能力
android·前端·flutter
黄林晴5 小时前
告别无效重建:Gradle 9.6.0 解决 CI 构建缓存失效痛点告别无效重建:Gradle 9.6.0 解决 CI 建筑缓存失效痛点
android·gradle