嵌套类
一个类可以定义在另一个类的内部,前者称为嵌套类或嵌套类型。
下面是一个使用C++嵌套类的示例:
#include <iostream>
class OuterClass {
public:
class InnerClass {
public:
void printMessage() {
std::cout << "Hello from InnerClass!" << std::endl;
}
};
void callInnerClass() {
InnerClass inner;
inner.printMessage();
}
};
int main() {
OuterClass outer;
outer.callInnerClass();
return 0;
}
在上面的代码中,OuterClass
有一个嵌套类InnerClass
。在OuterClass
的callInnerClass()
函数中,我们创建了InnerClass
的一个对象,并调用了它的printMessage()
函数。最终的输出结果将会是"Hello from InnerClass!"。
嵌套类常用于定义作为实现部分的类
- 嵌套类是一个独立的类,与外层类基本没什么关系。
- 特别是,外层类的对象和嵌套类的对象是相互独立的。
- 在嵌套类的对象中不包含任何外层类定义的成员;
- 类似的,在外层类的对象中也不包含任何嵌套类定义的成员。
嵌套类的名字在外层类作用域中是可见的,在外层类作用域之外不可见。
和其他嵌套类的名字不会和别的作用域中的同一个名字冲突。
嵌套类中成员的种类与非嵌套类是一样的。
和其他类类似,嵌套类也使用访问限定符的名字一样,来控制外界对其成员的访问权限。
外层类对嵌套类的成员没有特殊的访问权限,同样,嵌套类对外层类的成员也没有特殊的访问权限。
嵌套类在其外层类中定义了一个类型成员。和其他成员类似,该类型的访问权限由外层类决定。
- 位于外层类public部分的嵌套类实际上定义了一种可以随处访问的类型;
- 位于外层类protected部分的嵌套类定义的类型只能被外层类及其友元和派生类访问:
- 位于外层类private部分的嵌套类定义的类型只能被外层类的成员和友元访问。
声明一个嵌套类
我们为TextQuery类定义了一个名为QueryResult的嵌套类,这两个类密切相关。QueryResult类的主要作用是表示TextQuery对象上query操作的结果,显然将QueryResult用作其他目的没有任何意义。为了充分体现这种紧密的相关性,我们可以把QueryResult定义成TextQuery的成员。
class TextQuery {
public:
class QueryResult; //嵌套类稍后定义
//...
};
我们只需对原来的TextQuery类做一处改动,即将QueryResult声明成嵌套类。
因为QueryResult是一个类型成员,所以我们必须对它先声明后使用,尤其是必须先声明QueryResult,再将它作为query成员的返回类型。类的其他成员没有任何变化。
在外层类之外定义一个嵌套类
我们在TextQuery内声明了QueryResult,但是没有给出它的定义。和成员函数一样,嵌套类必须声明在类的内部,但是可以定义在类的内部或者外部。
当我们在外层类之外定义一个嵌套类时,必须以外层类的名字限定嵌套类的名字;
// QueryResult是TextQuery的成员,下面的代码负责定义QueryResult
class TextQuery::QueryResult {
//位于类的作用域内,因此我们不必对QueryResult形参进行限定
friend std::ostream& print(std::ostream&, const QueryResult&);
public:
//无须定义 QueryResult::line_no
// 嵌套类可以直接使用外层类的成员,无须对该成员的名字进行限定
QueryResult(std::string,
std::shared ptr<std::set<line_no>>,
std::shared_ptr<std::vector<std::string>>);
//....
);
和原来的类相比唯一的改动是,我们无须在QueryResult内定义line_no成员了。因为该成员属于TextQuery,所以QueryResult可以直接访问它而不必再定义一次。
在嵌套类在其外层类之外完成真正的定义之前,它都是一个不完全类型
定义嵌套类的成员
在这个版本的QueryResult类中,我们并没有在类的内部定义其构造函数。
要想为其定义构造函数,必须指明QueryResult是嵌套在TextQuery的作用域之内的。具体做法是使用外层类的名字限定联套类的名字:
// QueryResult 类嵌套在 TextQuery 类中
// 下面的代码为QueryResult 类定义名为QueryResult的成员
TextQuery::QueryResult::QueryResult (string s,
shared ptr<set<line no>> p
shared ptr<vector<string>> f):
sought (s), lines(p), file(f) {}
从右向左阅读函数的名字可知我们定义的是QueryResult类的构造函数,而QueryResult类是嵌套在TextQuery类中的。该构造函数除了把实参值赋给对应的数据成员之外,没有做其他工作。
嵌套类的静态成员定义
如果QueryResult声明了一个静态成员,则该成员的定义将位于TextQuery的作用域之外。
例如,假设QueryResult有一个静态成员,则该成员的定义将形如:
// QueryResult类嵌套在TextQuery类中,
// 下面的代码为QueryResult定义一个静态成员
int TextQuery::QueryResult::static_mem= 1024;
嵌套类作用域中的名字查找
名字查找的一般规则在嵌套类中同样适用。
当然,因为嵌套类本身是一个嵌套作用域,所以还必须查找嵌套类的外层作用域。
这种作用域嵌套的性质正好可以说明为什么我们不在QueryResult的嵌套版本中定义line_no。
原来的QueryResult类定义了该成员,从而使其成员可以避免使用TextQuery::line_no的形式。
然而QueryResult的嵌套类版本本身就是定义在TextQuery中的,所以我们不需要再使用 typedef。嵌套的QueryResult无须说明line_no属于TextQuery就可以直接使用它。
如我们所知,嵌套类是其外层类的一个类型成员,因此外层类的成员可以像使用任何其他类型成员一样使用嵌套类的名字。
因为QueryResult嵌套在TextQuery中,所以TextQuery的query成员可以直接使用名字QueryResult:
//返回类型必须指明QueryResult是一个嵌套类
TextQuery::QueryResult
TextQuery::query(const string &sought) const
// 如果我们没有找到 sought,则返回set的指针
static shared ptr<set<line no>> nodata (new set<line_no>);
// 使用 find而非下标以避免向 wm 中添加单词
auto loc wn. find(sought);
if (loc =s wm,end())
{
return QueryResult(sought, nodata, file); // 没有找到
else
return QueryResult(sought, loc->second, file);
}
和过去一样,返回类型不在类的作用域中,因此我们必须指明函数的返回TextQuery::QueryResult类型。
不过在函数体内部我们可以直接访问QueryResult,比如上面的return语句就是这样。
嵌套类和外层类是相互独立的
尽管嵌套类定义在其外层类的作用域中,但是读者必须谨记外层类的对象和嵌套类的对象没有任何关系。
嵌套类的对象只包含嵌套类定义的成员;同样,外层类的对象只包含外层类定义的成员,在外层类对象中不会有任何嵌套类的成员。
说得再具体一些,TextQuery::query的第二条return语句
return QueryResult(sought, loc->second, file);
使用了TextQuery对象的数据成员,而query正是用它们来初始化QueryResult对象的。因为在一个QueryResult对象中不包含其外层类的成员,所以我们必须使用上述成员构造我们返回QueryResult对象。