32.1.共用体基本特性概述
(1)共用体union和结构体struct在类型定义和变量定义和使用方法上很相似;结构体类似于包裹,结构体中的成员彼此独立存在于不同的内存单元中,它们只是被打包成整体被称为结构体而已;共用体中的各个成员彼此存在于相同的内存单元中,该内存单元空间有多种解释方式,共用体union就是对同1块内存中存储的二进制的不同的理解方式。
(2)union的sizeof测到的大小实际是union中各个元素里面占用内存最大的那个元素的大小;union中的元素不存在内存对齐的问题,因为union中实际只有1个内存空间,开始地址均相同(开始地址即整个union占有的内存空间的首地址),则不涉及内存对齐。
32.2.共用体的主要用途
(1)共用体和结构体的相同点是操作语法几乎相同;不同点是struct是多个独立元素(内存空间)打包;而union是1个元素(内存空间)的多种不同解析方式。
(2)共用体就用在那种对同1个内存单元进行多种不同规则解析的这种情况下;C语言中其实是可以没有共用体的,用指针和强制类型转换可以替代共用体完成同样的功能,但是共用体的方式更简单、更便捷、更好理解。
32.3.大小端模式概述
(1)在计算机串行通信中(如串口)每次只能发送1个字节,发送1个int类型的数就存在byte0、byte1、byte2、byte3字节发送和接收顺序问题,即发送方和接收方必须按照同样的字节顺序来通信,否则就会出现错误,此即通信系统中的大小端模式(大端模式(big endian)和小端模式(little endian))。
(2)此处的大小端模式,更多是指计算机存储系统的大小端,在计算机内存/硬盘/Nnad中,因为存储系统是32位的,但数据仍然是以字节为单位的,则1个32位的二进制在内存中存储时有2种分布方式:高字节对应高地址(小端模式)、高字节对应低地址(大端模式)。
(3)大端模式和小端模式本身没有对错,没有优劣,理论上按照大端或小端都可以,但是要求必须存储时和读取时按照同样的大小端模式来进行,否则会出错。
(4)现实中的情况:有些CPU用大端(譬如C51单片机);有些CPU用小端(譬如ARM);普遍来说大部分CPU是用小端模式,大端模式的CPU不算多;当程序员不知道当前环境的大小端模式时就需要用代码来检测当前环境的大小端模式。
32.4.测试机器大小端模式
(1)在不知道当前机器的大小端模式时,我们可通过指针方式或union共用体方式测试机器的大小端模式。
(2)位与运算:位与的方式无法测试机器的大小端模式(表现就是大端机器和小端机器的&运算后的值相同的);因为位与运算是编译器提供的运算,该运算是高于内存层次的(&运算在二进制层次具有可移植性,即&运算时是高字节&高字节,低字节&低字节,和二进制存储顺序无关)。
(3)移位:移位的方式也不能测试机器大小端模式;因为C语言对运算符的级别是高于二进制层次的,右移运算永远是将低字节移除,而和二进制存储时该低字节在高位还是低位无关的。
(4)强制类型转换:强制类型转换也不能测试机器大小端模式;因为C语言处理强制类型转换时在二进制层次具有可移植性。
32.5.通信系统中的大小端
(1)譬如要通过串口发送1个0x12345678给接收方,但是因为串口本身限制,只能以字节为单位来发送,所以需要发4次;接收方分4次接收,内容分别是0x12、0x34、0x56、0x78,接收方接收到这4个字节之后需要去重组得到0x12345678(而不是得到0x78563412);则在通信双方需要约定好先发/先接的是高位还是低位,此即通信中的大小端问题。
(2)先发低字节叫小端;先发高字节就叫大端;实际操作中,在通信协议里面会去定义大小端,其会明确告诉你先发的是低字节还是高字节。
(3)在通信协议中,大小端是非常重要的,无论是使用别人定义的通信协议或者自己要去定义通信协议,一定都要注意标明通信协议中大小端的问题。
32.6.枚举的概述
(1)枚举在C语言中是符号常量集,即枚举定义了某些符号,这些符号的本质就是int类型的常量,每个符号和想对应的常量绑定,编译器对枚举的认知就是符号常量所绑定的那个int类型的数字。
(2)枚举中的枚举值都是常量数字(枚举值是全局的,可直接单独使用);枚举符号常量和其对应的常量数字相对来说,数字不重要,符号才重要;符号对应的数字只要彼此不相同即可,没有别的要求;则程序员都不明确指定该符号所对应的数字,而让编译器自动分配(编译器自动分配的原则:从0开始依次增加,若用户自己定义了某个值,则从那个值开始往后依次增加)。
(3)C语言没有枚举是可以的,使用枚举其实就是对1、0这些数字进行符号化编码,这样的好处就是编程时可以不用看数字而直接看符号,符号的意义是显然的,一眼可以看出,而数字所代表的含义除非看文档或者注释。
(4)宏定义的目的和意义是:不用数字而用符号,宏定义和枚举有内在联系;宏定义和枚举经常用来解决类似的问题,宏定义和枚举基本相当可以互换,但是有一些细微差别。
32.7.宏定义和枚举的区别
(1)枚举是将多个有关联的符号封装在某个枚举中(枚举即多选1,限定了枚举变量的取值范围);而宏定义是完全分散定义各个符号常量的。
(2)当定义的常量符号是某个有限集合时(譬如1星期有7天,譬如1月有31天,譬如1年有12个月···),推荐用枚举;当定义的常量符号之间无关联,或者无限的(此时无法使用枚举),推荐使用宏定义。
(3)宏定义先出现,用来解决符号常量的问题;后来发现有时候定义的符号常量彼此之间有关联(多选1的关系),用宏定义来实现极其不贴切,则发明了枚举。
32.union
/*
* 公司:XXXX
* 作者:Rston
* 博客:http://blog.csdn.net/rston
* GitHub:https://github.com/rston
* 项目:共同体和大小端及枚举
* 功能:测试共用体的基本特性。
*/
#include <stdio.h>
// 结构体类型定义
struct mystruct
{
int a;
char b;
};
// 共用体类型定义
union myunion
{
int a;
char b;
};
// 使用sizeof测试union
typedef union xx
{
int a;
char b;
}xxx;
// 测试同一内存空间使用不同方式去解析
union test
{
int a;
float b;
};
int main(int argc, char **argv)
{
// 测试结构体类型
struct mystruct s1;
s1.a = 88;
printf("s1.b = %d.\n", s1.b); // s1.b = -119.
printf("&s1.a = %p. &s1.b = %p.\n", &s1.a, &s1.b); // &s1.a = 0xbfa5c674. &s1.b = 0xbfa5c678.
// 测试共用体类型
union myunion u1;
u1.a = 88;
printf("u1.b = %d.\n", u1.b); // u1.b = 88.
printf("&u1.a = %p. &u1.b = %p.\n", &u1.a, &u1.b); // &u1.a = 0xbfa5c67c. &u1.b = 0xbfa5c67c.
// 使用sizeof测试
printf("sizeof(union xx) = %d.\n", sizeof(union xx)); // sizeof(union xx) = 4.
printf("sizeof(xxx) = %d.\n", sizeof(xxx)); // sizeof(xxx) = 4.
// 测试同一内存空间使用不同方式去解析
union test t1;
t1.a = 1123477881;
printf("t1.b = %f.\n", t1.b); // t1.b = 123.456001.
// 使用指针和强制类型转换替代共用体
int a = 1123477881;
printf("*((float *)&a) = %f.\n", *((float *)&a)); // *((float *)&a) = 123.456001.
return 0;
}
32.endian_test_error
/*
* 公司:XXXX
* 作者:Rston
* 博客:http://blog.csdn.net/rston
* GitHub:https://github.com/rston
* 项目:共用体和大小端及枚举
* 功能:演示通过位与、移位、强制类型转化测试机器的大小端模式。
*/
#include <stdio.h>
int main(void)
{
// 位与测试机器大小端模式错误
int a = 1;
int b = a & 0xff;
if (1 == b)
{
printf("当前环境为小端模式\n");
}
else
{
printf("当前环境为大端模式\n");
}
// 移位测试机器大小端模式错误
int c = 1, d = 0;
d = c >> 1;
if (0 == d)
{
printf("当前环境为小端模式\n");
}
else
{
printf("当前环境为大端模式\n");
}
// 强制类型转换测试机器大小端模式错误
int e = 1;
char f = (char)e;
if (1 == f)
{
printf("当前环境为小端模式\n");
}
else
{
printf("当前环境为大端模式\n");
}
return 0;
}
32.endian_test_ok
/*
* 公司:XXXX
* 作者:Rston
* 博客:http://blog.csdn.net/rston
* GitHub:https://github.com/rston
* 项目:共用体和大小端及枚举
* 功能:通过共用体和指针方式分别测试当前环境的大小端模式。
*/
#include <stdio.h>
union myunion
{
int a;
char b;
};
// 通过共用体测试当前环境的大小端模式
// 若当前环境是小端模式则返回1,若当前环境是大端模式返回0
int is_little_endian1(void)
{
union myunion u1;
u1.a = 1; // 起始地址处的那个字节内是1(小端)或者0(大端)
return u1.b;
}
// 通过指针方式测试当前环境的大小端模式
// 若当前环境是小端模式则返回1,若当前环境是大端模式返回0
int is_little_endian2(void)
{
int a = 1;
int b = *((char *)&a); // 共用体的本质即通过指针方式访问同一内存单元
return b;
}
int main(int argc, char **argv)
{
int ret = is_little_endian2();
if (0 == ret)
{
printf("当前环境是大端模式\n");
}
else
{
printf("当前环境是小端模式\n");
}
return 0;
}
32.enum_define
/*
* 公司:XXXX
* 作者:Rston
* 博客:http://blog.csdn.net/rston
* GitHub:https://github.com/rston
* 项目:共用体和大小端及枚举
* 功能:枚举的定义和使用详解。
*/
#include <stdio.h>
/*
****************************************************************
* enumeration 类型定义
****************************************************************
*/
// 定义类型和定义变量分离开
enum week
{
SUN, // SUN = 0
MON, // MON = 1
TUE,
WEN,
THU,
FRI,
SAT,
};
enum week today;
// 定义类型的同时定义变量
enum gender
{
WOMEN, // WOMEN = 0 因没有手动给成员WOMEN和MAN赋值,
MAN, // MAN = 1 导致WOMEN和SUN无法区分,MAN和MON无法区分
}rston,lucy;
// 定义类型的同时定义变量
enum
{
APPLE = 23, // APPLE = 23
BANANA = 24, // BANANA = 24
}fruit;
// 使用typedef定义枚举类型别名,并在后面使用别名定义枚举变量
typedef enum color
{
BLUE = 25, // BLUE = 25
YELLOW = 26, // YELLOW = 26
}color;
// 使用typedef定义枚举类型别名,并在后面使用别名定义枚举变量
typedef enum
{
MOUSE = 27, // MOUSE = 27
CAT = 28, // CAT = 28
}animal;
/*
****************************************************************
* 错误类型举例
****************************************************************
*/
#if 0
//枚举类型重名,编译时报错:error: conflicting types for ‘DAY’
typedef enum workday
{
MON, // MON = 1;
TUE,
WEN,
THU,
FRI,
}DAY;
typedef enum weekend
{
SAT,
SUN,
}DAY;
#endif
#if 0
// 枚举成员重名,编译时报错:redeclaration of enumerator ‘MON’
typedef enum workday
{
MON,
TUE,
WEN,
THU,
FRI,
}workday;
typedef enum weekend
{
MON,
SAT,
SUN,
}weekend;
// 结构体中元素可以重名
typedef struct
{
int a;
char b;
}st1;
typedef struct
{
int a;
char b;
}st2;
#endif
#if 0
// 宏定义可以重复定义(没有error但是有warning),结果以最后一次定义为准
#define MACRO1 12
#define MACRO1 24
#endif
int main(int argc, char **argv)
{
// SUN = 0. WOMEN = 0. APPLE = 23.
printf("SUN = %d. WOMEN = %d. APPLE = %d.\n", SUN, WOMEN, APPLE);
// BLUE = 25. MOUSE = 27.
printf("BLUE = %d. MOUSE = %d.\n", BLUE, MOUSE);
// 使用typedef重命名的别名定义变量并使用
color c1 = BLUE;
printf("c1 = %d.\n", BLUE); // c1 = 25.
animal a1 = MOUSE;
printf("a1 = %d.\n", MOUSE); // a1 = 27.
// 测试综合使用枚举
// 在定义多个不同的枚举类型时,注意应手动给每个枚举成员赋值,
// 以便在程序中区分不同枚举类型中的每个成员。
today = SUN; // SUN = 0.
lucy = WOMEN; // WOMEN = 0.
fruit = APPLE; // APPLE = 23.
// today = 0. lucy = 0. fruit = 23.
printf("today = %d. lucy = %d. fruit = %d.\n", today, lucy, fruit);
switch (fruit)
{
case SUN:
printf("SUN.\n");
break;
/*
case WOMEN:
printf("WOMEN.\n");
break;
*/
case APPLE:
printf("APPLE.\n"); // APPLE.
break;
default:
printf("default.\n");
break;
}
return 0;
}
32.enumeration
/*
* 公司:XXXX
* 作者:Rston
* 博客:http://blog.csdn.net/rston
* GitHub:https://github.com/rston
* 项目:共用体和大小端及枚举
* 功能:演示枚举的初步使用及枚举和宏定义的区别和联系。
*/
#include <stdio.h>
// 使用宏定义解决函数返回值问题
#define TRUE 1
#define FALSE 0
// 该枚举用来表示函数返回值,ERROR表示错误,RIGHT表示正确
enum return_value
{
ERROR = 20, // 枚举值是全局的,可直接单独使用
RIGHT = 25,
};
enum return_value func1(void);
int main(int argc, char **argv)
{
// 通过枚举判断函数执行正确与否
enum return_value r = func1();
if (RIGHT == r)
{
printf("函数执行正确\n");
}
else
{
printf("函数执行错误\n");
}
// 枚举中的枚举值都是常量 ERROR = 20. RIGHT = 25.
printf("ERROR = %d. RIGHT = %d.\n", ERROR, RIGHT);
// 通过宏定义判断函数执行正确与否
int ret = func2();
if (TRUE == ret)
{
printf("函数执行正确\n");
}
else
{
printf("函数执行错误\n");
}
return 0;
}
// 通过枚举返回函数执行结果
enum return_value func1(void)
{
enum return_value r = ERROR;
return r;
}
// 通过宏定义返回函数执行结果
int func2(void)
{
return TRUE;
}