C++ 学习之七、C++ 复合类型之结构、共用体、枚举

C++ 的结构、共用体、枚举等有一些共同之处,也有很多差别。

结构 struct

结构概念

数组是存储一组相同类型的数据,如果想在一个对象中存储多种不同类型的数据呢,比如想同时存储一个人的姓名,年龄,身高,体重。

结构是一种比数组更灵活的数据格式,因为同一个结构可以存储多种类型的数据。结构也是 C++ OOP (类) 的基石。

结构声明

结构声明包括两步。首先,定义结构描述——它描述并标记了能够存储在结构中的各种数据类型。然后按描述创建结构变量(结构数据对象)。

结构声明放置的位置决定作用范围,使用与基本数据类型的变量一致。C++ 不提倡使用外部变量,但提倡使用外部结构声明。另外,C++ 允许在声明结构时省略关键字 struct

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// StructDemo.cpp

#include <iostream>
struct person // 外部声明 person 结构,其它函数中都可以使用,如果是某个函数内部声明,则只能该函数内部使用
{
char name[20]; // 成员 name
bool sex; // 性别,true 表示男性
int age; // 成员 age
float height; // 成员 height: m
float weight; // 成员 weight: kg
}; // 注意结构声明中每个成员都以分号隔开,最后的大括号后面也要有分号

int main()
{
using namespace std;
person xiaoming =
{
"Xiao Ming",
true,
30,
1.70,
68
}; // 结构初始化时,各成员初始化使用逗号隔开
// 结构变量使用成员运算符(.)来访问各个成员
cout << xiaoming.name << " is " << xiaoming.age << " years old." << endl;
cout << "And he is " << xiaoming.height << "m, " << xiaoming.weight << "kg." << endl;
return 0;
}

结构类型变量初始化

定义结构后,就可以像基本数据类型变量一样使用结构。

1
2
3
4
5
6
7
8
9
10
11
12
person teacher = 
{ // 一般初始化方式,成员之间使用 , 分隔
"Xiao Zhang", // name
false,
50, // age
1.68, // height
68 // weight,最后一个数据可以不使用 , 分隔
}; // 初始化最后大括号依然使用分号,因为这是一条完整的语句

person teacher {"Xiao Zhang", false, 50, 1.68, 68}; // C++11 列表初始化方式,可以省略 = 号

person teacher {}; // C++11 列表初始化方式,大括号内未包含任何内容时,各个成员都将被设置为零,name 每个字节都将被设置为 0

可以在声明结构的同时定义出该类型的变量,或直接初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct book
{
int pages;
char bookName[20];
} book1, book2; // 直接定义 book1 book2 为 book 类型的变量

struct book
{
int pages;
char bookName[20];
} book3 = // 定义出 book 类型变量 book3 并直接初始化
{
200,
"Driving"
};

还可以声明没有名称的结构类型,即匿名结构,方法是省略名称,同时定义一种结构类型和一个该类型的变量:

1
2
3
4
5
struct  // 没有名称的结构,匿名结构
{
int x;
int y;
} position; // 结构变量,这种声明方法最终只能使用直接定义的变量,后面无法创建该结构的新变量

结构数组

如上述例子中,结构中可以包含数组(如 name[20]),也可创建元素为结构的数组,方法和创建基本类型数组一样。

1
2
3
4
5
6
7
8
// 如创建 person 结构数组
person teacher[3] =
{
{"Xiao Hong", false, 20, 1.50, 45},
{"Xiao Wang", true, 25, 1.80, 70.2},
{"Xiao Cheng", false, 18, 1.50, 43}
};
cout << teracher[0].name << endl; // 像使用正常数组一样使用结构数组

结构中的位字段

与 C 语言一样,C++ 也允许指定占用特定位数的结构成员,这使得创建与某个硬件设备上的寄存器对应的数据结构非常方便。字段的类型应为整数或枚举,接下来是冒号,冒号后面是一个数字,它指定了使用的位数。可以使用没有名称的字段来提供间距。每个成员都被称为位字段(bit field)。下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
struct torgle_register
{
unsigned int SN : 4; // 4 bits for SN value
unsigned int : 4; // 4 bits 未使用数据,用来占位提供间距
bool goodIn : 1; // valid input (1 bit)
bool goodTorgle: 1; // success ful torgling
};
// 可以像通常那样初始化这些字段,还可以使用标准的结构表示法来访问位字段:
torgle_register tr = { 14, true, false }; // 不用在意未命名的占位字段
...
if (tr.goodIN) // 获得成员来进行判断
...

共用体 union

共用体(union)是一种数据格式,它能够存储不同的数据类型,但只能同时存储其中的一种类型。共用体的句法和结构相似,但含义不同。共用体的用途之一是,当数据使用两种或更多格式(但不会同时使用)时,可节省空间。另外,共用体常用于操作系统数据结构或硬件数据结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 文件名 UnionDemo.cpp
#include <iostream>

struct Goods // 定义 Goods 结构
{
bool type; // 结构成员 type
float weight; // 结构成员 weight
float height; // 结构成员 height
union id // 结构成员 id 是一个共用体,只能存储定义中的其中一种类型
{
double d_id;
int int_id;
} id_var; // struct 内嵌套 union 使用时,union 需要直接定义出共用体类型的变量,否则无法正常使用
};

int main()
{
Goods first;
first.type = true;
first.weight = 1.3;
first.height = 0.5;
first.id_var.d_id = 1.6888; // 赋值 d_id,则此 union 内存中无 int_id
std::cout << "ID is " << first.id_var.d_id << std::endl;
}

当共用体中一个成员被赋值后,其它成员将像不存在一样。由于共用体每次只能存储一个值,因此它必须有足够的空间来存储最大的成员,所以共用体的长度为其最大成员的长度。

union 还有一种匿名类型,像匿名结构一样不需要名字,此时不需要定义该变量。此时由于共用体是匿名的,因此共用体的成员将被视为外部成员,它们的地址相同,所以不需要变量来标识。程序员负责确定当前哪个成员是活动的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct widget
{
char brand[20];
int type;
union // 匿名共用体
{
long id_num;
char id_char[20];
};
};
...
widget prize;
...
// id_num 和 id_char 被视为 prize 的两个成员,id_num 和 id_char 地址相同
if (prize.type == 1)
cin >> prize.id_num;
else
cin >> prize.id_char;

枚举 enum

C++ 的枚举(enum)工具提供了另一种创建符号常量的方式,这种方式可以代替 const。它还允许定义新类型,但必须按严格的限制进行。使用 enum 的句法与使用结构相似。

1
2
3
4
enum spectrum
{
red, orange, yellow, green, blue, violet, indigo, ultraviolet
};

spectrum 被称为枚举。大括号内将作为符号常量,它们对应的整数值 0 ~ 7。这些常量叫作枚举量(enumerator)。

枚举的特性

  • 默认情况下,将整数值赋给枚举量,第一个值为 0,第二个值为 1,依此类推。如上例中 red 值为 0,orange 值为 1。

  • 在不进行强制转换的情况下,只能将定义的枚举量赋值给相应的枚举变量,如

    1
    2
    3
    spectrum band;  // 定义 spectrum 类型的枚举变量 band
    band = blue; // 合法,band 变量的值只能为定义的枚举量
    band = 2000; // 非法,2000 不是定义的枚举量,不能赋给枚举变量
  • 枚举量是整型,可被提升为 int 类型,但 int 类型不能自动转换为枚举类型。

    1
    2
    3
    int color = orange; // 合法,枚举量可提升为整型
    band = 3; // 非法,int 类型不能自动转换为枚举类型
    color = 3 + red; // 合法,red 提升为 int 类型

    但是在枚举量的取值范围内的时候,int 类型可以强制转换为枚举量。如 band = spectrum(3)

枚举量的值

可以使用赋值运算符来显式地设置枚举量的值:

enum bits{one = 1, two = 2, four = 4, eight = 8};

指定的值必须是整数。也可以只显式地定义其中一些枚举量的值:

enum bigstep{first, second = 100, third};

这里,first 在默认情况下为 0。后面没有被初始化的枚举量的值将比其前面的枚举量大 1。因此 third 的值为 101。

最后,可以创建多个值相同的枚举量:

enum step{zero, null = 0, one, numro_uno = 1}

其中 zeronull 都为 0,onenumro_uno 都为 1。

枚举的取值范围

可以将在枚举取值范围内的整型强制转换为枚举量。

  • 上限。大于最大枚举量的、最小的 2 的幂,将它减去 1。如枚举量中最大值为 11,则此枚举上限为 15
  • 下限。如果枚举量最小值不小于 0,则取值范围的下限为 0,否则,采用与寻找上限方式相同的方法,但加上负号。如,假如最小的枚举量为 -6,而比它小的、最大的 2 的幂是 -8(加上负号),因此下限为 -7。