成员函数查找[条款24]---《C++必知必会》
调用一个成员函数,涉及三个步骤:第一步,编译器查找函数的名字;第二部,从可用候选者中选择最佳匹配函数;第三步,检查是否具有访问该函数的权限。
#include<iostream> using namespace std; class B{ public: void f(double para){ cout<<"B::f"<<para<<endl; } }; class D:public B{
//void f(int para){}; //私有成员,编译main中 d.f(12.3)行,会报错 public: void f(int para){ cout<<"D::f"<<para<<endl; } }; class E:public D{ int f; }; int main() { D d; d.f(12.3); //输出D::f12 E e; e.f(12.3); //错误 return 0; }
分析:
步骤1:查找函数的名字。因为我们正在调用一个D对象的成员,所以将从D的作用域开始查找并且立即定位到D::f上。
步骤2:从可用的候选者中选择最佳的匹配函数。我们只有一个候选者D::f(为什么只是一个,往后面看),因此会尝试匹配该函数。可以通过将实参12.3从double转换为int而做到这一点(这是合法的,但通常不是我们想要的,因为那样会丢失精度)。
步骤3:检查访问权限。我们(可能)会得到一个错误,因为D::f是私有成员。
基类中的哪一个看上去有着更好的匹配、并且可访问的函数f已经无关紧要,因为一旦在内层作用域中找到一个,编译器就不会到外层作用域中继续查找该名字。内层作用域中的名字会隐藏外层作用域中相同的名字。在这一点,C++不同于Java。Java中,内层作用域的方法名字和外层作用域中同名方法属于重载关系。
实际上,改名字甚至不是一个函数的名字:
class E:public D{ int f; };
//...
E e;
e.f(12.3); //错误
反思:
在这个例子中,我们得到一个编译器错误,因为在作用域E中查找名字f,结果找到了一个数据成员,而不是成员函数。顺便说一下,这也是建立并遵从简单的命名诸多理由之一。如果数据成员E::f被命名为 f_ 或者 m_f,它就不会隐藏被继承的基类函数 f 了