C++Primer P499
“在基类和派生类中使用同一名字的成员函数,在派生类作用域中派生类成员将屏蔽基类成员,即使函数原型不同,基类成员也会被屏蔽:”
struct base
{
int memfcn();
};
struct Derived: base
{
int memfcn(int);
};
int main()
{
Derived d;
base b;
b.memfcn();
d.memfcn(10);
d.memfcn(); //error?编译器需要查找名字memfcn,并在Derived类中找到。一旦找到了名字,编译器就不再继续找了。
d.base::memfcn();
return 0;
}
注1:
派生类中定义的函数也不重载基类中定义的成员。通过派生类对象调用函数时,实参必须与派生类中定义的版本相匹配,只有在派生类根本没有定义该函数时,才考虑基类函数。
注2:如果派生类重定义了重载成员,则通过派生类只能访问派生类中重定义的那些成员。
“派生类不用重定义所继承的每一个基类版本,它可以为重载成员提供using声明。一个using 声明只能指定一个名字,不能指定形参表。因此,为基类成员函数名称而做的using声明将改函数的所有重载实例加到派生类的作用域。”将所有名字加入作用域后,派生类只需要重定义本类型确实必须定义的那些函数。“
开始有点不理解,看了下林锐的高质量编程上的实例。
P250 成员函数的重载、覆盖与隐藏
成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数类型、顺序或数目不同(包括const和非const参数);
(4)virtual 关键字可有可无。
覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。
P251 令人迷惑的隐藏规则
”隐藏“是指派生类的成员函数遮蔽了与其同名的基类成员函数,具有规则:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
下段代码中:
(1)函数Derived::f(float)覆盖了Base::f(float)。
(2)函数Derived::g(int) 隐藏了Base::g(float),而不是重载。
(3)函数Derived::h(float)隐藏了Base::h(float),而不是覆盖。
class Base
{
public:
virtual void f(float x)
{
cout << "Base::f(float) " << x << endl;
}
void g(float x)
{
cout << "Base::g(float) " << x << endl;
}
void h(float x)
{
cout << "Base::h(float) " << x << endl;
}
};
class Derived : public Base
{
public:
virtual void f(float x)
{
cout << "Derived::f(float) " << x << endl;
}
void g(int x)
{
cout << "Derived::g(int) " << x << endl;
}
void h(float x)
{
cout << "Derived::h(float) " << x << endl;
}
};
int main(void)
{
Derived d;
Base *pb = &d;
Derived *pd = &d;
//好:程序的行为仅依赖于对象的真实类型
pb->f(3.14f); //Derived::h(float)
pd->f(3.14f); //Derived::h(float)
//不好:程序的行为依赖于指针的静态类型
pb->g(3.14f);
pd->g(3.14f); //静态绑定:对象在Derived类中找到g(),就会停止查找了,此时3.14f被转换为int型
//不好:程序的行为依赖于指针的静态类型
pb->h(3.14f); //静态绑定:Base::h(float)
pd->h(3.14f); //静态绑定: Derived::h(float)
return 0;
}
提示:如果确实想使用所谓的”跨越类边界的重载“,可以在派生类定义中的任何地方显示地使用using关键字。
class Derived:public Base
{
public:
using Base::g;
void g(int x){...}
};
P253 摆脱隐藏
class Base
{
public:
void f(int x);
};
class Derived:public Base
{
public:
void f(char *str);
};
/*
如果要让pd->f(10)调用函数Base::f(int )
1. 使用using声明;
2. 把类Derived修改如下
*/
//1. using 声明
class Derived:public Base
{
public:
// using Base::f(int x);
//using Base::f(int);
using Base::f; //using声明只能指定一个名字,不能指定形参表
void f(char *str);
};
//2.
class Derived:public Base
{
public:
void f(char *str);
void f(int x){Base::f(x);}
}
void Test(void)
{
Derived *pd=new Derived;
pd->f(10); //编译器查找名字f,在Derived中找到,发生类型不匹配错误(数字10不能被隐式转换为字符串)
}
但是,隐藏规则的存在有会带来好处:
(1)写语句pd->f(10)的人可能真的想调用Derived::f(char *)函数,只是他误将参数写错了。有了隐藏规则,编译器就可以明确指出错误,这未必不是好事。否则,编译器会静悄悄地将错就错,程序员将很难发现这个错误,流下祸根。
(2)假如类Derived 有多个基类(多重继承),有时搞不清楚哪些基类定义了函数f。如果没有隐藏规则,那么pd->f(10)可能会调用一个出乎意料的基类函数f。尽管隐藏规则看起来不怎么有道理,但它的确能消灭这些意外。