虚函数 (Virtual Functions)
C++ 中的虚函数是类的成员函数,它在基类中使用 virtual
关键字声明,意在被派生类重写。当通过指向基类的指针或引用调用一个虚函数时,C++ 运行时会根据对象的实际类型来决定调用哪个版本的函数,这种行为称为多态。
#include <iostream>
// 基类
class Animal {
public:
virtual void makeSound() {
std::cout << "动物发出声音" << std::endl;
}
};
// 派生类 Dog
class Dog : public Animal {
public:
void makeSound() override {
std::cout << "汪汪汪" << std::endl;
}
};
// 派生类 Cat
class Cat : public Animal {
public:
void makeSound() override {
std::cout << "喵喵喵" << std::endl;
}
};
int main() {
Animal* animalPtr;
Dog dog;
Cat cat;
animalPtr = &dog; // 父类指针指向子类对象
animalPtr->makeSound(); // 通过基类指针调用派生类对象的虚函数,会根据对象的实际类型调用对应版本的函数
animalPtr = &cat;
animalPtr->makeSound(); // 同样,通过基类指针调用另一个派生类对象的虚函数,会根据对象的实际类型调用对应版本的函数
return 0;
}
在子类的重写基类的虚函数 makeSound
过程中,不必再加virtual关键字。
输出结果如下:
在这个示例中,Animal 是一个基类,Dog 和 Cat 是它的两个派生类。它们都重写了基类的虚函数 makeSound
。在 main
函数中,我们通过指向基类的指针 animalPtr
分别指向 Dog 对象和 Cat 对象,并通过这个指针调用 makeSound
函数。由于 makeSound
是虚函数,因此会根据对象的实际类型来决定调用哪个版本的函数,从而展示了多态的特性。
纯虚函数
为什么需要纯虚函数
虚函数允许在派生类中重写函数,但不强制派生类重写。
而纯虚函数则强制派生类必须对其进行重写,以提供具体的实现。
这种机制用于定义一个接口,当我们只想规定派生类应该做什么,但不决定如何去做时。这通常用于设计一个公共接口或合同(contract),所有派生类都必须遵守这个接口。
纯虚函数
纯虚函数是在基类中声明但没有定义的虚函数,它们不需要提供函数体,而是通过在函数声明的结尾加上 "= 0" 来指示编译器这是一个纯虚函数。
以下是纯虚函数的语法示例:
class AbstractBase {
public:
// 纯虚函数的声明
virtual void pureVirtualFunction() = 0;
};
class Derived : public AbstractBase {
public:
// 继承自抽象基类的派生类必须实现纯虚函数
// 可以不用加virtual
void pureVirtualFunction() override {
// 实现纯虚函数的具体逻辑
}
};
抽象类
抽象类是包含至少一个纯虚函数的类。
它们的主要目的是作为基类使用,从而为派生类提供一个统一的接口。我们不能直接实例化抽象类,它们的存在是为了被继承并实现具体的功能。
下面的例子中,AbstractDrinking
类是一个抽象类,因为它包含了四个纯虚函数:
//抽象类定义
class AbstractDrinking
{
public:
virtual void Boil() = 0;
virtual void Brew() = 0;
virtual void PourInCup() = 0;
virtual void PutSomething() = 0;
//制作饮品的模板方法
void makeDrink()
{
Boil(); // 烧水
Brew(); // 冲泡
PourInCup(); // 倒入杯中
PutSomething(); //加入辅料
}
};
这个抽象类为制作饮料定义了一个模板方法 makeDrink()
,它按照特定的步骤制作一种饮料。然而,具体的步骤,如煮水(Boil)、冲泡(Brew)等,依赖于具体的饮料类型,因此通过纯虚函数声明,留给子类去实现。
在本文例子中,Coffee
和 Tea
类继承自 AbstractDrinking
类,并提供了纯虚函数的具体实现:
class Coffee : public AbstractDrinking
{
// 煮水
// 在C++中,当子类继承自一个包含纯虚函数的抽象基类时,
// 并且要重写这些纯虚函数时,并不需要再次使用 virtual 关键字进行修饰。
void Boil()
{
cout << "煮水" << endl;
}
//冲泡
virtual void Brew()
{
cout << "冲泡咖啡" << endl;
}
//倒入杯中
virtual void PourInCup()
{
cout << "倒入杯中" << endl;
}
//加入辅料
virtual void PutSomething()
{
cout << "加糖和牛奶" << endl;
}
};
class Tea : public AbstractDrinking
{
// 煮水
// 在C++中,当子类继承自一个包含纯虚函数的抽象基类时,
// 并且要重写这些纯虚函数时,并不需要再次使用 virtual 关键字进行修饰。
void Boil()
{
cout << "煮山泉水" << endl;
}
//冲泡
virtual void Brew()
{
cout << "冲泡茶叶" << endl;
}
//倒入杯中
virtual void PourInCup()
{
cout << "倒入茶碗中" << endl;
}
//加入辅料
virtual void PutSomething()
{
cout << "加桂花" << endl;
}
};
每个子类都根据自己的类型定制了制作饮料的具体步骤。例如,Coffee
类使用普通水煮沸,而 Tea
类则用山泉水。
利用多态制作饮料
多态允许我们使用基类的指针或引用,根据对象的实际类型调用相应的函数。这是通过使用基类指针调用 makeDrink()
函数实现的。
// 制作函数
void doWork(AbstractDrinking* abs)
{
abs->makeDrink();
delete abs; // 释放
}
void test01()
{
// 制作咖啡
doWork(new Coffee);
cout << "----------------" << endl;
// 制作茶叶
doWork(new Tea);
}
int main()
{
test01();
}
在 test01()
函数中,我们通过传递 Coffee
或 Tea
对象的地址给 doWork
函数演示了多态性。通过使AbstractDrinking
的指针指向不同的子类对象,我们可以在不知道对象具体类型的情况下制作不同的饮料。