考虑下面程序:
#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;
}
目前为止,我们已经介绍过具有两种不同类型成员的类类型:
- 数据成员
- 成员函数
类类型还支持另一种成员,那就是 嵌套类型(也称为成员类型)。创建嵌套类型很简单,只需要在类内部选择一个合适的访问修饰符下进行类型定义即可。下面的例子在功能上和上面的是一样的,但是它采用了嵌套类型的定义方式:
class Fruit
{
public:
// FruitType 已经被移动到类内部,并放置在 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; } // 获取被吃掉的百分比
// 在 Fruit 的成员函数内部,我们不再需要使用 Fruit::Type:: 前缀
bool isCherry() { return m_type == 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;
}
- 嵌套类型
Type
已在类的顶部定义。嵌套类型名称必须在使用之前完全定义,因此通常先定义它们。 - 嵌套类型遵循正常的访问规则。
Type
是在public
修饰符下定义的,这样类型名称和枚举就可以被公开访问。 - 类类型充当在其中声明的名称的作用域,就像命名空间一样。因此,
Type
的全限定名是Fruit::Type
,apple
的完全限定名是Fruit::apple
- 在类的成员中,我们不需要使用完全限定名。例如,在成员函数
isCherry()
中,我们访问cherry
枚举器而不使用Fruit::cherry
限定符。 - 但在类外部,我们必须使用完全限定名(例如
Fruit::apple
)。我们将FruitType
重命名为Type
,这样我们就可以以Fruit::Type 的形式
访问它(而不是更冗余的Fruit::FruitType
)。
类类型也可以包含嵌套的typedef
或类型别名:
#include <iostream>
#include <string>
#include <string_view>
class Employee
{
public:
using IDType = int; // 定义 IDType 类型别名
private:
std::string m_name{}; // 员工姓名
IDType m_id{}; // 员工 ID
double m_wage{}; // 员工工资
public:
// 构造函数,初始化员工的姓名、ID 和工资
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 对象
Employee::IDType id { john.getId() }; // 在类外部,必须使用完全限定名称 Employee::IDType
std::cout << john.getName() << " has id: " << id << '\n';
return 0;
}
C++标准库中的类使用嵌套类型定义是非常常见的。
嵌套类和外部类的访问关系
类将其他类作为嵌套类型是相当罕见的,但这不代表不可能(Java过来的兄弟就知道内部类在Java中的位置)。
在 C++中,嵌套类不能访问外部(包含)类的 this
指针,因此嵌套类不能直接访问外部类的成员。这是因为嵌套类可以独立于外部类进行实例化(在这种情况下,将没有外部类成员可供访问!)
但是由于嵌套内是外部类的成员,因此他们可以访问范围内内部类的任何私有成员。
class Employee
{
public:
using IDType = int;
class Printer
{
public:
void print(const Employee& e) const
{
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{}; // 实例化内部类 Printer 的对象
p.print(john);
return 0;
}
- Printer 无法访问 Employee 的
this
指针,- 因此我们无法直接打印 m_name 和 m_id。
- 相反,我们必须传递一个 Employee 对象来使用。
- 由于 Printer 是 Employee 的成员类,
- 我们可以直接访问私有成员 e.m_name 和 e.m_id。
在标准库中,嵌套类的使用十分频繁,大多数的迭代器类都是使用这种方式实现的。
嵌套类&前向声明
嵌套类可以在封装它的外部类中进行前向声明。然后可以在外部类内部或者外部第一嵌套类型 :
#include <iostream>
class outer
{
public:
class inner1; // ✅ 允许:在外部类(outer)内部进行前向声明
class inner1{}; // ✅ 允许:在外部类(outer)内部定义之前前向声明的类型
class inner2; // ✅ 允许:在外部类(outer)内部进行前向声明
};
// ✅ 允许:在外部类(outer)外部定义之前前向声明的 inner2
class outer::inner2
{
};
int main()
{
return 0;
}
但是,嵌套类型不能在定义外部类之前进行前向声明:
#include <iostream>
class outer; // ✅ 允许:可以对非嵌套类型(外部类)进行前向声明
class outer::inner1; // ❌ 错误:在定义 outer 之前,不能前向声明它的嵌套类型 inner1
class outer
{
public:
class inner1{}; // ℹ️ 注意:嵌套类型 inner1 在这里被声明和定义
};
// ✅ 允许(但多余):由于 inner1 已经作为 outer 类的一部分被声明,这里再次前向声明是多余的
class outer::inner1;
int main()
{
return 0;
}
说白了就是 必须先定义外部类(封闭类),然后才能声明或定义它的嵌套类。