文章目录
前言:
在C++中多态性是一个核心概念,它允许我们通过不同的方式实现相同的接口。重载(Overloading)、覆盖(Overriding,也被称为重写)和隐藏(Hiding)是实现多态性的三种主要手段。尽管它们名称相似,但在具体实现和用途上存在显著差异。下面将对这三种机制进行详细探讨。
一、重载、覆盖和隐藏
1、重载(overload)
1.1、定义
函数重载允许在同一作用域内声明多个同名但参数列表不同的函数的特性,是C++实现静态多态(编译期)的形式。这里的参数列表不同,可以是指参数的数量不同、参数的类型不同,或者参数的顺序不同。
注意: 函数的返回类型并不参与重载的区分。
1.2、使用 const
关键字
-
普通函数使用
const
关键字:两个同名的函数,一个函数的参数类型是
int
,另一个函数的参数类型是int &
,则这两个函数构成重载,例如:void show(const string &str) { cout << str << endl; } void show(string &str) { cout << str << endl; }
-
成员函数使用
const
关键字两个同名的成员函数,具有相同的参数列表,但是这两个成员函数的
const
性不同,这两个成员函数不构成重载,例如:class Base { public: void display() { cout << "display" << endl; } void display() const { cout << "display const" << endl; } };
1.3、实现原理
C++函数重载的实现原理主要依赖于编译器的名称修饰技术,编译器在编译阶段会根据函数的参数列表生成不同的修饰名,这些修饰名包含了函数的名称、参数类型、参数数量以及调用约定等信息。这样,即使函数名相同,但由于参数列表不同,编译器也能为它们生成唯一的修饰名,从而在链接阶段正确区分和调用相应的函数。
2、覆盖(override)
2.1、定义
覆盖是指派生类中的成员函数重新定义基类中的虚函数。当派生类中存在一个与基类虚函数同名、参数列表相同且返回类型相同的成员函数时,就发生了覆盖。例如:
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
关键字可以帮助编译器检查函数签名的匹配性,从而避免一些常见的错误,如函数隐藏或参数不匹配等。例如:
#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、示例
#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;
}
标签:知识点,函数,show,C++,Base,基类,重载,隐藏,cout
From: https://blog.csdn.net/cloud323/article/details/143442377