参考
2.2 变量—–局部和全局变量
12.3 (上 ) 类作用域
12.1.3 成员函数重载和Screen类定义
========================================================================================================================================
一、类作用域中的名字查找
首先,在使用该名字的块中查找名字的声明。只考虑在该项使用之前声明的名字。
如果找不到该名字,则在包围的作用域中查找
-如果找不到任何声明,则程序出错。所有名字都必须在使用之前声明
类作用域表现得有些不同,但遵循同一个规则,可能引起混淆得是函数中名字确定的方式,而该函数是在类定义体内定义的。
- 类定义实际上是在两个阶段中处理:
- 首先,编译成员声明;
- 只有在所有成员出现之后,才编译他们的定义本身
- 类定义实际上是在两个阶段中处理:
类作用域中使用的名字并非必须是类成员名,类作用域中的名字查找也会发现在其他作用域中声明的名字。在名字查找期间,如果类作用域中使用的名字不能确定为类成员名,则在包含该类或成员定义的作用域中查找,以便找到该名字的声明
1、类成员声明的名字查找
按以下方式确定在类成员的声明中用到的名字
检查出现在名字使用之前的类成员的声明
如果第一步查找不成功,则检查包含类定义的作用域中出现的声明以及出现在类定义之前的声明。
必须在类中先定义类型名字,才能将他们用作数据成员的类型,或者成员函数的返回类型或形参类型。
编译器按照成员声明在类中出现的次序来处理他们,通常,名字必须在使用之前进行定义。而且,一旦一个名字被用作类型名,该名字就不能被重复定义
typedef double Money;
class Account{
public:
Money balance(){return bal;}
private:
Money bal;
};
- 在处理balance函数的声明时,编译器首先在类Account的作用域中查找Money的声明。
- 编译器只考虑出现在Money使用之前的声明。因为找不到任何成员声明,编译器随后在全局作用域中查找Money的声明
只考虑出现在类Account的定义之前的声明,找到全局的类型别名Money的声明,并将它用作函数balance的返回类型和数据成员bal的类型。
编译器按照成员声明在类中出现的次序来处理他们,通常,名字必须在使用之前进行定义。而且,一旦一个名字被用作类型名,该名字就不能被重复定义
名字必须在使用之前定义,不然会出现编译错误,如果在Screen类中,把对类型别名index放到类中的最后一行,就会出错,因为对类型别名index的使用出现在其定义之前 Screen类在 12.1.3节
typedef double Money;
class Account{
public:
Money balance(){return bal;}//使用全局定义的Money
private:
typedef long double Money;//错误!!!!名字不能被重复定义
Money bal;
};
2、类成员定义中的名字查找
按以下方式确定在成员函数的函数体中用到的名字
- 1、首先检查成员函数局部作用域中的声明;
- 2、如果在成员函数中找不到该名字的声明,则检查对所有类作用域中类成员的声明
- 3、如果在类中找不到该名字的声明,则检查在此成员函数定义之前的作用域中出现的声明
3、类成员遵循常规的块作用域的名字查找
//下面的例子是不好的
//在12.1.3节中的Screen类,p372 typedef std::string::size_type index;
int height;//全局作用域中的height
class Screen{
public:
void dummf_fcn(index height){//函数局部作用域中的形参height
cursor=width*height;//这里到底用的是哪个height??
}
private:
index cursor;
index height,width;//类作用域中的类成员声明height
};
//查找dummf_fcn的定义中使用的名字height的声明时,编译器首先在该函数的局部作用域中查找,函数的局部作用域声明了一个函数形参,dummf_fcn函数体中使用的height就是这个形参,而不是Screen的数据成员height。
//类的数据成员height被屏蔽了,但任然可以通过用类名来限定成员名或显示使用this指针来使用它。
//如果我们想覆盖常规的查找规则,应该这样做:
void dummf_fcn(index height){
cursor = width*this->height;//height数据成员,而不是形参里面的height
cursor =width*Screen::height;//height数据成员,而不是形参里面的height
}
4、函数作用域之后,在类作用域中查找
//如果想要使用类里面的height数据成员,更好的方式是为形参取不同的名字
void dummy_fcn(index ht){
cursor = width*height;//很明显,这里用的是私有的数据成员height
}
//现在当编译器查找名字height时,它将不会在函数内查找该名字。会接着在类中查找,因为height是在成员函数内部使用,所以编译器在所有成员声明中查找。尽管height是先在dummy_fcn中使用,然后再声明,编译器还是确定这里用的是名为height的数据成员。
5、类作用域之后,在外围作用域中查找
如果编译器不能在函数或类作用域中找到,就在外围作用域中查找。在本例子中,出现在Screen定义之前的全局作用域中声明了一个名为height的全局对象,然而该对象被屏蔽了。
尽管全局作用域被屏蔽了,但通过用全局作用域确定操作符来限定名字,任然可以使用它
可以参照 2.2 节
a. 里面因为全局作用域本身并没有名字,所以当作用域操作符的左侧为空时,向全局作用域发出请求获取作用域操作符右侧名字对应的变量,::height
b. 局部变量会屏蔽全局变量的值,除非用到全局变量作用域运算符 “: :”。
void dummy_fcn(index height){
cursor=weight*::height//使用全局作用域中的height
}
6、在文件中名字的出现处确定名字
- 当成员定义在类定义的外部时,名字查找的第3步不仅要考虑在Screen类定义之前的全局作用域中的声明,而且要考虑在成员函数定义之前出现的全局作用域声明
class Screen{
public:
void setHeight(index);
private:
index height;
};
//看12.3节 上 类作用域
//在类外定义成员函数或是使用里面的名称要加上类作用域运算符,所以index前面加上Screen::。
//而vertify不是成员函数,所以它前面不用加上Screen::
Screen::index vertify(Screen::index);
//类外定义成员函数setHeight,所以要在函数名前面加上 Screen::
void Screen::setHeight(index var){
height=vertify(var);
//var指的是形参var
//height指的是类成员height
//vertify指的是全局的函数
}
- 全局函数vertify的声明在Screen类定义之前是不可见的。然而,名字查找的第3步要考虑那些出现在成员定义之前的外围作用域声明,并找到全局函数vertify的声明。
//习题 12.18
typedef string Type;//定义全局string类型别名Type
Type initVal(); //全局的initVal函数声明
//从此处开始Exercise类的定义体
class Exercise{
public:
typedef double Type;//定义Exercise类内部的double类型别名Type
Type setVal(Type);//成员函数setVal的声明
Type initval();//成员函数initVal的声明
private:
int val;//定义数据成员
};//类Exercise的定义体结束
//Exercise类的成员函数setVal在类外的定义,这里有错
Type Exercise::setVal(Type parm){
val=parm+initVal();
}
//1、在Exercise类的定义体内,成员函数setVal和initVal的声明中作为形参类型和返回类型的Type,所使用的都是Exercise内部定义的类型 别名Type
//2、在Exercise类的定义体之外成员函数setVal的定义中,作为形参类型的Type,使用的是Exercise内部定义的类型别名Type;作为返回类型的Type,使用的是全局的类型别名Type
//3、在Exercise类的成员函数setVal的定义中使用的initVal,用到的是类的成员函数。
//4、成员函数setVal的定义有错:编译器会认为Exercise类中存在两个相互重载的成员函数setVal,但这两个函数的区别仅在于返回类型不同,不能构成合法的函数重载,出错。而且函数返回的应该是一个值。
Exercise::Type Exercise::setVal(Type parm){
val=parm+initVal();
return val;
}
//对上述代码进行如下修改,可使得类Exercise使用全局的类型别名Type和全局函数initVal
typedef string Type;
Type initVal();
class Exercise{
public:
Type setVal(Type);//成员函数setVal的声明
private:
Type val;
};//类Exercise的定义体结束
Type Exercise::setVal(Type parm){
val=parm+initVal();
return val;
}