每个类都会定义自己的作用域,在类的作用域之外,普通数据和函数只能由对象、引用、指针或者成员访问符来访问。
一个类就是一个作用域的事实很好地解释了为什么类外定义成员函数时必须提供类名和函数名。在类的外部,成员的名字被隐藏起来了。
一旦遇到类名,定义的剩余部分就在类的作用域之内,这里的剩余部分包含参数列表和函数体,结果就是可以直接使用类的其它成员而无须再次授权了。
void Window_mgr::clear(ScreenIndex i)
{
Screen &S = screen[i];
s.contents = string(s.height*s.width,' ');
}
编译器在处理参数列表之前已经明确了当前正位于 Window_mgr 类的作用域之内,所以在参数列表和函数体内无须再声明类作用域。
但是,如果如果函数的返回类型是位于累的作用域之外,因为返回类型位于函数名之前,所以,此时需要显示的指明它属于哪个类。
Window_mgr::ScreenIndx Window_mgr::addScreen(const Screen &S)
{
screens.push_back(s);
}
名字查找与类的作用域
名字查找是寻找与所用名字最匹配的声明的过程:
- 首先,在名字所在的块中寻找声明语句,只考虑在名字的使用之前的出现的声明。
- 如果没有找到,继续查找外层作用域。
- 如果最终没有找到匹配的名字,则程序报错。
类的定义分两步:
- 首先编译成员的声明。
- 直到类全部可见后才编译函数体。
也就是说编译器处理完类中全部声明之后才会处理成员函数的定义。
因为成员函数体直到整个类可见后才会被处理,所以它能使用类中定义的任何名字。
用于类成员声明的名字查找
两阶段的处理方式只适用于成员函数中使用的名字。如果某个成员的声明使用了类中尚未出现的名字,则编译器将会在定义该类的作用域中继续查找 。
typedef double Money;
string bal;
class Account
{
public:
Money balance() { return bal; }
private:
Money bal;
};
编译器看到 balance
函数的声明时,会在 Account
类的范围内查找对应的 Money
声明,没有中找到,继续在外层作用域中查找,将会找到 Money
是定义的类型别名。
Money
被用作数据成员 bal
的定义,所以 balance 函数返回的 bal
是 Account
类的成员而不会是外层作用域的 string
对象。
类型名需要特殊处理
一般来说,内层作用域可以重新定义外层作用域的名字,即使该名字已经在内层作用域中使用过。
但是在类中,如果成员使用外层作用域中的某个名字,而该名字代表了一种类型,则该类不能在之后重新定义该名字。
typedef double Money;
class Account
{
public:
Money balance() { return bal; }
private:
typedef double Money; //错误,不能重新定义 Money,即使与外层定义的一致,也是错误
Money bal;
};
注意:
- 重新定义类型名字是一种错误的行为,但是编译器不对此负责,一些编译器可以顺利通过这些代码。
- 类型名一般定义在类的开始处,这样能够保证所有使用该类型的成员都出现在该类名定义之后。
成员定义中的普通块作用域的名字查找
成员函数中使用的名字按照如下方式解析:
- 首先。在成员函数内查找该名字的声明。只有在函数使用之前出现的声明才被考虑。
- 如果在成员函数中没有找到,则在类中继续查找,这时类的所有成员都可以被考虑。
- 如果类内没有找到名字,在成员函数定义之前的作用域内继续查找。
int height;
class Screen
{
public:
typedef std::string::size_type pos;
void dummy_fcn(pos height){
cursor = width * height;
}
private:
pos cursor = 0;
pos height = 0, width = 0;
};
编译器处理 dummy_fcn 函数时,函数的参数位于函数作用域内,因此 dummy_fcn 函数体内用到的名字 height 指的是参数声明。
此例中,height 参数同时隐藏了同名的成员。如果想绕开上面的查找规则,应该将代码变为:
void Screen::dummy_fcn(pos height){
cursor = width * this->height; //成员height
//或者
cursor = width * Screen::height; //成员height
}
尽管类的成员被隐藏了,但仍然可以使用类的名字或显示的使用 this
指针来强制访问成员。
但其实最好的方法是在成员函数的参数列表中不使用类中出现的成员名称。
void Screen::dummy_fcn(pos ht){
cursor = width * this->height; //成员height
}
类作用域之后,在外围的作用域中查找
如果编译器在函数和类的作用域中都没有做找到名字,它将在接着在外围的作用域中查找。
例如上面的例子中,外层作用域中的height被累的成员隐藏,如果需要使用外层作用域的名字,可以显示地通过作用域符来进行访问:
void Screen::dummy_fcn(pos height){
cursor = width * ::height; //全局作用域的height
}
尽管外层的对象被隐藏了,但仍然可以使用作用域操作符来使用它。
在文件中名字出现处对其进行解析
int height;
class Screen
{
public:
typedef std::string::size_type pos;
void setHeight(pos);
pos height = 0; //隐藏了外层作用域中的height
};
Screen::pos verify(Screen::pos); //全局函数
void Screen::setHeight(pos var)
{
height = verify(var); //height是类的成员,
}
verify
的声明在 Screen
类的定义之前是不可见的。但是,名字查找的第三步包括了成员函数出现之前的全局作用域,verify
的声明位于 setHeight
的定义之前,所以可以正常使用。