C语言联合体

文章目录

一、联合体类型的声明

🏈🏈🏈联合体也是一种自定义类型。像结构体一样,联合体也是由一个或者多个成员构成,这些成员可以是不同的类型。
但是编译器只为最大的成员分配足够的内存空间。联合体的特点是所有成员共用同一块内存空间。所以联合体也叫:共用体。

联合体类型的定义形式:
● union是联合体关键字
● tag是标签名,是自定义的
● union tag就是联合体类型
● { }放的是成员列表
● member1,member2是联合体成员
● variable-list是变量名

来看下面一段代码:

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

程序运行结果:
通过计算得到联合体变量un的大小为4(单位字节)。为什么是这个值呢?

二、联合体的特点

刚刚我们说过联合体的所有成员是共用一块内存空间的,这样一个联合体变量的大小,至少是最大成员的大小(因为联合体至少得有能力保存最大的那个成员)。
我们再来看两段代码:

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

程序运行结果:

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

程序运行结果:
通过代码1的运行结果可知,输出的三个地址一模一样,而通过代码2的运行结果,我们发现变量i的值变成了0x11223355。我们仔细分析就可以画出,un的内存布局图。
由上图,这里是小端字节序存储,为联合变量un的成员i开辟了4个字节的空间来存储数据0x11223344。而当我们紧接着为该联合变量un的成员c赋值0x55时,发现原来成员i存储的四个字节数据的低位字节数据的44被修改为了55(这里只修改了i成员的一个字节数据,因为成员c是char类型,只占一个字节,所以i只被覆盖了一个字节)。看到这里应该就明白了,那就是🥩联合体的成员是共用一块内存空间的🥩。对其中一个成员进行赋值修改时,再修改其他成员的值,那原来成员存储的值就会被刚刚修改的成员值所覆盖掉。所以对于联合体在同一时间最好是只使用一个成员。

上面我们计算过这个联合体类型union Un的大小为4,因为这个联合体类型有两个成员:一个是char类型的,一个是int类型的。则要先保证能存储最大的那个成员,其次小的成员是和这个最大的成员共用同一块内存空间的,所以这里计算出该联合体类型的大小就为4。

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

c 复制代码
struct S
 {
 char c;
 int i;
 };
 struct S s = {0};

计算该结构体的大小为:

c 复制代码
union Un
{
	char c;
	int i;
};
union Un un = { 0 };

计算联合体的大小为:
我们画出上面的结构体和联合体的内存布局示意图进行对比:
对于结构体来说,成员c和i是有自己独立的空间的,而对于联合体来说成员c和i是共用同一块内存空间的,这是它们之间的区别。所以对于结构体和联合体来说,它们是有各自的应用场景的。

三、联合体大小的计算

联合体的大小可不是就像上面说的一样,就是最大成员的大小,不是这么简单的。那只是其中一条规则:

🥑● 联合的大小至少得是最大成员的大小。

🥑● 联合体的大小必须是最大对齐数的整数倍。当最大成员的大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

在计算结构体大小时,是有最大对齐数的,那在计算联合体大小时也是有最大对齐数的。找对齐数的规则,跟结构体中是一样的。上面的第二条规则是啥意思?我们通过下面的例子来讲解:

c 复制代码
#include<stdio.h>
union Un1
{
	int i;
	char c;
	short arr1[7];
};
union Un2
{
	char c;
	int i;
	int arr2[3];
};
union Un3
{
	char arr3[5];
	int i;
	double d;
};
int main()
{
	union Un1 un1 = { 0 };
	union Un2 un2 = { 0 };
	union Un3 un3 = { 0 };

	printf("%zd\n", sizeof(un1));
	printf("%zd\n", sizeof(un2));
	printf("%zd\n", sizeof(un3));

	return 0;
}

程序运行结果:
先看第一个联合体变量un1,他的成员中最大成员的大小是4,可能会有同学在有数组成员的地方犯迷糊,搞不清楚数组成员到底要怎么计算最大对齐数?其实很简单,首先我们知道数组在内存中是连续存放的,数组的大小就等于 🏉元素个数*元素类型的大小🏉。就像这里的联合变量un1中数组成员arr1的大小就是7*sizeof(short)=14(单位字节)。找数组成员的对齐数,其实就看数组单个元素类型的大小与编译器默认对齐数的较小值即可。所以对于联合变量un1的最大对齐数就为4:
那这里最大成员的大小就是14,而14并不是最大对齐数的整数倍,所以还要再浪费2个字节,到16就是4的倍数了,所以联合变量un1的大小就为16。

再看第二个联合变量un2,该变量三个成员中的最大对齐数是4,第三个成员数组arr2的大小为3*sizeof(int)=12,而12正好是最大对齐数4的倍数,所以该联合变量un2的大小就为12。

最后第三个联合变量un3,该变量的三个成员中最大对齐数是8,第一成员数组arr3的大小为5*sizeof(char)=5,这里最大的成员就是数组arr3,但是5不是最大对齐数8的整数倍,还要再浪费3个字节,到8就是8的倍数了。

联合体的应用案例

🍟使用联合体是可以节省空间的,下面的一个案例可以说明:
比如,我们要搞一个活动,要上线一个礼品兑换单,礼品兑换单中有三种商品:图书、杯子、衬衫。每一种商品都有:库存量、价格、商品类型和商品类型相关的其他信息。

如果我们采用结构体的形式来包装上面的属性:

c 复制代码
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这几个特殊属性了。

所以我们就可以把公共属性单独写出来,剩余属于各种商品本身的属性用联合体包装起来,这样就可以减少所需的内存空间,一定程度上节省了内存。

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

上面的特殊属性就放在了联合体中,因为每创建一个商品变量时,只会用到联合体中的其中一个结构体成员,而联合体中的成员是共用同一块内存空间的,所以这里大大的节省了内存空间。这就是联合体的好处。(上面的结构体中的联合体成员及其内部的结构体成员都是采用匿名的,意思是这个联合变量只会被使用一次)

用联合体判断当前机器是小端还是大端

🍚🍚联合体还可以用来判断当前自己使用的机器平台是大端字节序存储还是小端字节序存储,小端和大端字节序存储是什么意思?比如我们创建了一个整型变量 i 存储了0x00000001这样一个值,那请问这个值在内存中的排放顺序是什么?是低位的值开从低地址处开始存放呢,还是低位的值从高地址处开始存放呢?
通过使用联合体就可以知道当前机器是小端还是大端:

c 复制代码
#include<stdio.h>
int check_sys()
{
	union
	{
		int i;
		char c;
	}un;
	un.i = 1;
	return un.c;//返回1是⼩端,返回0是⼤端
}
int main()
{
	printf("%d\n", check_sys());
	return 0;
}

程序运行结果:

我们可以逐语句(F11)调试起来,在内存中查看联合变量un存储的值,就是因为在联合体中变量i和变量c是共用同一块内存空间的,所以在对整型变量i赋值1后,我们就可以通过字符类型(1个字节)的变量c查看在低位字节的值是不是1来判断当前机器是小端还是大端。通过上图可以看出,我使用的机器平台采用的是小端字节序存储(低位从低地址处开始存放)。

相关推荐
Monodye14 分钟前
【Java】网络编程:TCP_IP协议详解(IP协议数据报文及如何解决IPv4不够的状况)
java·网络·数据结构·算法·系统架构
一丝晨光20 分钟前
逻辑运算符
java·c++·python·kotlin·c#·c·逻辑运算符
元气代码鼠21 分钟前
C语言程序设计(进阶)
c语言·开发语言·算法
无名指的等待71244 分钟前
SpringBoot中使用ElasticSearch
java·spring boot·后端
做完作业了1 小时前
【C语言】预处理详解
c语言·预处理
Tatakai251 小时前
Mybatis Plus分页查询返回total为0问题
java·spring·bug·mybatis
武子康1 小时前
大数据-133 - ClickHouse 基础概述 全面了解
java·大数据·分布式·clickhouse·flink·spark
.生产的驴1 小时前
SpringBoot 消息队列RabbitMQ 消费者确认机制 失败重试机制
java·spring boot·分布式·后端·rabbitmq·java-rabbitmq
Code哈哈笑2 小时前
【C++ 学习】多态的基础和原理(10)
java·c++·学习
chushiyunen2 小时前
redisController工具类
java