C++成员函数的重载、覆盖和隐藏
ref: https://blog.csdn.net/wanghuiqi2008/article/details/28419645
ref: https://blog.csdn.net/zgbsoap/article/details/566120
ref: 《高质量程序设计指南 C++/C语言 第3版》
1. 重载与覆盖
成员函数被重载的特征是:
- 具有相同的作用域(即同一个类定义中);
- 函数名字相同;
- 参数类型、顺序或数目不同(包括);
- virtual 关键字可有可无。
覆盖是指派生类重新实现(或者改写)了基类的成员函数,其特征是:
- 不同的作用域(分别位于派生类和基类中);
- 函数名称相同;
- 参数列表完全相同;
- 基类函数必须是虚函数。
#pragma once
#include <iostream>
using namespace std;
// 展示了 Overload 和 Override
class Base
{
public:
void f(int x) { cout << "Base::f(int) " << x << endl; }
// overload Base::f(int x)
void f(float x) { cout << "Base::f(float) " << x << endl; }
virtual void g(void) { cout << "Base::g(void)" << endl; }
};
class Derived : public Base
{
public:
// override Base::g(void)
virtual void g(void) { cout << "Derived::g(void)" << endl; }
};
void test()
{
Derived d;
Base *pb = &d;
pb->f(42); // Base::f(int) 42
pb->f(3.14f); // Base::f(float) 3.14
pb->g(); // Derived::g(void)
}
注:
- virtual 关键字告诉编译器,派生类中相同的成员函数应该放到 vtable 中,并替换基类相应成员函数的槽位;
- 虚函数的覆盖有两种方式:完全重写和扩展。扩展是指派生类虚函数首先调用基类的虚函数,然后在增加新的功能。
2. 令人迷惑的隐藏规则
隐藏是指派生类的成员函数遮蔽了与其同名的基类成员函数,具体规则如下:
- 派生类的函数与基类的函数同名,但是参数列表有所差异,此时,不论有无 virtual 关键字,基类的函数在派生类中将被隐藏(注意别于重载混淆);
- 派生类的函数与基类的函数同名,参数列表也相同,但是基类函数没有 virtual 关键字。此时,基类的函数在派生类中将被隐藏(注意别与覆盖混淆)。
#pragma once
#include <iostream>
using namespace std;
// 展示了 Override 和 Hide
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:
// override Base::f()
virtual void f(float x) { cout << "Derived::f(float) " << x << endl; }
// hide Base::g()
void g(int x) { cout << "Derived::g(int) " << x << endl; }
// hide Base::h()
void h(float x) { cout << "Derived::h(float) " << x << endl; }
};
void test()
{
Derived d;
Base *pb = &d;
Derived *pd = &d;
// Good : behavior depends solely on type of the object
pb->f(3.14f); // Derived::f(float) 3.14
pd->f(3.14f); // Derived::f(float) 3.14
// Bad : behavior depends on type of the pointer
pb->g(3.14f); // Base::g(float) 3.14
pd->g(3.14f); // Derived::g(int) 3 (surprise!)
// Bad : behavior depends on type of the pointer
pb->h(3.14f); // Base::h(float) 3.14 (surprise!)
pd->h(3.14f); // Derived::h(float) 3.14
}
注:如果你确实想使用所谓的“跨越类边界的重载”,可以在派生类定义中的任何地方显示地使用 using 关键字,如下:
class Derived: public Base{
public:
using Base::g;
void g(int x) {...}
}
3. 摆脱隐藏
隐藏规则引起不少麻烦。下面代码中,程序员可能期望语句 pd->f(10)
调用 Base::f(int)
,但是 Base::f(int)
不幸被Derived::f(char)
隐藏了。由于数字 10 不能被隐式地转换为字符串类型,所以在编译时出错。
#pragma once
#include <iostream>
using namespace std;
// 展示了 Hide 所带来的麻烦
class Base
{
public:
void f(int x) { cout << x << endl; }
};
class Derived : public Base
{
public:
// hide Base::f(int)
void f(char *str) { cout << str << endl; }
// 摆脱隐藏法一
//using Base::f;
// 摆脱隐藏法二
//void f(int x) { Base::f(x); }
};
void test(void)
{
Derived *pd = new Derived;
pd->f(10); // error 编译时报错,因为Base中函数被隐藏
}
如果语句 pd->f(10)
确实想调用函数 Base::f(int)
,那么有两个办法:其一就是使用 using 声明;其二就是把类 Derived 修改为如下的样子。
class Derived : public Base
{
public:
// hide Base::f(int)
void f(char *str) { cout << str << endl; }
// 调用传递
void f(int x) { Base::f(x); }
};