什么是多态

C++中的多态是面向对象编程的三个特性之一(另外两个分别是封装和继承),多态允许使用统一的接口来操作不同的数据类型,从而增加代码的灵活性和可复用性。
多态分为两种:

  • 编译时多态(静态多态): 通过函数重载和运算符重载实现
  • 运行时多态(动态多态): 通过虚函数和继承实现

运行时多态(动态多态)

当基类中的函数被声明为虚函数时,派生类可以重写该函数,然后通过基类指针或引用来调用该函数,而实际中调用的是派生类重写的函数

虚函数

在基类中,使用virtual声明的成员函数就是虚函数,在派生类中,可以使用override关键字(C++11中引入)来显示的重写虚函数

#include <iostream>
using namespace std;

class Animal {
public:
	virtual void speak() {  // 虚函数
		cout << "animal speaks!" << endl;
	}

	virtual ~Animal() {}  // 虚析构函数
};


class Dog : public Animal {
public:
	void speak() override {  // override关键字是C++11中引入的,C++98/03中是没有override关键字的,使用override关键字也不会出现问题
		cout << "woof woof!" << endl;
	}
};


class Cat : public Animal {
public:
	void speak() override {
		cout << "meow meow!" << endl;
	}
};


int main() {
	Animal* dog = new Dog();
	Animal* cat = new Cat();
	dog->speak();
	cat->speak();

	delete dog;
	delete cat;

	return 0;
}

override关键字使用说明

override关键字是C++11中引入的,在C++98/03中没有,通过函数名称找到对应的重写函数,此时如果函数签名有误,比如参数不一致,相当于派生类中创建了一个新函数,而不是被认为重新来基类的虚函数

class Dog : public Animal {
public:
    // 错误:本想重写speak,但写成了speak(参数不同),没有override关键字,编译器不会报错
    void speak(int volume) {
        cout << "Woof! Woof! with volume " << volume << endl;
    }
};

使用override关键字的一个好处是避免程序产生预料之外的行为,例如上面的代码中参数不同,main函数中调用的将会是基类的speak()方法,与预期行为不符,如果使用了override关键字,编译会报错,这样可以避免不符合预期的行为发生

class Dog : public Animal {
public:
    // 错误:函数签名不匹配,且使用了override,编译器会报错
    void speak(int volume) override {
        cout << "Woof! Woof! with volume " << volume << endl;
    }
};

实际项目中的使用考量

  1. 不使用override关键字
    • 维护老版本C++代码(C++98/03)
    • 简单的个人项目
    • 团队约定不使用C++11特性
  2. 推荐使用override关键字
    • 新项目开发
    • 大型团队协作
    • 需要高质量代码的项目
    • 避免潜在的重写错误

纯虚函数和抽象类

如果基类中的虚函数没有具体实现,可以将其声明为纯虚函数,包含纯虚函数的类称为抽象类(只要有一个函数是纯虚函数,其它函数就算不是纯虚函数,也是一个抽象类)抽象类不能被实例化,之类作为基类被继承。
纯虚函数的一般定义如下:

virtual 返回类型 函数名(参数列表) = 0;

抽象类定义

#include <iostream>

using namespace std;

class Shape {
public:
	// 虚函数
	virtual double area() const = 0;
	virtual double perimeter() const = 0;
	// 虚析构含数
	virtual ~Shape() = default;
};


class Circle : public Shape {
private:
	double radius;
public:
	Circle(double r) : radius(r) {}
	double area() const override {
		return 3.14159 * radius * radius;
	}
	double perimeter() const override {
		return 2 * 3.14159 * radius;
	}
};


class Rectangle : public Shape {
private:
	double width, height;
public:
	Rectangle(double w, double h) : width(w), height(h) {}
	double area() const override {
		return width * height;
	}
	double perimeter() const override {
		return (width + height) * 2;
	}
};

工作原理

多态是通过虚函数表(vtable)和虚函数指针(vptr)实现的,当类中存在虚函数时,编译器会为该类创建一个虚函数表,表中存放着虚函数的地址,同时,每个实例化对象会持有一个指向该虚函数表的指针,当通过基类指针调用虚函数时,程序会根据实际对象的虚函数指针找到对应的虚函数表,然后调用正确的函数

多态的应用场景

  • 统一接口处理不同对象

    void printArea(const Shape& shape) {
        cout << "Area: " << shape.area() << endl;
    }
    
    int main() {
        Circle circle(5.0);
        Rectangle rect(4.0, 6.0);
        
        printArea(circle);  // 输出圆的面积
        printArea(rect);    // 输出矩形的面积
        return 0;
    }
    
  • 工厂模式

    class AnimalFactory {
    public:
        static Animal* createAnimal(const string& type) {
            if (type == "dog") return new Dog();
            if (type == "cat") return new Cat();
            return nullptr;
        }
    };
    

虚析构函数

必要性

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

class Derived : public Base {
public:
    Derived() { cout << "Derived constructor" << endl; }
    ~Derived() override { cout << "Derived destructor" << endl; }
};

int main() {
    Base* obj = new Derived();
    delete obj;  // 正确调用Derived和Base的析构函数
    return 0;
}

final关键字(C++11)

final关键字是从C++11中引入,用于类或者虚函数,用于类时,表示该类不能被继承,用于虚函数时,表示该虚函数不能被重写

class Base final {  // 类不能被继承
    // ...
};

class Base {
public:
    virtual void func() final {}  // 函数不能被重写
};

// class Derived : public Base {};  // 错误!Base是final的

动态类型识别

typeiddynamic_cast

#include <typeinfo>

class Animal {
public:
    virtual ~Animal() = default;
};

class Dog : public Animal {};
class Cat : public Animal {};

void identifyAnimal(Animal* animal) {
    if (dynamic_cast<Dog*>(animal)) {
        cout << "This is a Dog" << endl;
    } else if (dynamic_cast<Cat*>(animal)) {
        cout << "This is a Cat" << endl;
    }
    
    cout << "Type: " << typeid(*animal).name() << endl;
}

多态的优势

  • 复用性: 使用统一的接口管理不同的对象
  • 可扩展性: 容易添加新的派生类
  • 灵活性: 运行时动态决定调用哪个对象
  • 可维护性: 降低代码耦合度
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐