联合体(共用体)基本概念
联合体的外在形式跟结构体非常类似,但它们有一个本质的区别:结构体中的各个成员是各自独立的,而联合体中的各个成员却共用同一块内存,因此联合体也称为共用体。
联合体各成员的堆叠效果
联合体内部成员的这种特殊的“堆叠”效果,使得联合体有如下基本特征:
- 整个联合体变量的尺寸,取决于联合体中尺寸最大的成员。
- 给联合体的某个成员赋值,会覆盖其他的成员,使它们失效。
- 联合体各成员之间形成一种“互斥”的逻辑,在某个时刻只有一个成员有效。
联合体的定义:
union 联合体标签
{
成员1;
成员2;
...
};
- 语法:
- 联合体标签,用来区分各个不同的联合体。
- 成员,是包含在联合体内部的数据,可以是任意的数据类型。
// 定义了一种称为 union attr 的联合体类型
union attr
{
int x;
char y;
double z;
};
int main()
{
// 定义联合体变量
union attr n;
}
联合体操作
联合体的操作跟结构体形式上别无二致,但由于联合体特殊的存储特性,不管怎么初始化和赋值,最终都有且仅有一个成员是有效的。
- 初始化:
// 普通初始化:第一个成员有效(即只有100是有效的,其余成员会被覆盖)
union attr at = {100, 'k', 3.14};
// 指定成员初始化:最后一个成员有效(即只有3.14是有效的,其余成员会被覆盖)
union attr at = {
.x = 100,
.y = 'k',
.z = 3.14,
};
- 成员引用:
at.x = 100;
at.y = 'k';
at.z = 3.14; // 只有最后一个赋值的成员有效
printf("%d\n", at.x);
printf("%c\n", at.y);
printf("%lf\n", at.z);
- 联合体指针:
union attr *p = &at;
p->x = 100;
p->y = 'k';
p->z = 3.14; // 只有最后一个赋值的成员有效
printf("%d\n", p->x);
printf("%c\n", p->y);
printf("%lf\n", p->z);
联合体定义与初始化
#include <stdio.h>
// 联合体声明
union node
{
char c;
double a;
int b;
};
// 联合体声明并创建数据段空间
union
{
char c;
double a;
int b;
}node1;
int main(int argc, char const *argv[])
{
// 计算联合体大小
printf("%d\n",sizeof(union node));
// 联合体不能使用普通成员初始化
// union node un = {12,'k',3.14};
// printf("%d,%c,%lf\n",un.a,un.b,un.c);
// 联合体指定成员初始化
// 指定成员初始化的覆盖方式为从下往上覆盖,比如b会覆盖a
// 和联合体的声明无关
union node un =
{
.c = 'k',
.a = 3.14,
.b = 97,
};
printf("%lf,%d,%c\n",un.a,un.b,un.c);
// 联合体成员引用
// 指定成员初始化的覆盖方式为从下往上覆盖,比如b会覆盖a
// 和联合体的声明无关
union node un1;
un1.a = 4.18;
un1.b = 19;
un1.c = 'k';
printf("%f,%d,%c\n",un1.a,un1.b,un1.c);
node1.a = 8.9;
node1.b = 20;
node1.c = 'f';
printf("%f,%d,%c\n",node1.a,node1.b,node1.c);
return 0;
}
联合体的使用
联合体一般很少单独使用,而经常以结构体的成员形式存在,用来表达某种互斥的属性。
- 示例:
struct node
{
int a;
char b;
double c;
union attr at; // at内有三种互斥的属性,非此即彼
};
int main()
{
struct node n;
n.at.x = 100; // 使用连续的成员引用符来索引结构体中的联合体成员
}
测试大小端
#include <stdio.h>
union node
{
int data;
char ch;
};
int main(int argc, char const *argv[])
{
union node n;
n.data = 0x12345678;
// 通过联合测试,字节序为小端模式
printf("%x\n",n.ch);
return 0;
}
联合体使用场景
联合体一般不会单独使用,而是以结构体的成员方式存在,用来表示互斥的效果,即一次只能赋予一个值
用来表示某种互斥属性,比如学生选修课
#include <stdio.h>
struct student
{
char name[256];
union
{
int Chinese; // 语文
float Math; // 数学
double English; // 英文
}cur;
};
int main(int argc, char const *argv[])
{
struct student st = {
.name = "jack",
.cur.Chinese = 1
};
if(st.cur.Chinese == 1)
{
printf("%s选择语文课\n",st.name);
}
return 0;
}
枚举
枚举类型的本质是提供一种范围受限的整型,比如用0-6表示七种颜色,用0-3表示四种状态等,但枚举在C语言中并未实现其本来应有的效果,直到C++环境下枚举才拥有原本该有的属性。
- 枚举常量列表
- enum是关键字
- spectrum是枚举常量列表标签,可以省略。省略的情况下无法定义枚举变量
enum spectrum{red, orange, yellow, green, blue, cyan, purple};
enum {reset, running, sleep, stop};
- 枚举变量
enum spectrum color = orange; // 等价于 color = 1
- 语法要点:
- 枚举常量实质上就是整型,首个枚举常量默认为0。
- 枚举常量在定义时可以赋值,若不赋值,则取其前面的枚举常量的值加1。
- C语言中,枚举等价于整型,支持整型数据的一切操作。
- 使用举例:
switch(color)
{
case red:
// 处理红色...
case orange:
// 处理橙色...
case yellow:
// 处理黄色...
}
#include <stdio.h>
// 创建枚举一般是在全局变量或者头文件
enum color{red,orange,yellow=5,green,blue,cyan,purple};
int main(int argc, char const *argv[])
{
enum color co = blue;
switch (co)
{
case red:
printf("显示红色\n");
break;
case orange:
printf("显示橙色\n");
break;
case blue:
printf("显示蓝色\n");
default:
break;
}
return 0;
}
- 枚举数据最重要的作用,是使用有意义的单词,来替代无意义的数字,提高程序的可读性。
总结:
enum weekday{mon,tue,wed,thu,fri}; // mon = 0,tue = 1,wed = 2,thu = 3,fri = 4
1.枚举第一个值默认为0
比如mon为0,后面的大小依以此类推 1,2,3,4
2.如果枚举变量里面某个成员设置了数值,往后的其它成员的数值在它基础上进行累加
enum weekday{mon,tue=100,wed,thu,fri}; // mon = 0,tue = 100,wed = 101,thu = 102,fri = 103
3.枚举常量实际就是一个整型,首个枚举常量默认为0
4.枚举在c语言的作用是增加代码的可读性
练习:
设计一个小车,用枚举实现前进,后退,向左,向右四个功能,根据mode选择小车移动的方向
#include <stdio.h>
enum Car{FORWARD=1,BACK,LEFT,RIGHT};
int main(int argc, char const *argv[])
{
int mode;
printf("输入小车的运行轨迹(1:向前,2:向后,3:向左,4:向右): ");
scanf("%d",&mode);
enum Car MyCarMode = mode;
switch (MyCarMode)
{
case FORWARD:
printf("小车向前运动\n");
break;
case BACK:
printf("小车向后运动\n");
break;
case LEFT:
printf("小车向左运动\n");
break;
case RIGHT:
printf("小车向右运动\n");
break;
default:
break;
}
return 0;
}
设计一个错误系统
#include <stdio.h>
enum errorInfo
{
E_ERROR_DIV, // 除数不能为0
E_ERROR_USERNAME_EXIST, // 用户名已经存在
E_ERROR_FILE_EXIST, // 文件存在
E_ERROR_FILE_NOEXIST, // 文件不存在
E_ERROR_NONE // 未知错误
};
// 定义指针数组记录错误信息
static char *error_buf[] = {
// [0] = "除数不能为0" == *(error+0) = "除数不能为0"
[E_ERROR_DIV] = "除数不能为0",
// [1] = "用户名已存在" == *(error+1) = "用户名已存在"
[E_ERROR_USERNAME_EXIST] = "用户名已存在",
[E_ERROR_FILE_EXIST] = "文件已存在",
[E_ERROR_FILE_NOEXIST] = "文件不存在",
[E_ERROR_NONE] = "未知错误"
};
char *get_errorInfo(enum errorInfo mc_erron)
{
return error_buf[mc_erron];
}
int main(int argc, char const *argv[])
{
int a = 0;
int b = 0;
//int c = a/b; // 错误
int erron = E_ERROR_DIV;
if(b == 0)
{
// 输出分母不能为0
printf("%s\n",get_errorInfo(erron));
}
return 0;
}