欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

C++ 中的虚拟函数、纯虚拟函数和抽象类

最编程 2024-05-23 09:23:25
...

虚函数 (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 的指针指向不同的子类对象我们可以在不知道对象具体类型的情况下制作不同的饮料。

推荐阅读