C++ 基础

C++ 主页 C++ 概述 C++ 环境设置 C++ 基本语法 C++ 注释 C++ Hello World C++ 省略命名空间 C++ 标记 C++ 常量/字面量 C++ 关键字 C++ 标识符 C++ 数据类型 C++ 数字数据类型 C++ 字符数据类型 C++ 布尔数据类型 C++ 变量类型 C++ 变量作用域 C++ 多变量 C++ 基本输入/输出 C++ 修饰符类型 C++ 存储类 C++ 数字 C++ 枚举 C++ 枚举类 C++ 引用 C++ 日期和时间

C++ 运算符

C++ 运算符 C++ 算术运算符 C++ 关系运算符 C++ 逻辑运算符 C++ 位运算符 C++ 赋值运算符 C++ sizeof 运算符 C++ 条件运算符 C++ 逗号运算符 C++ 成员运算符 C++ 强制类型转换运算符 C++ 指针运算符 C++ 运算符优先级 C++ 一元运算符

C++ 控制语句

C++ 决策语句 C++ if 语句 C++ if else 语句 C++ 嵌套 if 语句 C++ switch 语句 C++ 嵌套 switch语句 C++ 循环类型 C++ while 循环 C++ for 循环 C++ do while 循环 C++ Foreach 循环 C++ 嵌套循环 C++ break 语句 C++ continue 语句 C++ goto 语句

C++ 字符串

C++ 字符串 C++ 循环遍历字符串 C++ 字符串长度 C++ 字符串连接 C++ 字符串比较

C++ 函数

C++ 函数 C++ 多函数参数 C++ 递归函数 C++ 返回值 C++ 函数重载 C++ 函数重写 C++ 默认参数

C++ 数组

C++ 数组 C++ 多维数组 C++ 指向数组的指针 C++ 将数组传递给函数 C++ 从函数返回数组

C++ 结构 &联合

C++ 结构 C++ 联合

C++ 指针

C++ 指针 C++ 解引用 C++ 修改指针

C++ 类和对象

C++ 面向对象 C++ 类 &对象 C++ 类成员函数 C++ 类访问修饰符 C++ 静态类成员 C++ 静态数据成员 C++ 静态成员函数 C++ 内联函数 C++ this 指针 C++ 友元函数 C++ 指向类的指针

C++ 构造函数

C++ 构造函数 &析构函数 C++ 默认构造函数 C++ 参数化构造函数 C++ 复制构造函数 C++ 构造函数重载 C++ 带默认参数的构造函数 C++ 委托构造函数 C++ 构造函数初始化列表 C++ 使用构造函数动态初始化

C++ 继承

C++ 继承 C++ 多重继承 C++ 多级继承

C++ 面向对象

C++ 重载 C++ 多态性 C++ 抽象 C++ 封装 C++ 接口 C++ 虚函数 C++ 纯虚函数与抽象类

C++ 文件处理

C++ 文件和流 C++ 文件读取

C++ 进阶

C++ 异常处理 C++ 动态内存 C++ 命名空间 C++ 模板 C++ 预处理器 C++ 信号处理 C++ 多线程 C++ Web 编程 C++ 套接字编程 C++ 并发 C++ 高级概念 C++ Lambda 表达式 C++ unordered_multiset

C++ 实用资源

C++ 问答 C++ 快速指南 C++ 速查表 C++ STL 教程 C++ 标准库 C++ 实用资源 C++ 讨论


C++ 中的函数重写

函数是包含指令的代码块集合,用于执行特定任务。它可以根据需求进行重用,从而将复杂问题分解成更小、更易于管理的部分。

什么是 C++ 中的函数重写?

函数重写是面向对象编程的一个概念,它允许派生类重新定义基类中已定义的函数。

这里,方法的名称和参数保持不变,但派生类会根据其特定需求改变其行为。

示例

让我们考虑这两个函数;一个是基类 (a),另一个是派生类 (b),函数 (c) 是 main(),我们在这里实现函数重写 -

Function (a)

class base {
public:
   void notice( )
   cout << "This is my Base Class" ;
}

Function (b)

class derived: public base {
   public:
      void notice( )
      cout << "This is my Derived Class";
}

Function (c)

void main ( ) {
    // 为基类创建一个对象并调用它
    base b;
    b.notice();
    // 为派生类创建一个对象并调用它
    derived d;
    d.notice();   
}

覆盖说明

  • 这里,我们分别创建了"函数 (a)"和"函数 (b)"的基类和派生类对象,并在函数 (c) 中分别调用它们,如下所示:"b.notice()"和"d.notice()"。
  • 在这种情况下,对于派生类,d.notice() 将首先执行,因为它代表函数的最新版本。
  • 但是,如果我们创建基类的对象,如函数 (c)"b.msg()"所示,并调用它,它将使用基类中的原始版本。

简而言之,当在派生类中重新定义基类的功能时,就会发生函数覆盖。当创建派生类的对象时,它将调用派生类中更新后的函数,这意味着基类函数 (a) 被派生类函数 (b) 重写。

函数重写是面向对象编程中的一个重要概念,它支持多态性和动态绑定。

函数重写示例

以下是一个简单的示例,说明了重写的工作原理

#include <iostream>
using namespace std;

// 基类
class Shape {
   public:
      // 需要重写的虚方法
      virtual void draw() const {
         cout << "Drawing a shape" << endl;
      }
};

// 派生类 Circle
class Circle : public Shape {
   public:
      // 重写基类方法
      void draw() const override {
         cout << "Drawing a circle" << endl;
      }
};

// 派生类 Square
class Square : public Shape {
   public:
      // 重写基类方法
      void draw() const override {
         cout << "Drawing a square" << endl;
      }
};

// 主函数
int main() {
    Shape* shapePtr;
    Circle circle;
    Square square;
    
    // 指向 Circle 并调用 draw()
    shapePtr = &circle;
    shapePtr->draw(); // 输出:绘制一个圆形
    
    // 指向 Square 并调用 draw()
     shapePtr = &square;
    shapePtr->draw(); // 输出:绘制一个正方形
    
    return 0;
}

输出

Drawing a circle
Drawing a square

函数覆盖与函数重载

虽然函数覆盖和函数重载都是 C++ 面向对象编程中的重要概念,但它们的用途却有所不同。

函数覆盖允许派生类为其先前定义的基类获取方法的新实现,因为具有不同的作用域(基类和派生类),在运行时解析多态性(动态绑定),它仅在存在继承的情况下发生,并且只能覆盖一次,执行速度相对较慢。

而函数重载允许您在同一作用域内创建具有相同名称但不同参数列表的多个函数。它在编译时解析多态性(静态绑定),其中继承的存在并不重要。这些函数可以多次重载,执行速度往往相对较快。

高级重写概念

以下是涵盖高级重写概念的其他子主题列表 -

1. 虚析构函数

虚析构函数确保在通过基类指针删除对象时执行派生类的析构函数。使用虚析构函数进行函数重写,通过确保通过基类指针正确删除对象,可以避免资源泄漏和不可预测的行为。

示例

#include <iostream>
using namespace std;

class BaseClass {
   public:
      virtual ~BaseClass() {  // 虚拟析构函数
         cout << "BaseClass destructor" << endl;
      }
};

class DerivedClass : public BaseClass {
   public:
      ~DerivedClass() override {  // 重写析构函数
         cout << "DerivedClass destructor" << endl;
      }
};

int main() {
    BaseClass* a = new DerivedClass();
    // 调用 DerivedClass 的析构函数,然后
    // 调用 BaseClass 的析构函数
    delete a;
    return 0;
}

输出

DerivedClass destructor
BaseClass destructor

2. 协变返回值类型

协变返回类型允许派生类重写方法并返回比基类方法更具体的类型。这意味着派生类可以返回指向派生类型(而非基类型)的指针或引用。函数重写提高了面向对象编程的灵活性和精度。

注意

在派生类中,返回类型必须是指向从基类返回类型派生的类型的指针或引用。

示例

#include <iostream>
using namespace std;
class Vehicle {
   public:
      // Final 方法
      virtual void honk() final {
         cout << "Vehicle honk: Beep beep!" << endl;
      }
};

class SportsCar : public Vehicle {
   // 此处无法覆盖 honk()
};

int main() {
   Vehicle* v = new SportsCar();
   v->honk(); // 调用车辆的 honk() 方法
   delete v;
   return 0;
}

输出

Vehicle honk: Beep beep!

3. 覆盖和 final 关键字的替代词

`final` 关键字会阻止对类进行任何进一步的子类化或对方法进行覆盖。

使用 `final` 关键字进行函数覆盖非常重要,因为它可以保证类或方法无法进一步更改或扩展。

示例

#include <iostream>
using namespace std;

class Subject {
   public:
      // Final 方法
      virtual void examType() final {
         cout << "This subject has a written exam." << endl;
      }
};

class Math : public Subject {
   // 此处无法覆盖 examType()
};

int main() {
   Subject* s = new Math();
   s->examType(); // 调用 Subject 的 examType() 方法
   delete s;
   return 0;
}

输出

This subject has a written exam.

4. 虚拟继承

C++ 中的虚拟继承解决了多重继承中出现的问题,尤其是菱形继承问题。它确保当一个类继承自具有共同祖先的多个基类时,只会创建该共同基类的一个实例。

虚拟继承确保当多个派生类在层次结构中共享该基类时,只使用基类的一个副本。

示例

#include <iostream>
using namespace std;

class Base {
   public:
      void present() { cout << "Display from Base class" << endl; }
};

class A : virtual public Base { };
class B : virtual public Base { };

class Final : public A, public B {
   public:
      void get() { cout << "Display from Final class" << endl; }
};

int main() {
    Final obj;
    // 显示:来自基类的显示
    obj.present();
    // 显示:来自 Final 类的显示
    obj.get();
    return 0;
}

输出

Display from Base class
Display from Final class

函数重写的优势

1. 多态性

重写通过将不同派生类的对象视为基类的实例来实现多态性。这允许在运行时进行动态方法绑定,并根据对象类型选择正确的方法实现,从而增强灵活性和适应性,使其成为多态性的基本组成部分。

2. 代码可重用性

开发人员可以通过从基类继承方法并在派生类中自定义它们来利用现有代码。这种方法可以构建更精简、更有条理的代码结构。

3. 可维护性

重写通过将各种功能封装在不同的类中来促进模块化设计。这种方法简化了代码的理解、维护、更新和扩展。

4.设计模式

重写在模板方法模式中扮演着关键角色,它通过在基类中定义算法的整体结构,同时允许子类重写特定步骤。

策略模式利用重写来封装各种算法,并允许它们在运行时进行交换。

5. 内存管理

在使用继承和动态内存分配时,虚拟析构函数对于正确的内存管理至关重要。在派生类中重写析构函数可确保基类和派生类分配的资源被正确释放,从而防止内存泄漏并确保资源干净地释放。