216 lines
6.0 KiB
Markdown
216 lines
6.0 KiB
Markdown
# 2.6 结构体与联合体
|
||
|
||
## 2.6.1 结构体
|
||
|
||
数组中保存的是一组相同类型的数据,而结构体中可以保存一组不同类型的数据,他是一类数据的聚合。
|
||
|
||
```cpp
|
||
// 元素 A 和 B 占用不同存储空间.
|
||
struct _A_STRUCT
|
||
{
|
||
short A; // 内部元素 A.
|
||
char B; // 元素 B.
|
||
int C; // 元素 C.
|
||
}AStruct = {15, 8, 32};
|
||
|
||
struct _A_STRUCT* pAStruct = &AStruct; // pAStruct 是一个结构体指针.
|
||
|
||
printf("%d\n", sizeof(AStruct)); // AUnion 的长度为 8(涉及到内存对齐).
|
||
printf("A=%d\n", AStruct.A); // 输出 15.
|
||
printf("B=%d\n", pAStruct->B); // 输出 8.
|
||
```
|
||
|
||
结构体变量使用 “.” 来访问内部元素,而结构体指针使用 “->” 来访问内部元素。
|
||
|
||
我们看到,上面定义结构体指针的方法非常繁琐,我们有更简单的方式:
|
||
|
||
```cpp
|
||
typedef struct _A_STRUCT
|
||
{
|
||
short A;
|
||
char B;
|
||
int C;
|
||
}A_STRUCT; // 通过 typedef 将 A_STRUCT 定义成了 struct _A_STRUCT 类型.
|
||
|
||
A_STRUCT AStruct = {15, 8, 32}; // 这样用起来就方便了.
|
||
A_STRUCT* pAStruct = &AStruct; // 嗯,确实方便多了.
|
||
```
|
||
|
||
## 2.6.2 联合体
|
||
|
||
联合体则为同一地址空间起了两或多个名字。
|
||
|
||
```cpp
|
||
// 元素 A 和 B 占用同一存储空间.
|
||
union _A_UNION
|
||
{
|
||
short A;
|
||
char B;
|
||
}AUnion;
|
||
|
||
printf("%d\n", sizeof(AUnion)); // AUnion 的长度为 2.
|
||
```
|
||
|
||
访问联合体内部元素的方法与结构体类似。
|
||
|
||
联合体也支持 typedef,方法与结构体一致。
|
||
|
||
## 2.6.3 结构体、联合体混合使用
|
||
|
||
结构体中可以嵌套联合体:
|
||
|
||
```cpp
|
||
typedef struct _SuperHero
|
||
{
|
||
char* name;
|
||
char* actor;
|
||
union
|
||
{
|
||
unsigned short ShieldLevel;
|
||
unsigned short IronLevel;
|
||
}; // 结构体中允许使用匿名联合体.
|
||
}SuperHero;
|
||
|
||
void InitSuperHero(SuperHero* hero)
|
||
{
|
||
if(0==strcmp("Captain America", hero->name))
|
||
{
|
||
hero->ShieldLevel = 3;
|
||
}
|
||
else if(0==strcmp("Iron Man", hero->name))
|
||
{
|
||
hero->IronLevel = 4;
|
||
}
|
||
}
|
||
```
|
||
|
||
除了结构体嵌套联合体外,还允许结构体嵌套结构体、联合体嵌套联合体、联合体嵌套结构体等,由此可以组合成复杂的数据结构。
|
||
|
||
## 2.6.4 结构体、联合体、数组混合使用
|
||
|
||
可以创建结构体数组,联合体数组,或更加复杂数据结构类型的数组。
|
||
|
||
```cpp
|
||
typedef struct _SuperHero
|
||
{
|
||
char* name;
|
||
char* actor;
|
||
union
|
||
{
|
||
unsigned short ShieldLevel;
|
||
unsigned short IronLevel;
|
||
}; // 结构体中允许使用匿名联合体.
|
||
}SuperHero;
|
||
|
||
SuperHero MarvelHeros[8000]; // 听说漫威有 7000 多个超级英雄,多余空位预留吧!
|
||
|
||
MarvelHeros[0].name = "Black Widow";
|
||
MarvelHeros->actor = "Scarlett Johansson";
|
||
```
|
||
|
||
## 2.6.5 结构体与内存对齐
|
||
|
||
计算机有一个特点,如果访问的内存地址是自然对齐的(由计算机地址总线位宽决定),那么访问速度就比较快。如果不是自然对齐的,访问速度就会慢一些。基于此特性,编译器会对我们的程序进行一些优化,使得没有特殊的情况下,变量总是自然对齐。
|
||
|
||
编译器的这个特性造成了一些奇怪的现象。
|
||
|
||
```cpp
|
||
struct
|
||
{
|
||
char a;
|
||
char b;
|
||
}sta;
|
||
|
||
struct
|
||
{
|
||
char a;
|
||
short b;
|
||
char c;
|
||
}stb;
|
||
|
||
struct
|
||
{
|
||
char a;
|
||
short b;
|
||
char c;
|
||
int d;
|
||
}stc;
|
||
|
||
printf("%d\n", sizeof(sta)); // 输出 2.
|
||
printf("%d\n", sizeof(stb)); // 输出 6.
|
||
printf("%d\n", sizeof(stc)); // 输出 12.
|
||
```
|
||
|
||
由于 stb 中元素 b 需要对齐到 2 倍数地址上,所以在元素 a 和元素 b 之间需要空出一个字节。另外,由于结构体自身对齐值为其元素中最大的那个,造成 stb 也需要对齐到 2 倍数的地址上,因此其最后一个元素 c 后面也要保留一个字节。这对于结构体 stc 来说也是一样的。
|
||
|
||
某些情况下需要取消默认对齐,通常是要求结构体按字节对齐,方法如下:
|
||
|
||
```cpp
|
||
#pragma pack(push, 1)
|
||
|
||
struct
|
||
{
|
||
char a;
|
||
short b;
|
||
char c;
|
||
int d;
|
||
}stc;
|
||
|
||
#pragma pack(pop)
|
||
|
||
printf("%d\n", sizeof(stc)); // 输出 8.
|
||
|
||
```
|
||
|
||
需要注意的是,不同编译器下,修改对齐的方法可能不同。
|
||
|
||
总结一下结构体对齐的原则:
|
||
|
||
* 数据类型自身的对齐值:char型数据自身对齐值为1字节,short型数据为2字节,int/float型为4字节,double型为8字节。
|
||
|
||
* 结构体或类的自身对齐值:其成员中自身对齐值最大的那个值。
|
||
|
||
* 指定对齐值:#pragma pack (value)时的指定对齐值value。
|
||
|
||
* 数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中较小者。
|
||
|
||
## 2.6.6 位域
|
||
|
||
在结构体中,有一种特别的进行位操作的方法:
|
||
|
||
```cpp
|
||
struct
|
||
{
|
||
char bit0:1;
|
||
char bit1:3;
|
||
char bit2:4;
|
||
char bit3:2;
|
||
}reg;
|
||
|
||
|
||
printf("%d\n", sizeof(reg)); // 输出 2.
|
||
```
|
||
|
||
其中,bit0 占 1 位,bit1 占 3 位,bit2 占 4 位,bit3 占 2 位。从 bit0 到 bit2 共占据一个字节空间,bit3要占据一个字节的两位,其余位空置,因此结构体变量 reg 的长度为 2。位域比较适合应用在嵌入式系统中描述寄存器,因为嵌入式系统中,很多设备的寄存器功能都是按位划分的。
|
||
|
||
## 练习
|
||
|
||
一个 RS485 通讯报文如下:
|
||
|
||
| Byte Offset | Description |
|
||
|-------------|--------------|
|
||
| 0 | CMD ID |
|
||
| 1 | Fun/Dir/Type |
|
||
| 2 | Dst Addr |
|
||
| 3 | Data[0] |
|
||
| 4 | Data[1] |
|
||
| 5 | Data[2] |
|
||
| 6 | Data[3] |
|
||
| 7 | Data[4] |
|
||
| 8 | Data[5] |
|
||
| 9 | Data[6] |
|
||
| 10 | Data[7] |
|
||
| 11 | CRC L |
|
||
| 12 | CRC H |
|
||
|
||
当 CMD ID 为 0 时,Fun 功能有效;当 CMD ID 为 1 时,Dir 功能有效;当 CMD ID 为 2 时,Type 功能有效(Type 的 0 位代表使能,1位表示读/写,2位表示多功能设备)。请根据报文结构实现 RS485 数据发送程序。 |