在C++中,实现多态的方式就是继承基类,不同的派生类重写相同函数名,不同函数内容的虚函数。下面就是一个非常简单的多态的示例:
class Father {
public:
virtual void func() {
std::cout << "Father's func()" << std::endl;
}
};
class Son : public Father {
public:
void func() override {
std::cout << "Son's func()" << std::endl;
}
};
int main()
{
Father *p = new Son();
p->func();
// 调用 Father 类的 func() 函数
p->Father::func();
delete p;
return 0;
}
我们可以很明显的知道这里的 p->func()得到的结果是“Son's func()”。至于为什么是“Son's func()”,以及底层涉及到的虚函数、虚函数表原理,可以取看看这篇博客,博主讲的非常详细。我也受益良多。
C++虚函数表,虚表指针,内存分布__silverBlack的博客-优快云博客
这时候我就会想,父对象指针能够指向子对象的内存空间,调用子对象的公开成员,那么父对象还能不能调用父类本身的虚函数呢。于是就想到用作用域修饰。p->Father::func()就成功打印了“Father's func()”。
此时又会有一个问题,那就是func是public修饰的,很多时候我们写的成员函数,是用protected、private修饰的。于是我将代码改进,改成以下写法,更贴合实际开发。
class Father {
public:
void runFunc() {
func();
}
protected:
virtual void func() {
std::cout << "Father's func()" << std::endl;
}
};
class Son : public Father {
public:
void runFunc(){
func();
}
protected:
void func() override {
std::cout << "Son's func()" << std::endl;
}
};
int main()
{
Father *p = new Son();
p->runFunc();
// 调用 Father 类的 func() 函数
p->Father::runFunc();
delete p;
return 0;
}
现在我们会发现,无论是 p->runFunc()还是p->Father::runFunc(),最后打印的都是"Son's Func()"。
很奇怪吧。即使加了作用域,还是没办法输出"Father's Func()"。
后来我从虚函数的多继承原理中受到了启发。当派生类继承多个基类,且每个基类中都有各自的虚函数时,派生类和基类共用的虚函数表中,无论声明的时候函数的顺序是怎么样的,最先继承的基类的虚函数都会被放在虚函数表的最上方,然后是第二个基类的虚函数,依此类推。
所以我猜想,在Father类中的runFunc函数中调用的func函数,编译器会优先在虚函数表中从头开始寻找func函数。导致这种现象的发生。
为了避免出现这种情况,在写代码的时候要细心一些,及时的在虚函数调用前加上作用域,如下图:
class Father {
public:
void runFunc() {
Father::func();
}
protected:
virtual void func() {
std::cout << "Father's func()" << std::endl;
}
};
这样写,就可以通过调用Father::runFunc来调用父类的虚函数了。