7.4 类的作用域

7.4 类的作用域

在类的作用域之外,如何访问类的成员?

  1. 对于普通的数据成员和函数成员只能由对象、引用或者指针使用成员访问运算符来访问。

    Screen scr(ht, wd, ' ');
    Screen *p = &scr;
    char c = src.get();	// 访问scr对象的get成员
    c = p->get();		// 访问p所指对象的get成员
    
  2. 对于类类型成员则只用作用域运算符访问。

    Screen::pos ht = 24, wd = 80;
    

作用域和定义在类外部的成员

​ 在类的外部定义函数时必须同时提供类名函数名

​ 一旦遇到了类名,类名之后函数定义的剩余部分(参数列表和函数体)就在类的作用域之内了,就可以直接使用类的其它成员了。

// 类名(Window_mgr);函数名(clear)
void Window_mgr::clear(ScreenIndex i)	// ScreenIndex是Window_mgr类定义的,可直接使用
{
    Screen &s = screens[i];	// screens也是在Window_mgr类中定义的,可直接使用
    s.contents = string(s.height * s.width, ' ');
}

另一方面,函数的返回类型通常出现在函数名之前,所以它在类的作用域之外。

class Window_mgr {
 public:
    // 向窗口添加一个Screen,返回它的编号
    ScreenIndex addScreen(const Screen&);
};

// 首先处理返回类型,之后才进入Window_mgr类的作用域,所以需明确返回类型ScreenIndex的来源
Window_mgr::ScreenIndex Window_mgr::addScreen(const Screen &s)
{
    screens.push_back(s);
    return screens.size() - 1;
}

7.4.1 名字查找与类的作用域

一般的名字查找过程:

  • 首先,在名字所在的块中寻找其声明语句,只考虑在名字的使用之前出现的声明;
  • 如果没有找到,继续查找外层作用域;
  • 如果最终没有找到匹配的声明,则程序报错。

对于定义在类内部的成员函数来说,编译器会处理完类中的全部声明才会处理成员函数的定义。类的定义分为两步:

  • 首先,编译成员的声明;
  • 知道类全部可见后才编译函数体。

类成员声明的名字查找

​ 类成员声明中使用的名字,包括返回类型或者参数列表中使用的名字,都必须在使用前确保可见。

typedef double Money;
string bal;
class Account{
public:
    Money balance() { return bal; }	// 返回的是Money对象,而不是string对象
private:
    Money bal;
};

​ 当编译器看到balance函数的声明语句时,它将在Account类的范围内寻找对Money 的声明。且只考虑Account中使用Money之前出现的声明,因为没找到匹配的成员,所以编译器会接着到Account的外层作用域中查找。所以,编译器会找到Money 的typedef语句,该类型被用作balance函数的返回类型以及数据成员bal的类型。

​ 另一方面,balance函数体在整个类可见后才被处理,因此,该函数的return语句返回名为bal的成员,而非外层作用域的string对象bal。

类型名要特殊处理

​ 在类中,如果成员使用了外层作用域中的某个名字,而该名字代表一种类型,则类不能在之后重新定义该名字(即使两个定义完全一致):

typedef double Money; 
class Account { 
public: 
    Money balance() { return bal; }	// 使用外层作用域的Money
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;	// 哪个height?是那个参数,在成员函数内找到该名字的声明
    }
private:
    pos cursor = O;
    pos height = 0, width = O;
};
  • 如果在成员函数内没有找到,则在类内继续查找,这时类的所有成员都可以被考虑。
int height;
class Screen {
public:
    typedef std::string::size_type pos;
    
    void dummy_fcn_1() {
        cursor = width * height;	// 类成员height,在类内找到该名字的声明
    }
    // 等价于
    void dummy_fcn_2(pos height) {	// 不建议的写法. 成员函数中的名字不应该隐藏同名的成员
        cursor = width * this->height;		// 类成员height,在类内找到该名字的声明
        cursor = width * Screen::height;	// 另外一种表示该类成员的方式
    }
private:
    pos cursor = O;
    pos height = 0, width = O;
};
  • 如果类内也没找到该名字的声明, 在成员函数定义之前的作用域内继续查找。
int height;
class Screen {
public:
    typedef std::string::size_type pos;
    
    void dummy_fcn_1() {
        cursor = width * height;	// 类外定义的height(height定义在类定义之前的全局作用域)
    }
    // 等价于
    void dummy_fcn_2(pos height) {
        cursor = width * ::height;
    }
private:
    pos cursor = O;
    pos width = O;
};
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是类成员
    height = verify(var);	// verify定义在成员函数定义之前的全局作用域
}
posted on 2022-07-16 14:20  天官赐福  阅读(67)  评论(0)    收藏  举报