C++ 中重载、重写、隐藏
函数重载 (overload)
函数重载有几个特征,分别是:
(1) 在同一作用域 (在同一个类中)
(2) 函数名称相同
(3) 参数列表不同(包括类型、数目、顺序)
(4) virtual 关键字可有可无
注: const成员函数(const位于函数体前面)和非const成员函数是两种函数,哪怕参数列表也相同,但也不是函数重载,因为前者表明在函数体内无法修改对象的成员数据,也不能调用非const成员函数。
函数重写 (override)
函数重写有几个特征,分别是:
(1) 在不同作用域 (分布在基类和派生类中)
(2) 函数名称相同
(3) 参数列表完全相同
(4) 基类函数必须有virtual关键字,派生类可以不需要有virtual关键字
注:函数重写分为两种: 完全重写和扩展。完全重写时是派生类虚函数不调用基类对应的虚函数;扩展是指派生类虚函数调用基类虚函数之后再续写自己剩余的功能
函数隐藏 (overwrite)
函数隐藏是前置条件是: 基类和派生类的函数同名。分参数列表相同和不同的情况,如下:
(1) 参数列表不同,无论是否有virtual 关键字,基类同名函数在派生类会被隐藏
(2) 参数列表相同,但基类函数无virtual 关键字,基类同名函数在派生类也会被隐藏
多态
多态其实分为两种,分为静态多态和动态多态,但一般把动态多态简称为多态。
静态多态:函数重载就是静态多态,在编译期就决定了
动态多态:派生类重写父类虚函数来实现的,运行时根据实际对象来决定调用哪个虚函数
一个例子
#include <iostream>
class CBase {
public:
void Func() { std::cout << "CBase::Func() call" << std::endl; }
virtual void Func(const std::string& strValue)
{
std::cout << "CBase::Func(const std::string&) call: " << strValue << std::endl;
}
virtual void ExtendFunc()
{
std::cout << "CBase::ExtendFunc() call: " << std::endl;
}
void Method() { std::cout << "CBase::Method() call" << std::endl; }
};
class CDerived : public CBase {
public:
void Func() { std::cout << "CDerived::Func() call" << std::endl; }
// 完全重写的方式
void Func(const std::string& strValue) override
{
std::cout << "CDerived::Func(const std::string&) call: " << strValue << std::endl;
}
// 扩展重写的方式
void ExtendFunc() override
{
CBase::ExtendFunc();
std::cout << "CDerived::ExtendFunc() call: " << std::endl;
}
// 仔细看,基类method()并没有被隐藏
virtual void Method() { std::cout << "CDerived::Method() call" << std::endl; }
};
int main()
{
CDerived* pDerived = new CDerived();
CBase* pBase = new CDerived();
pBase->Func();
pBase->Func("pBase");
pBase->ExtendFunc();
pBase->Method();
std::cout << std::endl;
pDerived->Func();
pDerived->Func("pDerived");
pDerived->ExtendFunc();
pDerived->Method();
delete pDerived;
pDerived = nullptr;
delete pBase;
pBase = nullptr;
}
读者可自测结果,后文有个人解析和结果,该部分涉及到虚函数部分的知识。本段代码包含了上面的理论知识,但不包含const成员函数的解释,望悉知。只针对于本篇主题编写的例子,个人觉得隐藏在实际项目中用处不大,因为产生了隐藏可能就说明了类的设计不那么合理,以上仅代表个人愚见。
写在最后
上段运行代码结果如下:
/*
* (以下是输出部分)
*① CBase::Func() call
*② CDerived::Func(const std::string&) call: pBase
*③ CBase::ExtendFunc() call:
*④ CDerived::ExtendFunc() call:
*⑤ CBase::Method() call
*
*⑥ CDerived::Func() call
*⑦ CDerived::Func(const std::string&) call: pDerived
*⑧ CBase::ExtendFunc() call:
*⑨ CDerived::ExtendFunc() call:
*⑩ CDerived::Method() call
*/
CDerived* pDerived = new CDerived(); new的作用是分配内存空间,并初始化之后返回指向对象的指针,所以 pDerived 指向了一个本类对象;
CBase* pBase = new CDerived(); 同理 pBase 指向了一个派生类对象(此时如果派生类重写了基类的虚函数,则会发生运行时调用,即多态)。
pBase->Func(); 因为Func()并不是虚函数,不发生动态调用,只是在派生类被隐藏了,此处是显式调用;
pBase->Func(“pBase”); 发生了动态调用,实际上调用的是派生类的重写的虚函数。
pBase->ExtendFunc(); 跟上面一样发生了动态调用,只不过这里是扩展的方式重写虚函数,上面是完全重写的方式
pBase->Method(); Method()函数在基类是普通函数,在派生类中是虚函数,好像是倒转了似的,但此处并不发生动态调用也不会被派生类所隐藏,是普通调用。
而剩下的
pDerived->Func();
pDerived->Func(“pDerived”);
pDerived->ExtendFunc();
pDerived->Method();
都是关于派生类的普通调用。
因本人水平有限,错误之处在所难免,欢迎评论区交流指正。