您的位置:首页 > 编程语言 > C语言/C++

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

2013-09-22 19:00 387 查看
派生类可以继承基类中的非私有函数成员,当然也就可以继承其中非私有的被重载的函数。如下:

【参考代码】

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c++ 编程 继承