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