上一个专题咱们详细的分享了c语言里面的结构体用法,读者在看这些用法的时候,能够一边看一边试验,掌握了这些基本用法就彻底够用了,固然在之后的工做中,若是有遇到了更高级的用法,咱们能够再来总结学习概括。好了,开始咱们今天的主题分享。
c++
1、共用体union:编程
一、什么是共用体union?数组
这个共用体,估计你们平时在代码也比较少见,我去看了一下stm32的例程里面没怎么看到这个用法(下面的示例分享是在stm32里面找的);其实这个共用体union(也叫联合体)跟咱们上次分享的结构体定义是很是像的,好比说:类型定义、变量定义、使用方法上很类似。就像下面两个例子同样,把许多类型联合在一块儿(不过虽然形式上相似,可是具体用法仍是有区别的,下面会讲他们之间的区别):app
union st{
int a;
char b;
};
二、共用体与结构体的区别:
ide
结构体相似于一个包裹,结构体中的成员彼此是独立存在的,分布在内存的不一样单元中,他们只是被打包成一个总体叫作结构体而已;共用体中的各个成员实际上是一体的,彼此不独立,他们使用同一个内存单元。能够理解为:有时候是这个元素,有时候是那个元素。更准确的说法是同一个内存空间有多种解释方式。因此共用体用法总结以下:学习
union中能够定义多个成员,union的内存大小由最大的成员的大小来决定。 测试
union成员共享同一块大小的内存,一次只能使用其中的一个成员。编码
对某一个成员赋值,会覆盖其余成员的值(这是为啥呢?,简单来说就是由于他们共享一块内存。但前提是成员所占字节数相同,当成员所占字节数不一样时只会覆盖相应字节上的值,好比对char成员赋值就不会把整个int成员覆盖掉,由于char只占一个字节,而int占四个字节)。spa
共用体union的存放顺序是全部成员都从低地址开始存放的。
指针
三、代码实战:
#include <stdio.h>
typedef union{
int a;
char c;
//int a;
// int b;
}st;
int main(void)
{
st haha;
haha.c='B';
// haha.a=10;
//haha.b=60;
printf("the haha size is %d\n",sizeof(haha));
printf("haha.c=%d\n",haha.c);
return 0;
}
#include <stdio.h>
typedef union{
int a;
char c;
int b;
}st;
int main(void)
{
st haha;
haha.c='B';
haha.a=10;
haha.b=60;
printf("the haha size is %d\n",sizeof(haha));
printf("haha.c=%d,haha.a=%d,haha.b=%d\n",haha.c,haha.a,haha.b);
printf("the a is 0x%x\n",&haha.a);
printf("the c is 0x%x\n",&haha.c);
printf("the b is 0x%x\n",&haha.b);
return 0;
}
演示结果:
the haha size is 4
haha.c=66
the haha size is 4
haha.c=60,haha.a=60,haha.b=60
the a is 0x61feac
the c is 0x61feac
the b is 0x61feac
说明:
经过上面的代码示例,读者能够发现这个共用体的大小,并非像咱们以前结构体那样是把每一个成员所占内存大小加起来,而是咱们上面说的那样,共用体由成员占用内存大小最大的那个决定的,上面的示例中int 占用4个字节大小,为最大的,因此sizeof(haha)得出结果就是4个字节大小,并且读者细心能够发现到打印出来的结果a和b都是60,它是访问内存占用大小最大的那个成员的数值,由于那个'B'的acii码值是是66;经过示例,咱们也发现共用体访问其成员方式跟结构体是同样的(上面也有说到过)。下面是和结构体作对比的代码示例:
#include <stdio.h>
// 共用体类型的定义
struct mystruct
{
int a;
char b;
};
// a和b其实指向同一块内存空间,只是对这块内存空间的2种不一样的解析方式。
// 若是咱们使用u1.a那么就按照int类型来解析这个内存空间;若是咱们使用
// u1.b那么就按照char类型
// 来解析这块内存空间。
union myunion
{
int a;
char b;
double c;
};
int main(void)
{
struct mystruct s1;
s1.a = 23;
printf("s1.b = %d.\n", s1.b); // s1.b = 0. 结论是s1.a和s1.b是独立无关的
printf("&s1.a = %p.\n", &s1.a);
printf("&s1.b = %p.\n", &s1.b);
union myunion u1; // 共用体变量的定义
u1.a = 23;
u1.b='B';
u1.a=u1.b; // 共用体元素的使用
printf("u1.a = %d.\n", u1.a);
printf("u1.b = %d.\n", u1.b);
printf("u1.c = %d.\n", u1.c);
// u1.b = 23.结论是u1.a和u1.b是相关的
// a和b的地址同样,充分说明a和b指向同一块内存,只是对这块内存的不一样解析规则
printf("&u1.a = %p.\n", &u1.a);
printf("&u1.b = %p.\n", &u1.b);
printf("the sizeof u1 is %d\n",sizeof(u1));
return 0;
}
演示结果:
s1.b = 22.
&s1.a = 0061FEA8.
&s1.b = 0061FEAC.
u1.a = 66.
u1.b = 66.
u1.c = 66.四、
&u1.a = 0061FEA0.
&u1.b = 0061FEA0.
the sizeof u1 is 8
四、小结:
union的sizeof测到的大小实际是union中各个元素里面占用内存最大的那个元素的大小。由于能够存的下这个就必定可以存的下其余的元素。
union中的元素不存在内存对齐的问题,由于union中实际只有1个内存空间,都是从同一个地址开始的(开始地址就是整个union占有的内存空间的首地址),因此不涉及内存对齐。
2、枚举
一、什么是枚举?
枚举在C语言中实际上是一些符号常量集。直白点说:枚举定义了一些符号,这些符号的本质就是int类型的常量,每一个符号和一个常量绑定。这个符号就表示一个自定义的一个识别码,编译器对枚举的认知就是符号常量所绑定的那个int类型的数字。枚举符号常量和其对应的常量数字相对来讲,数字不重要,符号才重要。符号对应的数字只要彼此不相同便可,没有别的要求。因此通常状况下咱们都不明确指定这个符号所对应的数字,而让编译器自动分配。(编译器自动分配的原则是:从0开始依次增长。若是用户本身定义了一个值,则从那个值开始日后依次增长)。
二、为何要用枚举,和宏定义作对比:
(1)C语言没有枚举是能够的。使用枚举其实就是对一、0这些数字进行符号化编码,这样的好处就是编程时能够不用看数字而直接看符号。符号的意义是显然的,一眼能够看出。而数字所表明的含义除非看文档或者注释。
(2)宏定义的目的和意义是:不用数字而用符号。从这里能够看出:宏定义和枚举有内在联系。宏定义和枚举常常用来解决相似的问题,他们俩基本至关能够互换,可是有一些细微差异。
(3)宏定义和枚举的区别:
枚举是将多个有关联的符号封装在一个枚举中,而宏定义是彻底散的。也就是说枚举实际上是多选一。
(4)使用枚举状况:
什么状况下用枚举?当咱们要定义的常量是一个有限集合时(譬如一星期有7天,譬如一个月有31天,譬如一年有12个月····),最适合用枚举。(其实宏定义也行,可是枚举更好)
不能用枚举的状况下(定义的常量符号之间无关联,或者无限的),这个时候就用宏定义。
总结:
宏定义先出现,用来解决符号常量的问题;后来人们发现有时候定义的符号常量彼此之间有关联(多选一的关系),用宏定义来作虽然能够可是不贴切,因而乎发明了枚举来解决这种状况。
三、代码示例:
a、几种定义方法:
/* // 定义方法1,定义类型和定义变量分离开
enum week
{
SUN, // SUN = 0
MON, // MON = 1;
TUE,
WEN,
THU,
FRI,
SAT,
};
enum week today;
*/
/* // 定义方法2,定义类型的同时定义变量
enum week
{
SUN, // SUN = 0
MON, // MON = 1;
TUE,
WEN,
THU,
FRI,
SAT,
}today,yesterday;
*/
/* // 定义方法3,定义类型的同时定义变量
enum
{
SUN, // SUN = 0
MON, // MON = 1;
TUE,
WEN,
THU,
FRI,
SAT,
}today,yesterday;
*/
/* // 定义方法4,用typedef定义枚举类型别名,并在后面使用别名进行变量定义
typedef enum week
{
SUN, // SUN = 0
MON, // MON = 1;
TUE,
WEN,
THU,
FRI,
SAT,
}week;
*/
/* // 定义方法5,用typedef定义枚举类型别名,并在后面使
用别名进行变量定义
typedef enum
{
SUN, // SUN = 0
MON, // MON = 1;
TUE,
WEN,
THU,
FRI,
SAT,
}week;
b、错误类型举例(下面的举例中也加入告终构体做为对比):
/* // 错误1,枚举类型重名,编译时报错:error: conflicting
// types for ‘DAY’
typedef enum workday
{
MON, // MON = 1;
TUE,
WEN,
THU,
FRI,
}DAY;
typedef enum weekend
{
SAT,
SUN,
}DAY;
*/
/* // 错误2,枚举成员重名,编译时报错:redeclaration //of
// enumerator ‘MON’
typedef enum workday
{
MON, // MON = 1;
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;
*/
说明:
通过测试,两个struct类型内的成员名称能够重名,而两个enum类型中的成员不能够重名。实际上从二者的成员在访问方式上的不一样就能够看出了。struct类型成员的访问方式是:变量名.成员,而enum成员的访问方式为:成员名。所以若两个enum类型中有重名的成员,那代码中访问这个成员时到底指的是哪一个enum中的成员呢?因此不能重名。可是两个#define宏定义是能够重名的,该宏名真正的值取决于最后一次定义的值。编译器会给出警告但不会error,下面的示例会让编译器发出A被重复定义的警告。
#include <stdio.h>
#define A 5
#define A 7
int main(void)
{
printf("hello world\n");
return 0;
}
c、代码实战演示:
#include <stdio.h>
typedef enum week
{
SUN, // SUN = 0
MON, // MON = 1;
TUE, //2
WEN, //3
THU,
FRI,
SAT,
}week;
int main(void)
{
// 测试定义方法4,5
week today;
today = WEN;
printf("today is the %d th day in week\n", today);
return 0;
}
演示结果:
today is the 3 th day in week
d、接着咱们把上面枚举变量改变它的值(不按照编译模式方式来),看看会发生什么变化:
#include <stdio.h>
typedef enum week
{
SUN, // SUN = 0
MON=8, // MON = 1;
TUE, //2
WEN, //3
THU,
FRI,
SAT,
}week;
int main(void)
{
// 测试定义方法4,5
week today,hh;
today = WEN;
hh=SUN;
printf("today is the %d th day in week\n", SUN);
printf("today is the %d th day in week\n", today);
return 0;
}
演示结果(咱们能够看到改变了枚举成员值,它就在这个基础递增下面的成员值):
today is the 0 th day in week
today is the 10 th day in week
注意:
这里要注意,只能把枚举值赋予枚举变量,不能把元素的数值直接赋予枚举变量,如必定要把数值赋予枚举变量,则必须用强制类型转换,可是我在测试时,发现编译器竟然能够这样赋值,读者最好本身测试一下(不过这里后面发如今c语言里面能够这样操做,在c++里面不能够这样操做,必须强制类型转换)。
枚举元素不是字符常量也不是字符串常量,使用时不要加单、双引号。
枚举类型是一种基本数据类型,而不是一种构造类型,由于它不能再分解为任何基本类型。
枚举值是常量,不是变量。
3、大小端模式:
一、什么是叫大小端模式?
a、什么叫大端模式(big-endian)?
在这种格式中,字数据的高字节存储在低地址中,而字数据的低字节则存放在高地址中。
b、什么叫小端模式(little-endian)?
与大端存储格式相反,在小端存储格式中,低地址中存放的是字数据的低字节,高地址存放的是字数据的高字节。
二、实际解释:
----- 咱们把一个16位的整数0x1234存放到一个短整型变量(short)中。这个短整型变量在内存中的存储在大小端模式由下表所示:
地址偏移 |
大端模式 |
小端模式 |
0x00 |
12 |
34 |
0x01 |
34 |
12 |
说明:
由上表所知,采用大小模式对数据进行存放的主要区别在于在存放的字节顺序,大端方式将高位存放在低地址,小端方式将低位存放在低地址。
三、代码实战来判断大小端模式:
#include <stdio.h>
// 共用体中很重要的一点:a和b都是从u1的低地址开始的。
// 假设u1所在的4字节地址分别是:0、一、二、3的话,那么a天然就是0、一、二、3;
// b所在的地址是0而不是3.
union myunion
{
int a;
char b;
};
// 若是是小端模式则返回1,小端模式则返回0
int is_little_endian(void)
{
union myunion u1;
u1.a = 1; // 地址0的那个字节内是1(小端)或者0(大端)
return u1.b;
}
int is_little_endian2(void)
{
int a = 1;
char b = *((char *)(&a)); // 指针方式其实就是共用体的本质
return b;
}
int main(void)
{
int i = is_little_endian2();
if (i == 1)
{
printf("小端模式\n");
}
else
{
printf("大端模式\n");
}
return 0;
}
演示结果:
这是小端模式
四、看似可行实则不行的测试大小端方式:位与、移位、强制类型转化:
#include <stdio.h>
int main(void)
{
// 强制类型转换
int a;
char b;
a = 1;
b = (char)a;
printf("b = %d.\n", b); // b=1
/*
// 移位
int a, b;
a = 1;
b = a >> 1;
printf("b = %d.\n", b); //b=0
*/
/*
// 位与
int a = 1;
int b = a & 0xff; // 也能够写成:char b
printf("b = %d.\n", b); //b=1
*/
return 0;
}
说明:
(1)位与运算:
结论:位与的方式没法测试机器的大小端模式。(表现就是大端机器和小 端机器的&运算后的值相同的)
理论分析:位与运算是编译器提供的运算,这个运算是高于内存层次的(或者说&运算在二进制层次具备可移植性,也就是说&的时候必定是高字节&高字节,低字节&低字节,和二进制存储无关)。
(2)移位:
结论:移位的方式也不能测试机器大小端。
理论分析:缘由和&运算符不能测试同样,由于C语言对运算符的级别是高于二进制层次的。右移运算永远是将低字节移除,而和二进制存储时这个低字节在高位仍是低位无关的。
(3)强制类型转换和上面分析同样的。
五、通讯系统中的大小端(数组的大小端)
(1)譬如要经过串口发送一个0x12345678给接收方,可是由于串口自己限制,只能以字节为单位来发送,因此须要发4次;接收方分4次接收,内容分别是:0x十二、0x3四、0x5六、0x78.接收方接收到这4个字节以后须要去重组获得0x12345678(而不是获得0x78563412)。
(2)因此在通讯双方须要有一个默契,就是:先发/先接的是高位仍是低位?这就是通讯中的大小端问题。
(3)通常来讲是:先发低字节叫小端;先发高字节就叫大端。在实际操做中,在通讯协议里面会去定义大小端,明确告诉你先发的是低字节仍是高字节。
(4)在通讯协议中,大小端是很是重要的,你们使用别人定义的通讯协议仍是本身要去定义通讯协议,必定都要注意标明通讯协议中大小端的问题。
4、总结:
上面分享了一些咱们经常使用的一些用法,掌握了这些就能够了,当往后工做中有其余用法,再总结概括,完善本身的知识体系。