类作用域
每个类都定义了自己的新作用域和唯一的类型。在类的定义体内声明类成员,将成员名引入类的作用域。两个不同的类具有两个的类作用域。
即使两个类具有完全相同的成员列表,它们也是不同的类型。每个类的成员不同于任何其他类(或任何其他作用域)的成员。
例如:
class First {
public:
int memi;
double memd;
};
class Second {
public:
int memi;
double memd;
};
First obj1;
Second obj2 = obj1; // error: obj1 and obj2 have different types
使用类的成员
在类作用域之外,成员只能通过对象或指针分别使用成员访问操作符 . 或-> 来访问。这些操作符左边的操作数分别是一个类对象或指向类对象的指针。跟在操作符后面的成员名字必须在相关联的类的作用域中声明:
Class obj; // Class is some class type
Class *ptr = &obj;
// member is a data member of that class
ptr->member; // fetches member from the object to which ptr points
obj.member; // fetches member from the object named obj
// memfcn is a function member of that class
ptr->memfcn(); // runs memfcn on the object to which ptr points
obj.memfcn(); // runs memfcn on the object named obj
一些成员使用成员访问操作符来访问,另一些直接通过类使用作用域操作符(::)来访问。一般的数据或函数成员必须通过对象来访问。
作用域与成员定义
尽管成员是在类的定义体之外定义的,但成员定义就好像它们是在类的作用域中一样。出现在类的定义体之外的成员定义必须指明成员出现在哪个类中:
double Sales_item::avg_price() const
{
if (units_sold)
return revenue/units_sold;
else
return 0;
}
在这里,我们用完全限定名 Sales_item::avg_price 来指出这是类Sales_item 作用域中的 avg_price 成员的定义。一旦看到成员的完全限定名,就知道该定义是在类作用域中。因为该定义是在类作用域中,所以我们可以引用revenue 或 units_sold,而不必写 this->revenue 或 this->units_sold。
形参表和函数体处于类作用域中
在定义于类外部的成员函数中,形参表和成员函数体都出现在成员名之后。这些都是在类作用域中定义,所以可以不用限定而引用其他成员。例如:
char Screen::get(index r, index c) const
{
index row = r * width; // compute the row location
return contents[row + c]; // offset by c to fetch specified
character
}
该函数用 Screen 内定义的 index 类型来指定其形参类型。因为形参表是在 Screen 类的作用域内,所以不必指明我们想要的是 Screen::index。我们想要的是定义在当前类作用域中的,这是隐含的。同样,使用 index、width 和contents 时指的都是 Screen 类中声明的名字。
函数返回类型不一定在类作用域中
与形参类型相比,返回类型出现在成员名字前面。如果函数在类定义体之外定义,则用于返回类型的名字在类作用域之外。如果返回类型使用由类定义的类型,则必须使用完全限定名。例如:
class Screen {
public:
typedef std::string::size_type index;
index get_cursor() const;
};
inline Screen::index Screen::get_cursor() const
{
return cursor;
}
该函数的返回类型是 index,这是在 Screen 类内部定义的一个类型名。如果在类定义体之外定义 get_cursor,则在函数名被处理之前,代码不在类作用域内。当看到返回类型时,其名字是在类作用域之外使用。必须用完全限定的类型名 Screen::index 来指定所需要的 index 是在类 Screen 中定义的名字。
类作用域中的名字查找
名字查找(寻找与给定的名字使用相匹配的声明的过程)是相对直接的。
1. 首先,在使用该名字的块中查找名字的声明。只考虑在该项使用之前声明的名字。
2. 如果找不到该名字,则在包围的作用域中查找。
如果找不到任何声明,则程序出错。在 C++ 程序中,所有名字必须在使用之前声明。
类定义实际上是在两个阶段中处理:
1. 首先,编译成员声明;
2. 只有在所有成员出现之后,才编译它们的定义本身。
当然,类作用域中使用的名字并非必须是类成员名。类作用域中的名字查找也会发生在其他作用域中声明的名字。在名字查找期间,如果类作用域中使用的名字不能确定为类成员名,则在包含该类或成员定义的作用域中查找,以便找到该名字的声明。
类成员声明的名字查找
按以下方式确定在类成员的声明中用到的名字。
1. 检查出现在名字使用之前的类成员的声明。
2. 如果第 1 步查找不成功,则检查包含类定义的作用域中出现的声明以及出现在类定义之前的声明。
例如:
typedef double Money;
class Account {
public:
Money balance() { return bal; }
private:
Money bal;
// ...
};
在处理 balance 函数的声明时,编译器首先在类 Account 的作用域中查找Money 的声明。编译器只考虑出现在 Money 使用之前的声明。因为找不到任何成员声明,编译器随后在全局作用域中查找 Money 的声明。只考虑出现在类Account 的定义之前的声明。找到全局的类型别名 Money 的声明,并将它用作函数 balance 的返回类型和数据成员 bal 的类型。
必须在类中先定义类型名字,才能将它们用作数据成员的类型,或者成员函数的返回类型或形参类型。
编译器按照成员声明在类中出现的次序来处理它们。通常,名字必须在使用之前进行定义。而且,一旦一个名字被用作类型名,该名字就不能被重复定义:
typedef double Money;
class Account {
public:
Money balance() { return bal; } // uses global definition of Money
private:
// error: cannot change meaning of Money
typedef long double Money;
Money bal;
// ...
};
类成员定义中的名字查找
按以下方式确定在成员函数的函数体中用到的名字。
1. 首先检查成员函数局部作用域中的声明。
2. 如果在成员函数中找不到该名字的声明,则检查对所有类成员的声明。
3. 如果在类中找不到该名字的声明, 则检查在此成员函数定义之前的作用域中出现的声明。
下面的函数使用了相同的名字来表示形参和成员,这是通常应该避免的。这样做的目的是展示如何确定名字:
// Note: This code is for illustration purposes only and reflects bad practice
// It is a bad idea to use the same name for a parameter and a member int height;
class Screen {
public:
void dummy_fcn(index height) {
cursor = width * height; // which height? The parameter
}
private:
index cursor;
index height, width;
};
查找 dummy_fcn 的定义中使用的名字 height 的声明时,编译器首先在该函数的局部作用域中查找。函数的局部作用域中声明了一个函数形参。dummy_fcn的函数体中使用的名字 height 指的就是这个形参声明。
在本例中,height 形参屏蔽名为 height 的成员。尽管类的成员被屏蔽了,但仍然可以通过用类名来限定成员名或显式使用 this 指针来使用它。
如果想覆盖常规的查找规则,应该这样做:
// bad practice: Names local to member functions shouldn't hide member names
void dummy_fcn(index height) {
cursor = width * this->height; // member height
// alternative way to indicate the member
cursor = width * Screen::height; // member height
}
现在当编译器查找名字 height 时,它将不会在函数内查找该名字。编译器接着会在 Screen 类中查找。因为 height 是在成员函数内部使用,所以编译器在所有成员声明中查找。尽管 height 是先在 dummy_fcn 中使用,然后再声明,编译器还是确定这里用的是名为 height 的数据成员。
类作用域之后,在外围作用域中查找
如果编译器不能在函数或类作用域中找到,就在外围作用域中查找。在本例子中, 出现在 Screen 定义之前的全局作用域中声明了一个名为 height 的全局声明。然而,该对象被屏蔽了。
尽管全局对象被屏蔽了,但通过用全局作用域确定操作符来限定名字,仍然可以使用它。
// bad practice: Don't hide names that are needed from surrounding scopes
void dummy_fcn(index height) {
cursor = width * ::height;// which height? The global one
}
在文件中名字的出现处确定名字
当成员定义在类定义的外部时,名字查找的第 3 步不仅要考虑在 Screen类定义之前的全局作用域中的声明,而且要考虑在成员函数定义之前出现的全局作用域声明。例如:
class Screen {
public:
// ...
void setHeight(index);
private:
index height;
};
Screen::index verify(Screen::index);
void Screen::setHeight(index var) {
// var: refers to the parameter
// height: refers to the class member
// verify: refers to the global function
height = verify(var);
}
注意,全局函数 verify 的声明在 Screen 类定义之前是不可见的。然而,名字查找的第 3 步要考虑那些出现在成员定义之前的外围作用域声明,并找到全局函数 verify 的声明。

浙公网安备 33010602011771号