嵌套类型(成员类型)
本节阅读量:
考虑以下简短的程序:
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
29
30
31
32
33
34
35
36
37
38
39
|
#include <iostream>
enum class FruitType
{
apple,
banana,
cherry
};
class Fruit
{
private:
FruitType m_type { };
int m_percentageEaten { 0 };
public:
Fruit(FruitType type) :
m_type { type }
{
}
FruitType getType() { return m_type; }
int getPercentageEaten() { return m_percentageEaten; }
bool isCherry() { return m_type == FruitType::cherry; }
};
int main()
{
Fruit apple { FruitType::apple };
if (apple.getType() == FruitType::apple)
std::cout << "I am an apple";
else
std::cout << "I am not an apple";
return 0;
}
|
这个程序没有问题。但由于枚举类FruitType旨在与Fruit类一起使用,但它独立于类存在,这让我们不得不记住他们的关联。
嵌套类型(成员类型)
到目前为止,我们已经看到了类类型的两种不同成员:数据成员和成员函数。在上面的例子中,Fruit类同时具有这两个特性。
类类型支持另一种类型的成员:嵌套类型(也称为成员类型)。要创建嵌套类型,只需在类内的适当访问说明符下定义类型。
下面是与上面相同的程序,重写为使用在Fruit类中定义的嵌套类型:
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
#include <iostream>
class Fruit
{
public:
// FruitType 移动到了 Fruit 内部, 在 public 下面
// 同时重命名为Type,并定义为 enum 而不是 enum class
enum Type
{
apple,
banana,
cherry
};
private:
Type m_type {};
int m_percentageEaten { 0 };
public:
Fruit(Type type) :
m_type { type }
{
}
Type getType() { return m_type; }
int getPercentageEaten() { return m_percentageEaten; }
bool isCherry() { return m_type == cherry; } // 在 Fruit 内, 不需要使用 FruitType:: 前缀
};
int main()
{
// 注: 在 class 外部, 使用 Fruit:: 前缀访问
Fruit apple { Fruit::apple };
if (apple.getType() == Fruit::apple)
std::cout << "I am an apple";
else
std::cout << "I am not an apple";
return 0;
}
|
这里有几点值得指出。
首先,请注意,FruitType现在在类中定义,由于稍后将讨论的原因,它已被重命名为Type。
其次,在类的顶部定义了嵌套类型Type。嵌套类型名称在使用之前必须完全定义,因此通常首先定义它们。
第三,嵌套类型遵循正常的访问规则。类型是在public访问说明符下定义的,因此类型名称和枚举元素可以由外部直接访问。
第四,类类型充当中声明的名称的作用域,就像命名空间一样。因此,Type的完全限定名为Fruit::Type,而apple枚举元素的完全限定名称为Fruit::apple。
在类的成员中,不需要使用完全限定名。例如,在成员函数 isCherry() 中,在没有Fruit:: 作用域限定符的情况下访问cherry枚举元素。
在类之外,必须使用完全限定的名称(例如,Fruit::apple )。我们将FruitType重命名为Type,以便可以使用 Fruit::Type(而不是更冗余的 Fruit::FruitType )。
最后,将枚举类型从 enum class 更改为 enum。由于类本身现在充当作用域,因此使用 enum class 有些多余。更改为未限定作用域的枚举意味着可以用Fruit::apple的形式访问枚举元素,而不是必须使用的较长的 Fruit::Type::apple 。
嵌套的typedef和类型别名
类类型还可以包含嵌套的typedef或类型别名:
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
29
30
31
32
33
34
35
|
#include <iostream>
#include <string>
#include <string_view>
class Employee
{
public:
using IDType = int;
private:
std::string m_name{};
IDType m_id{};
double m_wage{};
public:
Employee(std::string_view name, IDType id, double wage)
: m_name { name }
, m_id { id }
, m_wage { wage }
{
}
const std::string& getName() { return m_name; }
IDType getId() { return m_id; } // 类内部可以使用不带限定名的类型
};
int main()
{
Employee john { "John", 1, 45000 };
Employee::IDType id { john.getId() }; // 类外部必须使用完全限定名
std::cout << john.getName() << " has id: " << id << '\n';
return 0;
}
|
这将打印:
请注意,在类内部,可以直接使用IDType,但在类外部,必须使用完全限定名Employee::IDType。
我们之前讨论过Typedef和类型别名,它们在这里的作用是相同的。C++标准库中的类通常使用嵌套的typedef。截至编写时,std::string定义了十个嵌套的typedef!
嵌套类和对外部类成员的访问
类将其他类作为嵌套类型是相当少见的,但这是可行的。在C++中,嵌套类不能访问外部类的this指针,嵌套类也不能直接访问外部类的成员。这是因为嵌套类可以独立于外部类进行实例化(在这种情况下,将没有可访问的外部类实例和成员!)
然而,由于嵌套类是外部类的成员,因此它们可以访问外部类的任何私有成员。
用一个例子来说明:
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
#include <iostream>
#include <string>
#include <string_view>
class Employee
{
public:
using IDType = int;
class Printer
{
public:
void print(const Employee& e) const
{
// Printer 不能使用 Employee 的 `this` 指针
// 所以不能直接使用 m_name 和 m_id
// 代替的方案是, 可以传入一个 Employee 对象
// 因为 Printer 是 Employee 的成员,
// 所以可以直接访问私有成员 e.m_name 和 e.m_id
std::cout << e.m_name << " has id: " << e.m_id << '\n';
}
};
private:
std::string m_name{};
IDType m_id{};
double m_wage{};
public:
Employee(std::string_view name, IDType id, double wage)
: m_name{ name }
, m_id{ id }
, m_wage{ wage }
{
}
// 这个例子中不提供访问函数
};
int main()
{
const Employee john{ "John", 1, 45000 };
const Employee::Printer p{}; // 实例化嵌套类的实例
p.print(john);
return 0;
}
|
这将打印:
有一种情况下,嵌套类更常用。在标准库中,大多数迭代器类都被实现为容器的嵌套类,它们被设计为在容器上迭代。例如,std::string::iterator被实现为std:∶string的嵌套类。我们将在未来的一章中介绍迭代器。
嵌套类型不能前向声明
嵌套类型还有一个值得一提的限制——嵌套类型不能被前向声明。在C++的未来版本中,可能取消此限制。