一、函数的概念
函数是一组带有内部指令的代码块,用于特定的任务。它的设计目的是为了根据需要重用,使复杂问题可以分解为更小和更易管理的部分。
二、C++中的函数重写
函数重写是面向对象编程的一个概念,它允许派生类重新定义基类中已经定义的函数。
在这里,方法的名字和参数保持不变,但派生类改变了其行为以适应他们的具体需求。
示例
考虑下面这两个函数:一个是基类(a),另一个是派生类(b),以及主函数(c),我们在其中实现了重写:
函数 (a)
class base {
public:
void notice() {
cout << "这是我的基类";
}
};
函数 (b)
class derived: public base {
public:
void notice() {
cout << "这是我的派生类";
}
};
函数 (c)
void main () {
base b;
b.notice();
derived d;
d.notice();
}
重写解释
这里,我们创建了基类和派生类的对象,并分别在函数(c)中调用了它们,如 "b.notice()" 和 "d.notice()"。 在这种情况下,对于派生类 "d.notice()" 将会被优先执行,因为它代表了最新或更新的版本。 然而,如果我们像在函数(c)中那样创建基类的对象并调用它 "b.notice()",它将会使用来自基类的原始版本。 简而言之,函数重写发生在派生类重新定义了基类的功能时。当创建了一个派生类的对象时,它将调用派生类中的更新后的函数,这意味着基类函数(a)被派生类函数(b)重写了。
函数重写是面向对象编程的一个重要概念,它支持多态性和动态绑定。
函数重写的示例
下面是一个简单的示例,说明了重写是如何工作的:
#include <iostream>
using namespace std;
class Shape {
public:
virtual void draw() const {
cout << "绘制一个形状" << endl;
}
};
class Circle : public Shape {
public:
void draw() const override {
cout << "绘制一个圆" << endl;
}
};
class Square : public Shape {
public:
void draw() const override {
cout << "绘制一个方形" << endl;
}
};
int main() {
Shape* shapePtr;
Circle circle;
Square square;
shapePtr = &circle;
shapePtr->draw();
shapePtr = □
shapePtr->draw();
return 0;
}
输出
绘制一个圆
绘制一个方形
三、函数重写与函数重载
尽管函数重写和函数重载都是 C++ 面向对象编程中的关键概念,但它们各自有不同的用途。
函数重写允许派生类获取对其基类中先前定义的方法的新实现,这些方法具有不同的作用域(基类和派生类)。它在运行时多态性(动态绑定)中解决,只有在继承存在的情况下才会发生,并且只能重写一次,执行速度相对较慢。
而函数重载则允许你在同一个作用域内创建多个具有相同名称但参数列表不同的函数。它在编译时多态性(静态绑定)中解决,继承的存在与否并不重要,这些函数可以多次重载,并且执行速度相对更快。
四、高级重写概念
1. 虚析构函数
虚析构函数确保当通过基类指针删除对象时,派生类的析构函数被执行。函数重写与虚析构函数一起使用避免了资源泄漏和不可预测的行为,确保通过基类指针正确删除对象。
示例
#include <iostream>
using namespace std;
class BaseClass {
public:
virtual ~BaseClass() {
cout << "基类析构函数" << endl;
}
};
class DerivedClass : public BaseClass {
public:
~DerivedClass() override {
cout << "派生类析构函数" << endl;
}
};
int main() {
BaseClass* a = new DerivedClass();
delete a;
return 0;
}
输出
派生类析构函数
基类析构函数
2. 协变返回类型
协变返回类型允许派生类重写一个方法并返回一个比基类方法更具体的类型。这意味着派生类可以返回指向更派生类型的指针或引用,而不仅仅是基类型。函数重写提高了面向对象编程的灵活性和精确性。
注意
在派生类中,返回类型必须是指向或引用从基类返回类型派生的类型。
示例
#include <iostream>
using namespace std;
class Vehicle {
public:
virtual void honk() final {
cout << "车辆鸣笛: 嘟嘟!" << endl;
}
};
class SportsCar : public Vehicle {
};
int main() {
Vehicle* v = new SportsCar();
v->honk();
delete v;
return 0;
}
输出
车辆鸣笛: 嘟嘟!
3. 使用 final 关键字的重写与禁止进一步重写
final
关键字阻止进一步的类子类化或方法重写。
函数重写与 final
关键字一起使用很重要,因为它保证了类或方法不能被进一步更改或扩展。
示例
#include <iostream>
using namespace std;
class Subject {
public:
virtual void examType() final {
cout << "这个科目有书面考试。" << endl;
}
};
class Math : public Subject {
};
int main() {
Subject* s = new Math();
s->examType();
delete s;
return 0;
}
输出
这个科目有书面考试。
4. 虚继承
C++中的虚继承解决了多重继承带来的问题,特别是菱形问题。它确保当一个类从多个基类继承,而这些基类又有一个共同祖先时,只有一个该共同基类的实例被创建。
虚继承确保了当多个派生类在一个层次结构中共享那个基类时,只使用该基类的一个副本。
示例
#include <iostream>
using namespace std;
class Base {
public:
void present() { cout << "显示来自基类" << endl; }
};
class A : virtual public Base { };
class B : virtual public Base { };
class Final : public A, public B {
public:
void get() { cout << "显示来自 Final 类" << endl; }
};
int main() {
Final obj;
obj.present();
obj.get();
return 0;
}
输出
显示来自基类
显示来自 Final 类
五、函数重写的优势
-
多态性 重写通过让不同派生类的对象被视为基类的实例来支持多态性。这允许基于对象类型在运行时动态绑定正确的实现,从而增强灵活性和适应性,使其成为多态性的基本组成部分。
-
代码复用 开发者可以通过继承基类中的方法并在派生类中对其进行定制来利用现有代码。这种方法促进了更精简和有组织的代码结构。
-
可维护性 重写通过将各种功能封装在不同的类中来促进模块化设计。这种方法简化了理解和维护代码,更新和扩展代码也变得更简单。
-
设计模式 重写在模板方法模式中起着关键作用,通过在基类中定义算法的整体结构,同时允许子类重写特定步骤。 策略模式利用重写将多种算法封装起来,并允许在运行时进行交换。
-
内存管理 在使用继承和动态内存分配时,虚析构函数对于适当的内存管理至关重要。在派生类中重写析构函数确保由基类和派生类分配的资源被正确释放,防止内存泄漏并确保资源的干净释放。