文章目录
前言:
在C++中多态性是一个核心概念,它允许我们通过不同的方式实现相同的接口。重载(Overloading)、覆盖(Overriding,也被称为重写)和隐藏(Hiding)是实现多态性的三种主要手段。尽管它们名称相似,但在具体实现和用途上存在显著差异。下面将对这三种机制进行详细探讨。
一、重载、覆盖和隐藏
1、重载(overload)
1.1、定义
函数重载允许在同一作用域内声明多个同名但参数列表不同的函数 的特性,是C++实现静态多态 (编译期)的形式。这里的参数列表不同,可以是指参数的数量不同、参数的类型不同,或者参数的顺序不同。
注意: 函数的返回类型并不参与重载的区分。
1.2、使用 const
关键字
-
普通函数使用
const
关键字:两个同名的函数,一个函数的参数类型是
int
,另一个函数的参数类型是int &
,则这两个函数构成重载,例如:cppvoid show(const string &str) { cout << str << endl; } void show(string &str) { cout << str << endl; }
-
成员函数使用
const
关键字两个同名的成员函数,具有相同的参数列表,但是这两个成员函数的
const
性不同,这两个成员函数不构成重载,例如:cppclass Base { public: void display() { cout << "display" << endl; } void display() const { cout << "display const" << endl; } };
1.3、实现原理
C++函数重载的实现原理主要依赖于编译器的名称修饰 技术,编译器在编译阶段会根据函数的参数列表生成不同的修饰名,这些修饰名包含了函数的名称、参数类型、参数数量以及调用约定等信息。这样,即使函数名相同,但由于参数列表不同,编译器也能为它们生成唯一的修饰名,从而在链接阶段正确区分和调用相应的函数。
2、覆盖(override)
2.1、定义
覆盖是指派生类中的成员函数重新定义基类中的虚函数 。当派生类中存在一个与基类虚函数同名、参数列表相同且返回类型相同的成员函数时,就发生了覆盖。例如:
cpp
class Base {
public:
virtual void show() {
cout << "Base class" << endl;
}
};
class Derived : public Base {
public:
void show() override { // 使用override关键字明确表示这是一个覆盖操作
cout << "Derived class" << endl;
}
};
2.2、覆盖的条件
- 继承关系: 覆盖首先要求有继承关系,即派生类必须是从基类派生出来的。
- 虚函数: 基类中的函数必须被声明为虚函数。
- 函数签名相同: 派生类中的函数必须与基类中的虚函数具有相同的函数签名,包括函数名、参数列表以及返回类型。
- 访问权限: 派生类中的覆盖函数可以有不同的访问权限(如从
public
变为protected
或private
),但这通常不是推荐的做法,因为它可能会影响代码的可读性和可维护性。
2.3、override
关键字
在C++11及以后的版本中,
override
关键字被引入,用于明确表示一个派生类成员函数是对基类虚函数的覆盖。使用override
关键字可以帮助编译器检查函数签名的匹配性,从而避免一些常见的错误,如函数隐藏或参数不匹配等。例如:
cpp
#include <iostream>
using namespace std;
class Base {
public:
virtual void show() {
cout << "Base class show function" << endl;
}
virtual int getValue() const {
return 42;
}
};
class Derived : public Base {
public:
void show() override { // 明确表示这是对Base类中show函数的覆盖
cout << "Derived class show function" << endl;
}
int getValue() const override { // 明确表示这是对Base类中getValue函数的覆盖
return 100;
}
// 如果下面这个函数没有使用override关键字,并且Base类中有一个名为print的非虚函数,
// 则它不会覆盖Base::print,而是会隐藏它。但在这个例子中,我们假设Base类没有print函数。
// void print() override { // 错误:Base类中没有名为print的虚函数来覆盖
// cout << "Derived class print function" << endl;
// }
};
int main() {
Base* b = new Derived();
b->show(); // 调用Derived类中的show函数
cout << "Value: " << b->getValue() << endl; // 调用Derived类中的getValue函数
delete b;
return 0;
}
3、隐藏(hiding)
3.1、定义
隐藏发生在继承关系中,当派生类中的函数或成员变量与基类中的函数或成员变量同名时,派生类的成员会隐藏基类的同名成员。这意味着,当通过派生类的对象、指针或引用来访问这些同名成员时,将只会看到派生类中的成员,而基类中的同名成员将被遮蔽。
3.2、隐藏的条件
- 同名: 派生类中的成员与基类中的成员必须同名。
- 不同作用域: 隐藏发生在不同的作用域中,即基类和派生类。
- 函数参数列表可以不同: 对于函数来说,即使参数列表不同,只要函数名相同,也会导致基类函数被隐藏。这与重载不同,重载发生在同一作用域内,且要求参数列表不同。
- 基类函数是否为虚函数: 无论基类函数是否为虚函数,只要满足上述条件,都可能导致隐藏。但需要注意的是,如果基类函数是虚函数且派生类希望覆盖它,则应该使用
override
关键字来明确指示,以避免隐藏的发生。
3.3、隐藏与覆盖的区别
- 发生条件: 隐藏发生在同名成员之间,无论它们是否在同一作用域内。而覆盖则要求基类函数必须是虚函数,且派生类函数与基类函数具有相同的函数签名。
- 作用域: 隐藏涉及不同作用域中的同名成员,而覆盖则发生在继承关系中,但要求基类函数是虚函数。
- 编译器处理: 隐藏是在编译时确定的,编译器会根据作用域规则来选择要访问的成员。而覆盖则涉及运行时的动态绑定,即根据对象的实际类型来选择要调用的函数。
3.4、示例
cpp
#include <iostream>
using namespace std;
class Base {
public:
void show() {
cout << "Base class show function" << endl;
}
void fun(double d) {
cout << "Base class fun function with double" << endl;
}
};
class Derived : public Base {
public:
void show(int i) { // 隐藏了Base类中的show函数
cout << "Derived class show function with int" << endl;
}
void fun(int i) { // 隐藏了Base类中的fun函数
cout << "Derived class fun function with int" << endl;
}
};
int main() {
Derived d;
d.show(10); // 调用Derived类中的show函数(隐藏了Base类的show函数)
// d.show(); // 错误:没有匹配的函数来调用(因为Base类的show函数被隐藏了)
d.fun(3.14); // 错误:没有匹配的函数来调用(因为Base类的fun(double)函数被隐藏了)
d.fun(10); // 调用Derived类中的fun函数(隐藏了Base类的fun函数)
Base* b = &d;
b->show(); // 调用Base类中的show函数(因为通过基类指针调用)
// b->fun(3.14); // 正确:调用Base类中的fun函数(因为通过基类指针调用,且参数匹配)
// b->fun(10); // 错误:没有匹配的函数来调用(因为通过基类指针调用,且Base类中没有fun(int)函数)
return 0;
}