C++中派生类重写基类重载函数时需要注意的问题:派生类函数屏蔽基类中同名函数

来源:互联网 发布:java可以在手机上编程 编辑:程序博客网 时间:2024/04/29 09:21

派生类可以继承基类中的非私有函数成员,当然也就可以继承其中非私有的被重载的函数。如下:

【参考代码】

class Base { public:  void print() {    cout << "print() in Base." << endl;  }  void print(int a) {    cout << "print(int a) in Base." << endl;  }  void print(string s) {    cout << "print(string s) in Base." << endl;  }};class Derived : public Base { };int main() {  Derived d;  d.print();  d.print(10);  d.print("");  return 0;}
【运行结果】
print() in Base.print(int a) in Base.print(string s) in Base.

现在,我们想要在派生类中重写其中的一个重载函数:

class Derived : public Base { public:  void print() {    cout << "Rewrite print() in Derived." << endl;  }};
这样是不是就可以了呢? 我们来运行一下:

【运行结果】

reload_test.cc: In function ‘int main()’:reload_test.cc:39: error: no matching function for call to ‘Derived::print(int)’reload_test.cc:21: note: candidates are: void Derived::print()reload_test.cc:40: error: no matching function for call to ‘Derived::print(const char [1])’reload_test.cc:21: note: candidates are: void Derived::print()


结果出错了,显示说匹配不到后两种情况,这是为什么呢?

下面一段内容来自 C++ Primer:

理解 C++ 中继承层次的关键在于理解如何确定函数调用。确定函数调用遵循以下四个步骤:

1. 首先确定进行函数调用的对象、引用或指针的静态类型。

2. 在该类中查找函数,如果找不到,就在直接基类中查找,如此循着类的继承链往上找,直到找到该函数或者查找完最后一个类。如果不能在类或其相关基类中找到该名字,则调用是错误的。

3. 一旦找到了该名字,就进行常规类型检查,查看如果给定找到的定义,该函数调用是否合法。

4. 假定函数调用合法,编译器就生成代码。如果函数是虚函数且通过引用或指针调用,则编译器生成代码以确定根据对象的动态类型运行哪个函数版本,否则,编译器生成代码直接调用函数。

         原来,C++中,每个类都记录着在该类中定义的函数名及类型信息,当发生函数调用时,编译器先按函数名查找,如果在该类中查不到与之匹配的函数名,则向其父类查找,依次向上递归,直至函数名匹配成功,然后进行参数类型等信息的匹配;或者查到最顶层仍未匹配到相应的函数名。

         所以,当我们在派生类中没有重写重载函数之一的时候,在派生类中调用的重载函数是在其基类中查到的,因此,调用可以成功;然而,当我们仅重写了其中的一个重载函数时,在做函数名匹配时,在本类中就可以匹配到了,就不会向其父类查找了。而在派生类中,仅记录了这个被重写的函数的信息,当然也就没有另外两个重载函数的一些了,因此就导致了上述错误的出现了。  换句话说,派生类中的函数会将其父类中的同名函数屏蔽掉。

        因此,如果派生类想通过自身类型使用的基类中重载版本,则派生类必须要么重定义所有重载版本,要么一个也不重定义。

        那么,如果在派生类中需要且仅需要重写其中一个重载函数,必须得把其它重载函数都重定义吗?有没有简便的方法,仅重定义我们想要改变的那个,其它的还是从父类继承呢。答案是肯定的,有,而且还可以有不同的方式:

一、通过using在派生类中为父类函数成员提供声明:

         前面知道,因为派生类重写的函数名屏蔽了父类中的同名函数,那么我们可以通过using来为父类函数提供声明;这样,派生类不用重定义所继承的每一个基类版本,它可以为重载成员提供 using声明。一个 using 声明只能指定一个名字,不能指定形参表,因此,为基类成员函数名称而作的 using 声明将该函数的所有重载实例加到派生类的作用域。将所有名字加入作用域之后,派生类只需要重定义本类型确实必须定义的那些函数,对其他版本可以使用继承的定义。

上面说了那么多,不知道说明白了没有,不过,看了下面的例子,你就会豁然开朗: so easy!

class Derived : public Base { public:  using Base::print;  void print() {    cout << "print() in Derived." << endl;  }};
仅仅需要加入  using Base::print;  问题便解决了:

【运行结果】

print() in Derived.print(int a) in Base.print(string s) in Base.

二、通过基类指针调用


        在调用被屏蔽的重载函数时,可以不直接通过派生类对象调用,而是通过基类指针指向派生类对象,通过基类指针进行调用,这样就会直接在基类中进行查找函数名,从而可以匹配并进行类型匹配。

int main() {  Derived d;  Base* bp = &d;   d.print();  bp->print(10);  bp->print("");  return 0;}
【运行结果】
print() in Derived.print(int a) in Base.print(string s) in Base.

        但是这样就有两种调用方式,看起来很不舒服,而且容易弄错。那么把在派生类中需要重载的那个版本相应地在基类中声明为vitual,从而可以实现动态绑定,就能统一的使用基类指针来调用了:

【参考代码】

class Base { public:  virtual void print() {    cout << "print() in Base." << endl;  }  void print(int a) {    cout << "print(int a) in Base." << endl;  }  void print(string s) {    cout << "print(string s) in Base." << endl;  }};class Derived : public Base { public:  void print() {    cout << "print() in Derived." << endl;  }};int main() {  Derived d;  Base* bp = &d;   bp->print();  bp->print(10);  bp->print("");  return 0;}
【运行结果】
print() in Derived.print(int a) in Base.print(string s) in Base.




           原创文章,转载请注明: 转载自  

IIcyZhao's Road

          本文链接地址: http://blog.csdn.net/iicy266/article/details/11906697



原创粉丝点击