【C语言】自定义类型——联合和枚举

目录

一、联合体

1.1 联合体类型的声明

1.2 联合体的特点

1.2.1 特点1

1.2.2 特点2

1.2.3 特点3

1.3 联合体的大小

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

1.5 使用联合体节省空间的例子

1.6 运用联合体判断大小端

1.7 利用联合体打印存储的字节内容

二、枚举类型

2.1 枚举类型的声明

2.2 枚举类型的优点

2.3 枚举类型的使用


一、联合体

1.1 联合体类型的声明

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

声明方式如下:

#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>
//联合类型的声明
union Un
{
char c;
int i;
};

那联合体和结构体究竟有什么区别呢??

下面将重点讲解联合体的特点!!

1.2 联合体的特点

1.2.1 特点1

所有成员共⽤同⼀块内存空间。所以联合体也叫:共⽤体。

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;
}

000000B31D0FF694

000000B31D0FF694

000000B31D0FF694

我们可以发现,三个地址打印出来是一样的。那既然都共用一块空间,那大小有多大呢??

1.2.2 特点2

编译器只为最大的成员分配⾜够的内存空间(因为联合体至少得有能力保护最大的那个成员)

//联合类型的声明
union Un
{
char c;
int i;
}
Jint main()
//联合变量的定义union Un un ={0 };
//计算连个变量的大小
printf("%d\n",sizeof(un));
return 0;
}

4

1.2.3 特点3

给联合体其中⼀个成员赋值,其他成员的值也跟着变化。

#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;
}

11223355

这里为什么打印出来的是11223355呢,我们根据3个特点,可以分析画出un的内部布局图

充分说明了特点3!

1.3 联合体的大小

特点2提到,编译器只为联合体最大的成员分配足够的空间,那联合体的大小就一定等于最大成员变量的大小吗??

答案是不对的,我们可以看看下面的代码

#include <stdio.h>
union Un1
{
char c[5];//5
int i;//4

union Un2
{
short c[7];//14
int i;//4

int main()
{
//下面输出的结果是什么?
printf("%d\n",sizeof(union Un1));
printf("%d\n",!sizeof(union Un2));
return ;
}

8

16

 我们可以验证出,虽然编译器只为最大的成员分配足够空间,但不代表联合体的大小就是最大成员变量的大小!!!

联合体的大小要遵循以下两个特点:

1、联合的大小⾄少是最⼤成员的大小。

2、当最⼤成员大小不是最大对齐数的整数倍的时候,就要对⻬到最⼤对⻬数的整数倍。

这说明,联合体虽然可以节省空间,但也不是一味地节省,他也是有自己的对齐规则的。

分析上图代码:

Un1的第一个成员数组虽然是5个字节的大小,但是最大对齐数只能取char类型,所以是1,而int是4,所以Un1的最大对齐数是4,为了保证能放下5个字节的空间,所以最大对齐数翻倍变成8!

Un2的第一个成员数组虽然是14个字节的大小,但最大对齐数只能取short类型,所以是2,而int是4,所以Un2的最大对齐数是4,为了保证能放下14个字节的空间,所以最大对齐数翻4倍变成16!

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

我们再对⽐⼀下相同成员的结构体和联合体的内存布局情况。

struct S
{
char c;
int i;
};
struct Ss={0};

union Un
{
char c;
int i;
};
union Un un={ 0 };

这说明使用联合体是可以节省空间的!!!

1.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;//尺寸
};

但我们会发现,如果创建book变量,那设计、颜色、尺寸属性就会浪费掉。如果创建cup变量,那书名、作者、页数、可选颜色、尺寸属性就会浪费掉。如果创建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;
};

因为我们每个变量只使用一次,所以可以直接使用匿名结构体。

1.6 运用联合体判断大小端

int check_sys()
{
union
{
int i;
char c;
}un;
un.i = 1;
//如果是小端存储 01 00 00 00 此时因为c和i共用一块空间,c指向首字节,取c就是取01
//如果是大端存储00 00 00 01,取c就是取00
return un.c;//返回1是小端,返回0是大端
}

int main()
{
int ret = check_sys();
if (ret == 1)
printf("小端\n");
else
printf("大端\n");
return 0;
}

小端

1.7 利用联合体打印存储的字节内容

既然可以判断大小端,那其实也可以直接把存储的情况打印出来!!这是一个很神奇的代码!

union U
{
int i;//4
struct S
{
char c1:
char c2;
char c3;
char c4;
}s;//4
:
//因为恰好都是4个字节的大小,char1指向第一个字节,char2指向第二个字节,…以此类推
//所以我们只要将c1-c4打印出来,就可以知道内存的存储情况了,还可以顺便判断大小端。

int main()
{
union u un={0 };un.i = 0x11223344;//大端是11223344 小端是44332211
printf("%x %x %x %x\n",un.s.cl,un.s.c2,un.s.c3,un.s.c4);
}

44 33 22 11

 

二、枚举类型

2.1 枚举类型的声明

枚举顾名思义就是⼀⼀列举。

把可能的取值⼀⼀列举。

⽐如我们现实⽣活中:

⼀周的星期⼀到星期⽇是有限的7天,可以⼀⼀列举

性别有:男、女、保密,也可以⼀⼀列举

⽉份有12个⽉,也可以⼀⼀列举

三原⾊,也是可以意义列举

enum Day//星期
{
Mon,
Tues
Wed ,
Thur
Fri.
Sat,
Sun
};

enum Sex//性别
{
MALE,
FEMALE,
SECRET
};

enum Color//颜色
{
RED,
GREEN,
BLUE
};

{ }中是枚举类型的可能取值,也叫做枚举常量。

这些可能取值本身都是由值的,默认是从0开始依次递增1,当然我们在声明的时候也可以自己赋初值,但是定义完成之后,就不能在该类型的外部去修改了!!

enum Sex//性别
{
MALE,
FEMALE,
SECRET
};
enum Color//颜色
{
RED,
GREEN ,
BLUE
};

int main()
{
RED = 10;//内部定义好后,外部不能再修改了
}

2.2 枚举类型的优点

1、增加代码的可读性和可维护性

比如我们在实现游戏时常常会这样去写

int main()
{
int input = 0;
srand((unsigned int)time(NULL));//时间函数,在入口设置一次就可以了
do
{
menu();
printf("请做出你的选择:");
scanf("%d",&input);
switch(input)
{
case 1:
  game();
  break;
}
case 0:
  printf("退出游戏\n");
  break;
default:
  printf("非法选择,请重新输入\n");
} 
}while (input);
return 0;
}

此时如果不和菜单建立联系

void menu
{
printf("**********************\n");
printf("**********************\n");
printf("********1.play********\n");
printf("********0.exit********\n");
printf("**********************\n");
printf("**********************\n");
}

我们并不能一下子就看出来case1和case0的含义,可读性较差,可如果在这边使用枚举类型,就可以增加代码的可读性,并且后期在维护的时候也方便。

int main()
{
int input = 0;
srand((unsigned int)time(NULL));//时间函数,在入口设置一次就可以
do
{
menu();
printf("请做出你的选择:scanf("%d",&input);
switch(input)
{
case play:
 game();
 break;
case exit:
 printf("退出游戏\n");
 break;
default:
 printf("非法选择,请重新输入\n");
} 
}while (input);
return 0;

2、 和#define定义的标识符⽐较枚举有类型检查,更加严谨。

#define定义的标识符是不过是一个符号,而枚举是一种类型,有类型检查写代码会更加严谨

3、 便于调试,预处理阶段会删除 #define 定义的符号

 枚举类型在调试的时候会显示出成员名,但是#define就不会,标识符会直接替换成数字,后期如果需要调试找错误就不利于发现问题

4.、使⽤⽅便,⼀次可以定义多个常量

5.、枚举常量是遵循作⽤域规则的,枚举声明在函数内,只能在函数内使⽤

枚举有作用域的概念,在一个函数内部使用,出了函数就不能用了,但是#define定义的标识符没有作用域概念,他是一个全局都可以使用的常量。

2.3 枚举类型的使用

enum Color//颜色
{
RED = 1,
GREEN = 2,
BLUE = 4
};
enum Color cLr = GREEN;

使用方法:使⽤枚举常量给枚举变量赋值

那是否可以拿整数给枚举变量赋值呢?

在C语⾔中是可以的,但是在C++是不⾏的,C++的类型检查⽐ 较严格。

评论 43
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值