前言:通过前文,我们系统学习了结构体的相关知识,深入理解了自定义类型的概念与实践,承接上文,我们将继续学习新的自定义类型:联合和枚举。

一、联合体
像结构体⼀样,联合体也是由⼀个或者多个成员构成,这些成员可以不同的类型。但是编译器只为最⼤的成员分配⾜够的内存空间。联合体的特点是所有成员共⽤同⼀块内存空间。所以联合体也叫:共⽤体。
1.联合体类型的声明
联合体的语法结构如下:
union tag (关键字:union + 联合体名: tag)
{
member-list; //成员列表
};
温馨提示:末尾分号不要忘记。
形如:
union Un
{
char a;
int i;
};
2.联合体的特点
对于联合体而言,其最大的特点在于,共用一块内存,通过下列代码演示,我们能够充分的了解到这个特点。
union Un
{
char a; //1
int i; //4
};
int main()
{
union Un u={0};
// printf("%zd",sizeof(u));
printf("%p\n", &u);
printf("%p\n", &(u.a));
printf("%p\n", &(u.i));
return 0;
}
通过打印其地址,验证了它们共用同一块空间。
union Un
{
char a; //1
int i; //4
};
int main()
{
union Un u = { 0 };
u.i = 0x11223344;
u.a = 0x55;
printf("%x\n", u.i);
return 0;
}
①通过先对联合体中的 i 赋值,如下图所示:
②再对联合体中的 a 进行赋值,将覆盖 i 的一个字节大小的值。
③ 如下图所示:
进行调试也能发现:
3.相同成员的结构体和联合体对⽐
两者主要的区别在于其空间的分配:
对于结构体而言,其存在内存对齐规则,而对于联合体而言,其存在共用内存的规则。
4.联合体大小的计算
区别于结构体的内存大小计算方式,编译器只为最⼤的成员分配⾜够的内存空间,其中联合体的成员共用一块内存空间,联合体大小的计算需满足以下两个规则:
①:联合体的⼤⼩⾄少是最⼤成员的⼤⼩。
②:当最⼤成员⼤⼩不是最⼤对⻬数的整数倍的时候,就要对⻬到最⼤对⻬数的整数倍。
union Un
{
char a; //1
int i; //4
};
int main()
{
union Un u={0};
printf("%zd",sizeof(u));
return 0;
}
由于 int 类型变量占用4个字节大小的空间 char类型变量占用1个字节大小的空间
所以整个联合体取决于int类型的内存大小。
但也存在一定的特殊性,出现最大内存成员不是最大对齐数的整数倍时,则需对齐到最大对齐数的整数倍。
比如:
#include <stdio.h>
union Un1
{
char c[5]; //1 8 1
int i; // 4 8 4
};
union Un2
{
short c[7]; //2 8 2
int i; // 4 8 4
};
int main()
{
//下面输出的结果是什么?
printf("%d\n", sizeof(union Un1));
printf("%d\n", sizeof(union Un2));
return 0;
}
①:对于联合体Un1 ,其内存最大成员为char c[5] ,总共占5个字节大小,但其成员中的最大对齐数为4 ,出现了内存最大成员不为最大对齐数的整数倍,这时就需要对齐到最大内存的整数倍,则联合体的内存大小为8。
②:对于联合体Un2,其内存最大成员为short c[7] ,总共占14个字节大小,但其成员中的最大对齐数为4,出现了内存最大成员成员不是最大对齐数的整数倍时,这时就需要对齐到最大内存的整数倍,则该联合体的内存大小为16个字节。
5.联合体的应用
练习一:
比如要实现这样一个目的:我们要搞⼀个活动,要上线⼀个礼品兑换单,礼品兑换单中有三种商品:图书、杯⼦、衬衫。
每⼀种商品都有:库存量、价格、商品类型和商品类型相关的其他信息。
其中三种商品也有自己特殊的属性:
①图书:书名、作者、⻚数
②杯⼦:设计
③衬衫:设计、可选颜⾊、可选尺⼨
若我们不加思索直接用结构体描述的话可以写出如下代码:
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;//尺⼨
}
若仅通过结构体将其所有属性列出的话,我们会发现在描述书本时,向 设计 颜色 尺寸 这类属性将用不到,此时就导致了内存浪费,同理在在描述 杯子 和 衬衫 的时候也会有部分属性用不到,这时我们就要思考,如何既做到不浪费空间,又能实现所需目的,对此而言,联合体是一个很好的选择。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
// 定义商品类型常量(方便识别)
#define TYPE_BOOK 0
#define TYPE_MUG 1
#define TYPE_SHIRT 2
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;
};
// 打印礼品信息的函数
void print_gift(struct gift_list gift)
{
printf("库存量:%d,定价:%.2f,类型:", gift.stock_number, gift.price);
// 根据 item_type 判断具体类型并打印特有属性
switch (gift.item_type)
{
case TYPE_BOOK:
printf("书籍\n");
printf(" 书名:%s\n 作者:%s\n 页数:%d\n",
gift.item.book.title,
gift.item.book.author,
gift.item.book.num_pages);
break;
case TYPE_MUG:
printf("马克杯\n");
printf(" 设计:%s\n", gift.item.mug.design);
break;
case TYPE_SHIRT:
printf("衬衫\n");
printf(" 设计:%s\n 颜色数量:%d\n 尺寸数量:%d\n",
gift.item.shirt.design,
gift.item.shirt.colors,
gift.item.shirt.sizes);
break;
default:
printf("未知类型\n");
}
}
int main()
{
// 创建一个书籍类型的礼品
struct gift_list book_gift =
{
.stock_number = 50,
.price = 39.9,
.item_type = TYPE_BOOK
};
strcpy(book_gift.item.book.title, "C语言入门");
strcpy(book_gift.item.book.author, "张三");
book_gift.item.book.num_pages = 300;
// 创建一个衬衫类型的礼品
struct gift_list shirt_gift =
{
.stock_number = 20,
.price = 99.0,
.item_type = TYPE_SHIRT
};
strcpy(shirt_gift.item.shirt.design, "卡通图案");
shirt_gift.item.shirt.colors = 3; // 3种颜色可选
shirt_gift.item.shirt.sizes = 4; // 4种尺寸可选
// 打印礼品信息
print_gift(book_gift);
printf("-------------------\n");
print_gift(shirt_gift);
return 0;
}
通过在联合体中,再用结构体描述其特有的属性,就能够做到,在描述一种商品时,不出现空间浪费,而且还能做到,商品的信息独立,比如:在描述书本时就会覆盖 杯子 和 衬衫 的所存储在内存中的内容。
练习二:
写⼀个程序,判断当前机器是⼤端?还是⼩端
①原始方法:
通过用char * 的指针,指向一个大小为 1 的整形变量 ,解引用就能访问其第一个字节,若第一个字节为1则说明其是小端存储,反之若第一个字节为0则说明其是大端存储。
int check()
{
int i = 1;
char* p = (char *)&i; //0x 01 00 00 00
return *p;
}
int main()
{
if (check())
{
printf("小端存储");
}
else
{
printf("大端存储");
}
return 0;
}
②利用联合体: 将整形变量和字符变量放在同一个联合体,赋予整型变量的值为1,通过访问字符变量,就能得到整形变量中的第一个字节,若第一个字节为1则说明其是小端存储,反之若第一个字节为0则说明其是大端存储。
#include<stdio.h>
union Un
{
char c;
int i;
};
void check()
{
union Un u={0};
u.i=1;
if(u.c==1)
{
printf("小端存储");
}
else
{
printf("大端存储");
}
}
int main()
{
check();
return 0;
}
二、枚举
1.枚举变量的声明
枚举顾名思义就是⼀⼀列举,把可能的取值⼀⼀列举。
语法结构如下:
enum tag (关键字:enum + 变量名 :tag)
{
枚举常量,
枚举常量,
枚举常量
};
温馨提示:末尾分号别忘记
⽐如我们现实⽣活中:
①:⼀周的星期⼀到星期⽇是有限的7天,可以⼀⼀列举
②:性别有:男、⼥、保密,也可以⼀⼀列举
③:三原⾊,也是可以意义列举
enum Day//星期
{
Mon, //0
Tues, //1
Wed, //2
Thur, //3
Fri, //4
Sat, //5
Sun //6
};
enum Sex//性别
{
MALE, //0
FEMALE, //1
SECRET //2
};
enum Color//颜⾊
{
RED, //0
GREEN, //1
BLUE //2
};
以上定义的 enum Day , enum Sex , enum Color 都是枚举类型。
其中{ }中的内容是枚举类型的可能取值,也叫 枚举常量 。
这些可能取值都是有值的,默认从0开始,依次递增1,当然在声明枚举类型的时候也可以赋初值。
例如:
enum Color//颜⾊ { RED=2, GREEN=4, BLUE=8 };
如果枚举常量中的一个值改变,其后的枚举常量也会跟着该改变。
例如:
enum Color//颜⾊ { RED, GREEN=5, BLUE };此时枚举常量RED的值为0,GREEN的值为5,BLUE的值为6
2.枚举的优点
我们通常使用 #define 来定义常量,为什么要用枚举呢?
①. 增加代码的可读性和可维护性
对于#define 定义的常量,需要进行单独修改,而通过枚举定义的枚举常量,可以做到成群修改。
②. 和#define定义的标识符相⽐较,枚举有类型检查,更加严谨。
③. 便于调试,预处理阶段会删除 #define 定义的符号
④. 使⽤⽅便,⼀次可以定义多个常量
⑤. 枚举常量是遵循作⽤域规则的,枚举声明在函数内,只能在函数内使⽤
3.枚举的应用
练习一:
比如要实现这样一个目的:我们要搞⼀个活动,要上线⼀个礼品兑换单,礼品兑换单中有三种商品:图书、杯⼦、衬衫。
每⼀种商品都有:库存量、价格、商品类型和商品类型相关的其他信息。
其中三种商品也有自己特殊的属性:
①图书:书名、作者、⻚数
②杯⼦:设计
③衬衫:设计、可选颜⾊、可选尺⼨
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
// 定义商品类型常量(方便识别)
//#define TYPE_BOOK 0
//#define TYPE_MUG 1
//#define TYPE_SHIRT 2
//利用枚举修改define 定义的常量
enum TYPE
{
TYPE_BOOK,
TYPE_MUG,
TYPE_SHIRT
};
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;
};
// 打印礼品信息的函数
void print_gift(struct gift_list gift)
{
printf("库存量:%d,定价:%.2f,类型:", gift.stock_number, gift.price);
// 根据 item_type 判断具体类型并打印特有属性
switch (gift.item_type)
{
case TYPE_BOOK:
printf("书籍\n");
printf(" 书名:%s\n 作者:%s\n 页数:%d\n",
gift.item.book.title,
gift.item.book.author,
gift.item.book.num_pages);
break;
case TYPE_MUG:
printf("马克杯\n");
printf(" 设计:%s\n", gift.item.mug.design);
break;
case TYPE_SHIRT:
printf("衬衫\n");
printf(" 设计:%s\n 颜色数量:%d\n 尺寸数量:%d\n",
gift.item.shirt.design,
gift.item.shirt.colors,
gift.item.shirt.sizes);
break;
default:
printf("未知类型\n");
}
}
int main()
{
// 创建一个书籍类型的礼品
struct gift_list book_gift =
{
.stock_number = 50,
.price = 39.9,
.item_type = TYPE_BOOK
};
strcpy(book_gift.item.book.title, "C语言入门");
strcpy(book_gift.item.book.author, "张三");
book_gift.item.book.num_pages = 300;
// 创建一个衬衫类型的礼品
struct gift_list shirt_gift =
{
.stock_number = 20,
.price = 99.0,
.item_type = TYPE_SHIRT
};
strcpy(shirt_gift.item.shirt.design, "卡通图案");
shirt_gift.item.shirt.colors = 3; // 3种颜色可选
shirt_gift.item.shirt.sizes = 4; // 4种尺寸可选
// 打印礼品信息
print_gift(book_gift);
printf("-------------------\n");
print_gift(shirt_gift);
return 0;
}
定义商品类型常量(方便识别)
#define TYPE_BOOK 0#define TYPE_MUG 1
#define TYPE_SHIRT 2改进:利用枚举修改define 定义的常量
enum TYPE
{
TYPE_BOOK,
TYPE_MUG,
TYPE_SHIRT
};
通过将#define定义的常量封装到枚举变量中,便于维护和使用。
既然看到这里了,不妨点赞和评论一下吧!







1069





