【C语言】一篇文章深入解析联合体和枚举且和结构体的区别
📝前言
联合体(union
)是允许一个变量通过不同的接口访问内存的一种数据类型,表示一个变量可以存储不同类型的值,而枚举是使用enum
关键字定义一组相关且互斥的整形常量集合。本章阿森将和你学习联合体类型的声明,特点,有关大小的计算,还有枚举类型的声明,优点和使用。文章干货满满!学习起来吧😃!
🌠 联合体类型的声明
同结构体一样,声明结构体类型需要使用struct
关键字,联合体则用union
关键字。
- 包含对象名的声明方式:
union 联合体名
{
类型 成员1;
类型 成员2;
...
类型 成员n;
}对象名;
- 代码理解:
#include <stdio.h>
union S
{
char c;
int a;
}s1;
int main()
{
s1.c = 'a';
printf("%c\n", s1.c);
s1.a = 10;
printf("%d\n", s1.a);
return 0;
}
代码运行:
- 不包含对象名的声明格式:
union 类型名
{
类型 成员1;
类型 成员2;
...
类型 成员n;
};
- 代码实现:
#include <stdio.h>
union S
{
char c;
int a;
};
int main()
{
union S s2;
s2.c = 'b';
printf("%c\n", s2.c);
s2.a = 20;
printf("%d\n", s2.a);
return 0;
}
运行:
🌉联合体的特点
- 编译器只为最?的成员分配?够的内存空间。联合体的特点是所有成员共?同?块内存空间。所以联合体也叫:共?体。
例如:
union u
{
char c;
int u;
};
int main()
{
union u uu;
printf("联合体uu的大小为%zd\n", sizeof(uu));
printf(" (uu)地址为%p\n", &uu);
printf("&(uu.c)地址为%p\n", &(uu.c));
printf("&(uu.u)地址为%p\n", &(uu.u));
return 0;
}
输出:
图解:
- 联合的成员是共?同?块内存空间的,这样?个联合变量的??,?少是最?成员的??(因为联合?少得有能?保存最?的那个成员)。
//联合类型的声明
union u
{
char c;
int i;
};
int main()
{
//联合变量的定义
union u uu = { 0 };
un.i = 0x11223344;
un.c = 0x55;
printf("%x\n", uu.i);
return 0;
}
输出:
图解:
union
定义了int
和char
两个成员,共享同一块内存空间,int
类型占4
个字节,低地址在前,高地址在后,char
类型只占1
个字节,存储在int
的低地址字节。当执行:uu.i = 0x11223344时,此时int的4个字节分别存储如图,然后执行: uu.c = 0x55,由于VS是小端存储,低字节放在低地址处,char
只占1
个字节,它会覆盖int
低地址的那个字节。所以int
原来低地址字节0x44
被覆盖为0x55
。
🌠相同成员的结构体和联合体对?
结构体和联合体的主要区别在于:
- 结构体中每个成员占用自己独立的内存空间,可以同时访问每个成员。
- 联合体中所有成员共享同一块内存空间,只能同时访问其中一个成员。
-
内存布局:
结构体中每个成员都有固定的偏移地址,占用独立的内存空间。
联合体中所有成员共享同一块内存,没有偏移地址,只能使用一个成员。 -
访问成员:
结构体可以同时读取各个成员的值。
联合体只能访问当前使用的成员,其他成员的值将被覆盖。 -
大小:
结构体的大小是所有成员大小的和。
联合体的大小至少是最大成员的大小。
- 结构体:
struct S
{
char c;
int i;
};
struct S s = {0};
- 联合体:
union u
{
char c;
int i;
};
union u uu = { 0 };
图解对比:
结构体S
占用char + int+有可能开辟浪费的空间
大小的内存,可以独立访问c
和i
,联合体u
只占用int
大小的内存,访问c
或i
时值会覆盖,结构体各成员独立,联合体成员共享同一内存空间。
🌉联合体??的计算
点击可以查看结构体的内存对齐规则——>【C语言】自定义类型:结构体深入解析(二)结构体内存对齐&&宏offsetof计算偏移量&&结构体传参
联合体大小计算规则:
- 联合的???少是最?成员的??。
- 当最?成员??不是最?对?数的整数倍的时候,就要对?到最?对?数的整数倍。
- 来代码理解:
union Un1
{
char c[5];
int i;
};
union Un2
{
short c[7];
int i;
};
int main()
{
printf("%zd\n", sizeof(union Un1));//8
printf("%zd\n", sizeof(union Un2));//16
return 0;
}
运行:
图解分析:
首先看union Un1
如果联合体的大小是最大成员的最大成员的的大小,在联合体union Un1
中,char[5]
的大小理应是5
,那计算的结果不是5
。为什么是8
呢?这是因为它完成了对齐的操作,如果是数组,是按元素类型大小来算他的对齐数的。char
元素的类型大小是1
,VS
默认对齐数是8
,对齐数是8
,i
的大小是4
,VS
默认对齐数是8
,对齐数是4
,接下来(4>1
)整个联合体的对齐数是4
,根据当最?成员??不是最?对?数的整数倍的时候,就要对?到最?对?数的整数倍。此时最大成员大小是数组char [5]
大小为5
,5
不是4
的整数倍,8
(4*2
)是4
的整数倍。是不是真的是这样呢?会不会是偶然呢?
接下来我们看第二组:
union Un2
首先short c[7]
是数组,总大小为14
,然后由于数组是按照元素的类型大小来算对齐数,类型为short
类型大小为2
,VS
默认对齐数为8
,对齐数为2
(2<8
),i
的大小是4
,VS
默认对齐数是8
,那么对齐数是4
(4<8
),然后整个联合体的对齐数是4
(4>2
),然后看成员最大对齐数的大小(short c[7]
的大小是2*7=14
)是不是整个联合体的对齐数(4
)的整数倍,可见14
不是4
的整数倍, 根据第二条规则:当最?成员??不是最?对?数的整数倍的时候,就要对?到最?对?数的整数倍。因此还要多用2
个字节,升到16
(4*4
)个字节才是整数倍。
联合体的对齐规则与结构体相似:
点击可以查看结构体的内存对齐规则——>【C语言】自定义类型:结构体深入解析(二)结构体内存对齐&&宏offsetof计算偏移量&&结构体传参
🌠联合体应用
使?联合体是可以节省空间的:
?如,我们要搞?个活动,要上线?个礼品兑换单,礼品兑换单中有三种商品:图书、杯?、衬衫。
每?种商品都有:库存量、价格、商品类型和商品类型相关的其他信息。
图书:书名、作者、?数
杯?:设计
衬衫:设计、可选颜?、可选尺?
结构体表示:
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;
};
练习:写?个程序,判断当前机器是?端?还是?端?
- 第一种方法:
int check_sys()
{
int n = 1;//01 00 00 00 00 00 00 01
return *(char*)&n;
}
int main()
{
int ret = check_sys();
if (ret == 1)
printf("小端\n");
else
printf("大端\n");
return 0;
}
VS运行:
- 第二种联合体巧妙方法:
int check_sys()
{
union
{
char c;
int i;
}u;
u.i = 1;
return u.c;//返回1是?端,返回0是?端
}
VS运行:
小端
图解:
大端存储:是指数据的低位字节内容保存在内存的高地址处,而数据的高位位字节内容,保存在内存的低地址处。
小端存储:是指数据的低位字节内容保存在内存的低地址处,而数据的高位字节内容,保存在内存的高地址处。
如果01
是低位字节存储到低地址c
时,是小端存储,如果01
低位字节存储到高地址处,没有存储到c
的位置,那么c
的位置存储着00
,返回为0
,是大端存储。
🌉枚举类型的声明
枚举类型(enum
)是一种特殊的类型,它可以为一组相关的常量值赋予用户定义的名称。
—>简单来说:枚举顾名思义就是??列举。
枚举类型的声明语法:
enum 标识符
{
枚举常量1,
枚举常量2,
...
} 变量;
-
enum
关键字声明这是一个枚举类型。 -
标识符是枚举类型的名称。
-
在大括号{}内列出枚举类型的多个枚举常量,用逗号分隔。
-
变量是枚举类型的变量,可以直接使用枚举类型名或枚举常量初始化。
例如:
enum Color //Color是枚举类型名
{
RED, // 枚举常量
GREEN,
BLUE
} color;///color是Color类型的变量
int main()
{
printf("%d\n", RED);
printf("%d\n", GREEN);
printf("%d\n", BLUE);
return 0;
}
输出:
- 枚举常量默认从
0
开始依次累加1
。也可以手动为枚举常量赋值:
例如:
enum Color
{
RED = 1,
GREEN = 2,
BLUE = 4
}
运行结果:
- 当然第一个元素未被赋值,给其它的常量赋值,该常量前面的值是默认值(0,1,2)后面递增1。
例如:
enum Color
{
RED,
white,
GREEN = 8,
BLUE ,
BLACK,
};
int main()
{
printf("%d\n", RED);
printf("%d\n", white);
printf("%d\n", GREEN);
printf("%d\n", BLUE);
printf("%d\n", BLACK);
return 0;
}
输出:
🌠枚举类型的优点
为什么使?枚举?
我们可以使? #define 定义常量,为什么?要使?枚举?
枚举的优点:
- 增加代码的可读性和可维护性
如:之前的扫雷中可以这样定义用PLAY
代替1
,EXIT
代替0
,更具有个性化:
#include <stdio.h>
#include <string.h>
// 定义游戏选择枚举类型
enum Game_Selection
{
EXIT, // 退出游戏
PLAY // 开始游戏
};
// 打印菜单
void menu()
{
printf("********** Menu **********\n");
printf("PLAY - Start the game\n");
printf("EXIT - Exit the game\n");
printf("********** Menu **********\n");
}
int main()
{
enum Game_Selection input; // 声明游戏选择变量
char choice[10]; // 声明选择输入缓冲区
do
{
menu(); // 调用菜单函数
printf("Please enter your choice: ");
scanf("%s", choice); // 读取选择输入
getchar(); // 清除输入缓冲区
if (strcmp(choice, "PLAY") == 0)
{
input = PLAY; // 设置选择为开始游戏
}
else if (strcmp(choice, "EXIT") == 0)
{
input = EXIT; // 设置选择为退出游戏
}
else
{
printf("输入错误,请重新输入\n");
continue; // 输入错误,继续循环
}
switch (input)
{
case PLAY:
printf("扫雷游戏启动!\n");
break;
case EXIT:
printf("不玩了,启动不了!\n");
break;
}
} while (input != EXIT);
return 0;
}
代码运行:
-
和
#define
定义的标识符?较枚举有类型检查,更加严谨。 -
便于调试,预处理阶段会删除
#define
定义的符号 -
使??便,?次可以定义多个常量
-
枚举常量是遵循作?域规则的,枚举声明在函数内,只能在函数内使?
🌉 枚举类型的使?
那是否可以拿整数给枚举变量赋值呢?在C语?中是可以的,但是在C++是不?的,C++的类型检查?较严格。
-
在C语言中,枚举类型实际上就是整数类型,编译器会把枚举常量替换成对应的整数值。所以可以用整数直接给枚举变量赋值。
-
而在C++中,枚举类型是完全独立的类型。编译器会检查类型是否匹配,不允许用整数直接给枚举变量赋值。
例如:
# define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
//C语言
enum Color//颜?
{
RED = 1,
GREEN = 2,
BLUE = 4
};
int main()
{
enum Color c;
c = 1; // 可以直接赋值整数
return 0;
}
输出:
// C++语言
enum Color//颜?
{
RED = 1,
GREEN = 2,
BLUE = 4
};
Color c;
c = 1; // 错误,类型不匹配
输出:
总结:
C语言中枚举类型实际上就是整数,允许用整数直接赋值
C++中枚举类型是独立类型,不允许用整数直接赋值,需要强制类型转换
🚩总结
这次阿森和你一起学习联合体类型的声明,特点,然后进行相同成员的结构体和联合体对?,??的计算,联合体应用,枚举类型的声明,优点和扫雷改造使?方法,阿森将下一节和你一起学习动态内存管理🚀 。
感谢你的收看,如果文章有错误,可以指出,我不胜感激,让我们一起学习交流,如果文章可以给你一个小小帮助,可以给博主点一个小小的赞😘
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!