C/C++ 知识点:重载、覆盖和隐藏

文章目录

前言:

在C++中多态性是一个核心概念,它允许我们通过不同的方式实现相同的接口。重载(Overloading)、覆盖(Overriding,也被称为重写)和隐藏(Hiding)是实现多态性的三种主要手段。尽管它们名称相似,但在具体实现和用途上存在显著差异。下面将对这三种机制进行详细探讨。

一、重载、覆盖和隐藏

1、重载(overload)

1.1、定义

函数重载允许在同一作用域内声明多个同名但参数列表不同的函数 的特性,是C++实现静态多态 (编译期)的形式。这里的参数列表不同,可以是指参数的数量不同、参数的类型不同,或者参数的顺序不同。
注意: 函数的返回类型并不参与重载的区分。

1.2、使用 const 关键字

  • 普通函数使用const关键字:

    两个同名的函数,一个函数的参数类型是int,另一个函数的参数类型是int &,则这两个函数构成重载,例如:

    cpp 复制代码
    void show(const string &str)
    {
        cout << str << endl;
    }
    
    void show(string &str)
    {
        cout << str << endl;
    }
  • 成员函数使用const关键字

    两个同名的成员函数,具有相同的参数列表,但是这两个成员函数的const性不同,这两个成员函数不构成重载,例如:

    cpp 复制代码
    class 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变为protectedprivate),但这通常不是推荐的做法,因为它可能会影响代码的可读性和可维护性。

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;  
}
相关推荐
重生之我是数学王子3 分钟前
QT基础 编码问题 定时器 事件 绘图事件 keyPressEvent QT5.12.3环境 C++实现
开发语言·c++·qt
我们的五年27 分钟前
【Linux课程学习】:进程程序替换,execl,execv,execlp,execvp,execve,execle,execvpe函数
linux·c++·学习
做人不要太理性1 小时前
【C++】深入哈希表核心:从改造到封装,解锁 unordered_set 与 unordered_map 的终极奥义!
c++·哈希算法·散列表·unordered_map·unordered_set
程序员-King.1 小时前
2、桥接模式
c++·桥接模式
chnming19871 小时前
STL关联式容器之map
开发语言·c++
程序伍六七1 小时前
day16
开发语言·c++
小陈phd2 小时前
Vscode LinuxC++环境配置
linux·c++·vscode
火山口车神丶2 小时前
某车企ASW面试笔试题
c++·matlab
是阿建吖!2 小时前
【优选算法】二分查找
c++·算法
Ajiang28247353044 小时前
对于C++中stack和queue的认识以及priority_queue的模拟实现
开发语言·c++