#include <iostream.h>
class B {
private:
int nNumber;
public:
virtual void test() {
cout << "B::test()\n"; }
virtual void test(int x) {
nNumber = x; // 将传入的参数赋值给私有成员
cout << "B::test(int x)\n";
}
};
class D : public B {
public:
//test(void) 隐藏 B::test(int)
virtual void test() {
cout << "D::test()\n";
}
};
void main(int argc, char* argv[])
{
D d; // 派生类的实例
d.test(); // OK
d.test(17); // 产生编译错误的代码行
}
基类中有两个重载的test函数。当我在派生类中改写其中的一个函数时,我觉得另外一个应该在派生类中继承,但编译时出现一下错误信息:
... C2660 : 'test' :function does not take 1 parameters
在不放弃多态行为的情况下,我能不能同时重载和改写某个函数?
许多C++程序员都对这个问题感到困惑,为了解开这个惑点,有两个概念你必须要了然于心。一个是重载,一个是名字空间。从重载的视角看,你可能觉得C++应该按照你所想象的方式工作;也就是如果派生类只修改其中的一个重载函数的行为,那么另外一个应该按照通常的方式被继承。但从名字空间的角度看,当C++试图解决某个符号名时,它以由近到远的顺序进行搜索,它首先找本地变量,然后是类成员名,接着是基类名,全局变量……。一旦编译器解析了名字,它便终止解析过程。如果在不同的名字空间范围碰到同一个符号名,那么它便没有了主意。在你的代码中,一旦编译器确定名字"test"出现在类D中,那么它的名字空间就为类D。不论你调用不带参数的test也好,还是调用带一个整型参数的test(17)也好,编译器都是遵循准这个规则来解决名字问题。对于有整型参数的test(int x)函数,一旦编译器确定函数名test生存在类D中,它便终止范围探查,然后查找与参数匹配的函数。由于它没有在类D中找到带整型参数的函数,所以报错。此时编译器认为函数D::test(void)隐藏了函数B::test(int)。你可能会问,为什么要按这种方式处理?问得好。想找到答案,请参考Bjarne Stroustrup 的C++注释参考手册(Section 13.1)。简单地说,在类层次深处搜索与某个签名匹配的重载函数比不搜索更可能导致混乱。
那么如何解决上面出现的问题呢?很容易。只要在派生类中创建另一个显式调用基类的test函数即可,如:
class D : public B {
public:
virtual void test(int x) { B::test(x); }
……
};
这样,D就有了两个test函数,问题也解决了。 其实这个问题还有另外一个解法:那就是在派生类中使用"using"关键字。如:
class D : public B {
public:
using B::test;
// 改写test
virtual void test() {
cout << "D::test()\n";
}
};
这个关键字将B::test带入类D中。它的优点是当类B扩展更多的test重载函数时,你不用再去重写类D的代码。这样省了很多事。只要你使用using关键字,那么就意味着所有重载的 B::test函数都进入类D并且得到继承。这种特性可能对于某些人来说是优点,而对于另外一些人来说是缺点,依赖于你要做什么事情。如果你想要向类D的用户隐藏某些test函数,则可以用第一种方法(显式调用基类),或者让你想要隐藏的函数成为private 或 protected类型。如果你读过Scott的书《Effective C++》,还可以用第二种方法利用using关键字巧妙地保证基类的私隐。
class B {
public:
func1();
func2(double d);
virtual test();
virtual test(int x);
};
// D 从B中秘密派生
class D : private B {
public:
// 让所有 B::test 函数为 public类型
using B::test;
};
显然,当希望完全隐藏类B时,这个方法很有用,但如果想要公开B中的个别函数,则这个方法就不是那么好了。
延伸阅读
文章来源于领测软件测试网 https://www.ltesting.net/