目录
[1 什么是共用体](#1 什么是共用体)
[2 共用体与结构体的区别](#2 共用体与结构体的区别)
[3 声明共用体类型](#3 声明共用体类型)
[4 声明共用体变量](#4 声明共用体变量)
[5 共用体内存分析](#5 共用体内存分析)
[6 共用体成员的获取和赋值](#6 共用体成员的获取和赋值)
[7 综合案例](#7 综合案例)
[7.1 共同体特点演示](#7.1 共同体特点演示)
[7.2 使用共用体存储学生和教师信息](#7.2 使用共用体存储学生和教师信息)
1 什么是共用体
共用体(Union)是一种特殊的数据结构,允许不同数据类型的成员共享同一块内存区域。这意味着,在任意时刻,共用体中只有一个成员是有效的,因为新赋值的成员会覆盖前一个成员的值。
共用体主要用于需要节省内存或处理多种数据类型但每次只使用其中一种的情况。例如,表示学生的成绩时,成绩可能以不同的形式给出------整数(80、90)、字符等级('A'、'B')或是浮点数分数(80.5、60.5)。
2 共用体与结构体的区别
特性/类型 | 结构体 (Struct) | 共用体 (Union) |
---|---|---|
内存分配 | 每个成员分配独立的内存空间 | 所有成员共享同一块内存空间 |
内存大小 | 总内存大小可以认为是所有成员内存大小之和 | 总内存大小等于最大成员的内存大小 |
数据访问 | 可以同时访问所有成员 | 同一时间只能有效访问一个成员 |
成员存储 | 每个成员有自己的内存地址 | 所有成员的起始地址相同 |
初始化 | 可以初始化多个成员 | 只能初始化第一个成员 |
适用场景 | 需要存储和同时访问多个不同数据类型的数据 | 需要节省内存且每次只使用其中一个成员的情况 |
类型安全 | 更类型安全,每个成员有明确的类型 | 类型安全性较低,容易发生类型混淆 |
结构体:每个成员都有独立的内存空间,结构体所占用的总内存可以认为是所有成员占用内存之和(实际内存占用会受到对齐(alignment)的影响)。可以同时访问所有的成员,每个成员都有自己独特的内存地址。
共用体:所有成员共享同一块内存,共用体所占用的内存大小取决于其最大的成员。一次只能有效访问一个成员,访问不同的成员时,实际上是在查看同一内存区域的不同解释。
3 声明共用体类型
cpp
union 共用体类型名称 {
数据类型 成员名1;
数据类型 成员名2;
...
数据类型 成员名n;
};
下面的 union data 定义了一个名为 data 的共用体类型,该类型包含三个成员:一个整数 m、一个浮点数 x 和一个字符 c。
cpp
union data {
int m;
float x;
char c;
};
该共用体所有成员共享同一块内存,共用体的总内存大小等于其最大成员的大小。在大多数系统中,float 类型通常占用 4 个字节,因此 union data 也将占用 4 个字节。需要注意的是:同一时间只能有效访问一个成员,最后一个被赋值的成员是当前有效的成员。
4 声明共用体变量
方式 1:先定义共用体类型,再定义共用体变量
cpp
// 声明共用体类型
union data {
short m;
float x;
char c;
};
// 声明共用体变量
union data a, b;
首先定义一个共用体类型 union data,其中包含三个成员:short m、float x 和 char c。 然后在单独的语句中声明共用体变量 a 和 b,这两个变量都是 union data 类型。
方式 2:定义共用体类型的同时定义共用体变量
cpp
// 定义共用体类型并声明共用体变量
union data {
short m;
float x;
char c;
} a, b;
在同一行中定义共用体类型 union data 并同时声明共用体变量 a 和 b。这种方式简洁,减少了代码量。
方式3:在定义时也可以不给出共用体名
cpp
// 定义共用体类型并声明共用体变量,不给出共用体名
union {
short m;
float x;
char c;
} a, b;
定义共用体时不给出共用体名,直接在定义时声明共用体变量 a 和 b。这种方式适用于不需要多次使用该共用体类型的情况。
5 共用体内存分析
cpp
// 定义共用体类型并声明共用体变量
union data {
short m;
float x;
char c;
} a;
上面这个共用体它由3个成员组成,分别是 m、x 和 c,系统会按照最长的成员为它分配内存,由于成员 x 的长度最长,它占 4 个字节,所以共用体变量 a 的内存空间也为 4 个字节。
6 共用体成员的获取和赋值
同结构体一样,共用体也使用点号. 获取单个成员,可以进行赋值和取值。
方式 1:先声明共用体变量,再赋值
cpp
union data a;
a.c = 4;
首先声明一个共用体变量 a。然后使用点号 . 给成员 c 赋值为 4。
方式 2:声明共用体变量的同时,给任一成员赋值
cpp
union data a = {.c = 4};
在声明共用体变量 a 的同时,使用指定成员的方式给成员 c 赋值为 4。这种方式明确指定了赋值的成员,提高了代码的可读性。
方式 3:声明共用体变量的同时,给首成员赋值
cpp
union data a = {8};
在声明共用体变量 a 的同时,给第一个成员赋值为 8。这种方式不指定成员名,因此只能为第一个成员赋值。如果共用体的第一个成员是 short m,则 m 将被赋值为 8。
7 综合案例
7.1 共同体特点演示
cpp
#include <stdio.h>
// 方式1
union data
{
short m;
float x;
char c;
};
union data a1, b1;
// 方式2
union data2
{
short m;
float x;
char c;
} a2, b2;
// 方式3
union
{
short m;
float x;
char c;
} a3, b3;
int main()
{
// 赋值并访问
// 方式1
a1.m = 100;
b1.x = 3.14;
// 打印方式1
printf("a1.m: %hd\n", a1.m); // 100
printf("b1.x: %.2f\n", b1.x); // 3.14
// 方式2
a2.c = 'A';
b2.m = 200;
// 打印方式2
printf("a2.c: %c\n", a2.c); // A
printf("b2.m: %hd\n", b2.m); // 200
// 方式3
a3.x = 2.71;
b3.c = 'B';
// 打印方式3
printf("a3.x: %.2f\n", a3.x); // 2.71
printf("b3.c: %c\n", b3.c); // B
// 注意:访问其他成员时,值可能已经被覆盖
// 在任意时刻,共用体中只有一个成员是有效的
// 访问不同的成员时,实际上是在查看同一内存区域的不同解释
a1.c = 'a';
printf("a1.c: %c\n", a1.c); // a
printf("a1.m: %hd\n", a1.m); // 97,这是 a 转换成的数值 97
printf("a1.x: %f\n", a1.x); // 0.000000,字符转换成浮点数
// 共用体所占用的内存大小取决于其最大的成员
printf("共用体a1的长度为:%zu\n", sizeof(a1)); // 4,成员最大的是 float 类型,所以为 4
printf("float类型的长度:%zu\n", sizeof(float)); // 4
printf("short类型的长度:%zu\n", sizeof(short)); // 2
printf("char类型的长度:%zu\n", sizeof(char)); // 1
return 0;
}
输出结果如下所示:
7.2 使用共用体存储学生和教师信息
现有一张关于学生信息和教师信息的表格。学生信息包括姓名、编号、性别、职业、 分数,教师的信息包括姓名、编号、性别、职业、教学科目:可以参考下面的表格。
请利用共用体,只使用一个结构体保存每个人的信息。
Name | Num | Sex | Profession | Score / Course |
---|---|---|---|---|
孙二娘 | 501 | 女(f) | 学生(s) | 89.5 |
吴用 | 1011 | 男(m) | 老师(t) | math |
顾大嫂 | 109 | 女(f) | 老师(t) | English |
林冲 | 982 | 男(m) | 学生(s) | 95.0 |
cpp
#include <stdio.h>
#define TOTAL 2 // 定义人员总数
// 定义了一个结构体 Person
struct Person
{
char name[20]; // 姓名
int num; // 编号
char sex; // 性别 (f: 女, m: 男)
char profession; // 职业 (s: 学生, t: 老师)
union
{ // 共用体,用于存储学生的分数或教师的教学科目
float score; // 学生的分数
char course[20]; // 教师的教学科目
} sc; // sc 是一个共用体变量
};
// 以表格形式打印输出所有人的信息
void printTableInfo(struct Person *p);
int main()
{
struct Person persons[TOTAL]; // 定义了一个结构体数组,用于存储多个人的信息
// 输入人员信息
for (int i = 0; i < TOTAL; i++)
{
printf("请输入第%d人的信息,格式:姓名 编号 性别 (f: 女, m: 男) 职业 (s: 学生, t: 老师)\n", i + 1);
scanf("%s %d %c %c", persons[i].name, &(persons[i].num), &(persons[i].sex), &(persons[i].profession));
if (persons[i].profession == 's')
{ // 如果是学生
printf("请输入该学生成绩:");
scanf("%f", &persons[i].sc.score);
}
else
{ // 如果是老师
printf("请输入该老师课程:");
scanf("%s", persons[i].sc.course);
}
fflush(stdin); // 刷新输入缓冲区
}
// 调用输出函数
printTableInfo(persons);
return 0;
}
void printTableInfo(struct Person *p)
{
// 输出人员信息
printf("\n姓名\t\t编号\t性别\t职业\t成绩 / 科目\n");
for (int i = 0; i < TOTAL; i++)
{
if (p[i].profession == 's')
{ // 如果是学生
printf("%s\t\t%d\t%c\t%c\t\t%.1f\n",
p[i].name, p[i].num, p[i].sex, p[i].profession, p[i].sc.score);
}
else if (p[i].profession == 't')
{ // 如果是老师
printf("%s\t\t%d\t%c\t%c\t\t%s\n",
p[i].name, p[i].num, p[i].sex, p[i].profession, p[i].sc.course);
}
}
}
输出结果如下所示: