请先观察以下简短程序:
#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;
}
该程序完全正确。然而,由于 enum class FruitType
理应与 Fruit
类配套使用,将其独立置于类外,只能依靠命名推断二者关联,显得不够清晰。
嵌套类型(成员类型)
迄今为止,我们见过的类类型仅包含两种成员:数据成员与成员函数。上述 Fruit
类即同时拥有两者。
类类型还支持另一种成员:嵌套类型(亦称 成员类型)。创建嵌套类型时,只需在类内部、于合适的访问说明符之下定义该类型即可。
下面将同一程序改写,使用定义在 Fruit
类内部的嵌套类型:
#include <iostream>
class Fruit
{
public:
// 将 FruitType 移入类内,置于 public 访问说明符下
// 并更名为 Type,同时改为非限定枚举(enum)
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; } // 类内可直接使用 cherry,无需前缀
};
int main()
{
// 注意:类外访问枚举值需加 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
置于类顶部。嵌套类型名必须先完整定义方可使用,故通常放在最前。
最佳实践
请始终在类类型顶部定义任何嵌套类型。
- 嵌套类型遵循常规访问控制。
Type
位于public
区域,故外部可直接访问该类型名及其枚举值。 - 类类型如同命名空间,为内部声明的名称提供作用域。因此
Type
的完全限定名为Fruit::Type
,枚举值apple
的完全限定名为Fruit::apple
。- 类内成员无需使用完全限定名;如
isCherry()
中直接使用cherry
。 - 类外必须使用完全限定名,如
Fruit::apple
。
- 类内成员无需使用完全限定名;如
- 我们改用了非限定枚举(
enum
),因为类本身已构成作用域;若仍用enum class
,则需书写更冗长的Fruit::Type::apple
,显得多余。
嵌套 typedef 与类型别名
类类型亦可包含嵌套的 typedef
或类型别名:
#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; } // 类内可直接使用 IDType
};
int main()
{
Employee john { "John", 1, 45000 };
Employee::IDType id { john.getId() }; // 类外需使用完全限定名 Employee::IDType
std::cout << john.getName() << " has id: " << id << '\n';
return 0;
}
输出:
John has id: 1
类内可直接使用 IDType
;类外则必须用 Employee::IDType
。
类型别名的优势见第 10.7 课《typedef 与类型别名》。C++ 标准库中的许多类也大量运用嵌套 typedef;截至本文撰写时,std::string
定义了十个嵌套 typedef。
嵌套类与对外部类成员的访问
类包含另一类作为嵌套类型并不常见,但完全可行。
C++ 中,嵌套类不持有外部类的 this
指针,因此无法直接访问外部类成员——嵌套类可独立于外部类实例化(此时不存在外部类成员)。
然而,嵌套类本身是外部类的成员,故可访问外部类的任何 private
成员,只要这些成员在作用域内可见。
示例:
#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;
}
输出:
John has id: 1
嵌套类更常见的场景在标准库中:大多数迭代器类实现为其所遍历容器的嵌套类。例如 std::string::iterator
即 std::string
的嵌套类。后续章节讨论迭代器。
嵌套类型与前向声明
嵌套类型可在其外部类内前向声明,随后再定义,可位于外部类内,也可位于外部类外:
#include <iostream>
class outer
{
public:
class inner1; // 允许:在外部类内前向声明
class inner1 {}; // 允许:在外部类内定义先前前向声明的类型
class inner2; // 允许:在外部类内前向声明
};
class inner2 // 允许:在外部类外定义先前前向声明的类型
{
};
int main()
{
return 0;
}
然而,不可在外部类定义之前前向声明其嵌套类型:
#include <iostream>
class outer; // 允许:可前向声明非嵌套类型
class outer::inner1; // 错误:外部类尚未定义,无法前向声明其嵌套类型
class outer
{
public:
class inner1 {}; // 嵌套类型在此声明
};
class outer::inner1; // 允许(但冗余),因嵌套类型已随外部类声明
若在外部类定义之后前向声明其嵌套类型,也因外部类已包含嵌套类型声明而变得多余。