Team LiB
Previous Section Next Section

12.3. Class Scope

12.3. 类作用域

Every class defines its own new scope and a unique type. The declarations of the class members within the class body introduce the member names into the scope of their class. Two different classes have two different class scopes.

每个类都定义了自己的新作用域和唯一的类型。在类的定义体内声明类成员,将成员名引入类的作用域。两个不同的类具有两个的类作用域。

Even if two classes have exactly the same member list, they are different types. The members of each class are distinct from the members of any other class (or any other scope).

即使两个类具有完全相同的成员列表,它们也是不同的类型。每个类的成员不同于任何其他类(或任何其他作用域)的成员。



For example:

例如

     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

Using a Class Member

使用类的成员

Outside the class scope, members may be accessed only through an object or a pointer using member access operators dot or arrow, respectively. The left-hand operand to these operators is a class object or a pointer to a class object, respectively. The member name that follows the operator must be declared in the scope of the associated class:

在类作用域之外,成员只能通过对象或指针分别使用成员访问操作符 .-> 来访问。这些操作符左边的操作数分别是一个类对象或指向类对象的指针。跟在操作符后面的成员名字必须在相关联的类的作用域中声明:

     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

Some members are accessed using the member access operators; others are accessed directly from the class using the scope operator, (::). Ordinary data or function members must be accessed through an object. Members that define types, such as Screen::index, are accessed using the scope operator.

一些成员使用成员访问操作符来访问,另一些直接通过类使用作用域操作符(::)来访问。一般的数据或函数成员必须通过对象来访问。定义类型的成员,如 Screen::index,使用作用域操作符来访问。

Scope and Member Definitions

作用域与成员定义

Member definitions behave as if they are in the scope of the class, even if the member is defined outside the class body. Recall that member definitions that appear outside the class body must indicate the class in which the member appears:

尽管成员是在类的定义体之外定义的,但成员定义就好像它们是在类的作用域中一样。回忆一下,出现在类的定义体之外的成员定义必须指明成员出现在哪个类中:

     double Sales_item::avg_price() const
     {
         if (units_sold)
             return revenue/units_sold;
         else
             return 0;
     }

Here we use the fully qualified name Sales_item::avg_price to indicate that the definition is for the avg_price member in the scope of the Sales_item class. Once the fully qualified name of the member is seen, the definition is known to be in class scope. Because the definition is in class scope, we can refer to revenue and units_sold without having to write this->revenue or this->units_sold.

在这里,我们用完全限定名 Sales_item::avg_price 来指出这是类 Sales_item 作用域中的 avg_price 成员的定义。一旦看到成员的完全限定名,就知道该定义是在类作用域中。因为该定义是在类作用域中,所以我们可以引用 revenueunits_sold,而不必写 this->revenuethis->units_sold

Parameter Lists and Function Bodies Are in Class Scope

形参表和函数体处于类作用域中

In a member function defined outside the class, the parameter list and member-function body both appear after the member name. These are defined inside the class scope and so may refer to other class members without qualificationfor example, the definition of the two-parameter version of get in class Screen:

在定义于类外部的成员函数中,形参表和成员函数体都出现在成员名之后。这些都是在类作用域中定义,所以可以不用限定而引用其他成员。例如,类 Screenget 的二形参版本的定义:

     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
     }

This function uses the type name index defined inside Screen to name the types of its parameters. Because the parameter list is inside the scope of class Screen, there is no need to specify that we want Screen::index. It is implicit that the one we want is the one defined in the current class scope. Similarly, the uses of index, width, and contents all refer to names declared inside class Screen.

该函数用 Screen 内定义的 index 类型来指定其形参类型。因为形参表是在 Screen 类的作用域内,所以不必指明我们想要的是 Screen::index。我们想要的是定义在当前类作用域中的,这是隐含的。同样,使用 indexwidthcontents 时指的都是 Screen 类中声明的名字。

Function Return Types Aren't Always in Class Scope

函数返回类型不一定在类作用域中

In contrast to the parameter types, the return type appears before the member name. If the function is defined outside the class body, then the name used for the return type is outside the class scope. If the return type uses a type defined by the class, it must use the fully qualified name. For example, consider the get_cursor function:

与形参类型相比,返回类型出现在成员名字前面。如果函数在类定义体之外定义,则用于返回类型的名字在类作用域之外。如果返回类型使用由类定义的类型,则必须使用完全限定名。例如,考虑 get_cursor 函数:

     class Screen {
     public:
         typedef std::string::size_type index;
         index get_cursor() const;
     };
     inline Screen::index Screen::get_cursor() const
     {
         return cursor;
     }

The return type of this function is index, which is a type name defined inside the Screen class. If we define get_cursor outside the class body, the code is not in the class scope until the function name has been processed. When the return type is seen, its name is used outside of the class scope. We must use the fully qualified type name, Screen::index to specify that we want the name index that is defined inside class Screen.

该函数的返回类型是 index,这是在 Screen 类内部定义的一个类型名。如果在类定义体之外定义 get_cursor,则在函数名被处理之前,代码在不在类作用域内。当看到返回类型时,其名字是在类作用域之外使用。必须用完全限定的类型名 Screen::index 来指定所需要的 index 是在类 Screen 中定义的名字。

Exercises Section 12.3

Exercise 12.15:

List the portions of program text that are in class scope.

列出在类作用域中的程序文本部分。

Exercise 12.16:

What would happen if we defined get_cursor as follows:

如果如下定义 get_cursor,将会发生什么:

     index Screen::get_cursor() const
     {
         return cursor;
     }


12.3.1. Name Lookup in Class Scope

12.3.1. 类作用域中的名字查找

In the programs we've written so far, name lookup (the process of finding which declaration is matched to a given use of a name) has been relatively straightforward:

迄今为止,在我们所编写的程序中,名字查找(寻找与给定的名字使用相匹配的声明的过程)是相对直接的。

  1. First, look for a declaration of the name in the block in which the name was used. Only names declared before the use are considered.

    首先,在使用该名字的块中查找名字的声明。只考虑在该项使用之前声明的名字。

  2. If the name isn't found, the enclosing scope(s) are searched.

    如果找不到该名字,则在包围的作用域中查找。

If no declaration is found, then the program is in error. In C++ programs, all names must be declared before they are used.

如果找不到任何声明,则程序出错。在 C++ 程序中,所有名字必须在使用之前声明。

Class scopes may seem to behave a bit differently, but in reality they obey this same rule. Confusion can arise due to the way names are resolved inside a function defined within the class body itself.

类作用域也许表现得有点不同,但实际上遵循同一规则。可能引起混淆的是函数中名字确定的方式,而该函数是在类定义体内定义的。

Class definitions are actually processed in two phases:

类定义实际上是在两个阶段中处理:


  1. First, the member declarations are compiled.

    首先,编译成员声明;

  2. Only after all the class members have been seen are the definitions themselves compiled.

    只有在所有成员出现之后,才编译它们的定义本身。


Of course, the names used in class scope do not always have to be class member names. Name lookup in class scope finds names declared in other scopes as well. During name lookup, if a name used in class scope does not resolve to a class member name, the scopes surrounding the class or member definition are searched to find a declaration for the name.

当然,类作用域中使用的名字并非必须是类成员名。类作用域中的名字查找也会发生在其他作用域中声明的名字。在名字查找期间,如果类作用域中使用的名字不能确定为类成员名,则在包含该类或成员定义的作用域中查找,以便找到该名字的声明。

Name Lookup for Class Member Declarations
类成员声明的名字查找

Names used in the declarations of a class member are resolved as follows:

按以下方式确定在类成员的声明中用到的名字。

  1. The declarations of the class members that appear before the use of the name are considered.

    检查出现在名字使用之前的类成员的声明。

  2. If the lookup in step 1 is not successful, the declarations that appear in the scope in which the class is defined, and that appear before the class definition itself, are considered.

    如果第 1 步查找不成功,则检查包含类定义的作用域中出现的声明以及出现在类定义之前的声明。

For example:

例如:

     typedef double Money;
     class Account {
     public:
         Money balance() { return bal; }
     private:
         Money bal;
         // ...
     };

When processing the declaration of the balance function, the compiler first looks for a declaration of Money in the scope of the class Account. The compiler considers only declarations that appear before the use of Money. Because no member declaration is found, the compiler then looks for a declaration of Money in global scope. Only the declarations located before the definition of the class Account are considered. The declaration for the global typedef Money is found and is used for the return type of the function balance and the data member bal.

在处理 balance 函数的声明时,编译器首先在类 Account 的作用域中查找 Money 的声明。编译器只考虑出现在 Money 使用之前的声明。因为找不到任何成员声明,编译器随后在全局作用域中查找 Money 的声明。只考虑出现在类 Account 的定义之前的声明。找到全局的类型别名 Money 的声明,并将它用作函数 balance 的返回类型和数据成员 bal 的类型。

Names of types defined in a class must be seen before they are used as the type of a data member or as the return type or parameter type(s) of a member function.

必须在类中先定义类型名字,才能将它们用作数据成员的类型,或者成员函数的返回类型或形参类型。



The compiler handles member declarations in the order in which they appear in the class. As usual, a name must be defined before it can be used. Moreover, once a name has been used as the name of a type, that name may not be redefined:

编译器按照成员声明在类中出现的次序来处理它们。通常,名字必须在使用之前进行定义。而且,一旦一个名字被用作类型名,该名字就不能被重复定义:

     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;
         // ...
     };

Name Lookup in Class Member Definitions
类成员定义中的名字查找

A name used in the body of a member function is resolved as follows:

按以下方式确定在成员函数的函数体中用到的名字。

  1. Declarations in the member-function local scopes are considered first.

    首先检查成员函数局部作用域中的声明。

  2. If the a declaration for the name is not found in the member function, the declarations for all the class members are considered.

    如果在成员函数中找不到该名字的声明,则检查对所有类成员的声明。

  3. If a declaration for the name is not found in the class, the declarations that appear in scope before the member function definition are considered. 

    如果在类中找不到该名字的声明,则检查在此成员函数定义之前的作用域中出现的声明。

Class Members Follow Normal Block-Scope Name Lookup
类成员遵循常规的块作用域名字查找

Programs that illustrate how name lookup works often have to rely on bad practices. The next several programs contain bad style deliberately.

例示名字查找的程序经常不得不依赖一些坏习惯。下面的几个程序故意包含了坏的风格。



The following function uses the same name for a parameter and a member, which normally should be avoided. We do so here to show how names are resolved:

下面的函数使用了相同的名字来表示形参和成员,这是通常应该避免的。这样做的目的是展示如何确定名字:

     // 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;
     };

When looking for a declaration for the name height used in the definition of dummy_fcn, the compiler first looks in the local scope of that function. A function parameter is declared in the local scope of its function. The name height used in the body of dummy_fcn refers to this parameter declaration.

查找 dummy_fcn 的定义中使用的名字 height 的声明时,编译器首先在该函数的局部作用域中查找。函数的局部作用域中声明了一个函数形参。dummy_fcn 的函数体中使用的名字 height 指的就是这个形参声明。

In this case, the height parameter hides the member named height.

在本例中,height 形参屏蔽名为 height 的成员。

Even though the class member is hidden, it is still possible to use it by qualifying the member's name with the name of its class or by using the this pointer explicitly.

尽管类的成员被屏蔽了,但仍然可以通过用类名来限定成员名或显式使用 this 指针来使用它。



If we wanted to override the normal lookup rules, we could do so:

如果我们想覆盖常规的查找规则,应该这样做:

     // 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
     }

After Function Scope, Look in Class Scope
函数作用域之后,在类作用域中查找

If we wanted to use the member named height, a much better way to do so would be to give the parameter a different name:

如果想要使用 height 成员,更好的方式也许是为形参取一个不同的名字:

     // good practice: Don't use member name for a parameter or other local variable
     void dummy_fcn(index ht) {
         cursor = width * height; // member height
     }

Now when the compiler looks for the name height, it will not find that name in the function. The compiler next looks in the Screen class. Because height is used inside a member function, the compiler looks at all the member declarations. Even though the declaration of height appears after its use inside dummy_fcn, the compiler resolves this use to the data member named height.

现在当编译器查找名字 height 时,它将不会在函数内查找该名字。编译器接着会在 Screen 类中查找。因为 height 是在成员函数内部使用,所以编译器在所有成员声明中查找。尽管 height 是先在 dummy_fcn 中使用,然后再声明,编译器还是确定这里用的是名为 height 的数据成员。

After Class Scope, Look in the Surrounding Scope
类作用域之后,在外围作用域中查找

If the compiler doesn't find the name in function or class scope, it looks for the name in the surrounding scope. In our example, declarations in global scope that appear before the definition of the Screen include a global object named height. However, that object is hidden.

如果编译器不能在函数或类作用域中找到,就在外围作用域中查找。在本例子中,出现在 Screen 定义之前的全局作用域中声明了一个名为 height 的全局声明。然而,该对象被屏蔽了。

Even though the global object is hidden, it is still possible to use it by qualifying the name with the global scope resolution operator.

尽管全局对象被屏蔽了,但通过用全局作用域确定操作符来限定名字,仍然可以使用它。



     // 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
     }

Names Are Resolved Where They Appear within the File
在文件中名字的出现处确定名字

When a member is defined outside the class definition, the third step of name lookup not only considers the declarations in global scope that appear before the definition of class Screen, but also considers the global scope declarations that appear before the member function definitionfor example:

当成员定义在类定义的外部时,名字查找的第 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);
     }

Notice that the declaration of the global function verify is not visible before the definition of the class Screen. However, the third step of name lookup considers the surrounding scope declarations that appear before the member definition, and the declaration for the global function verify is found.

注意,全局函数 verify 的声明在 Screen 类定义之前是不可见的。然而,名字查找的第 3 步要考虑那些出现在成员定义之前的外围作用域声明,并找到全局函数 verify 的声明。

Exercises Section 12.3.1

Exercise 12.17:

What would happen if we put the typedef in the Screen class as the last line in the class?

如果将 Screen 类中的类型别名放到类中的最后一行,将会发生什么?

Exercise 12.18:

Explain the following code. Indicate which definition of Type or initVal is used for each use of those names. If there are any errors, say how you would fix the program.

解释下述代码。指出每次使用 TypeinitVal 时用到的是哪个名字定义。如果存在错误,说明如何改正。

     typedef string Type;
     Type initVal();

     class Exercise {
     public:
         // ...
         typedef double Type;
         Type setVal(Type);
         Type initVal();
     private:
         int val;
     };

     Type Exercise::setVal(Type parm) {
         val = parm + initVal();
     }

The definition of the member function setVal is in error. Apply the necessary changes so that the class Exercise uses the global typedef Type and the global function initVal.

成员函数 setVal 的定义有错。进行必要的修改以便类 Exercise 使用全局的类型别名 Type 和全局函数 initVal


Team LiB
Previous Section Next Section