对C++中的虚函数的说明

简介: 对C++中的虚函数的说明

在C++中,虚函数是对多态性(Polymorphism)的一种支持。当基类指针或引用被用来调用派生类对象的成员函数时,可以通过虚函数来实现动态绑定,即根据运行时类型确定要调用的函数。

下面是使用虚函数的两个典型场景:

  1. 实现多态

如果你需要在不同的子类上调用相同名字的函数,可以将该函数定义为虚函数。这样就可以使得程序在运行时按照其具体类型来选择调用哪个版本的函数,并实现多态性

考虑如下代码:

class Animal {
public:
    virtual void speak() {
        cout << "This animal makes a sound." << endl;
    }
};
class Dog : public Animal {
public:
    void speak() override {
        cout << "The dog barks." << endl;
    }
};
class Cat : public Animal {
public:
    void speak() override {
        cout << "The cat meows." << endl;
    }
};
int main() {
    Animal* animal1 = new Dog();
    Animal* animal2 = new Cat();
    animal1->speak(); // Output: The dog barks.
    animal2->speak(); // Output: The cat meows.
    delete animal1;
    delete animal2;
    return 0;
}

上述代码中,Animal类是一个基类,其中speak()函数被声明为虚函数。DogCat类都从Animal类派生而来,重新实现了speak()函数。在主函数中,我们分别用类型为Animal*的指针指向不同的子类对象,并调用了虚函数speak()。因为speak()是虚函数,所以调用的实际函数版本在运行时被动态地确定,从而实现了多态性。

  1. 实现动态绑定

当被重载的函数在程序运行时才能确定需要调用哪个版本时,使用虚函数非常有用。这种情况下,通过基类指针或引用调用该函数,编译器会根据其动态类型来选择调用正确的版本,也就是实现动态绑定的功能。

考虑如下代码:

class Car {
public:
    virtual void run() = 0;
};
class GasCar : public Car {
public:
    void run() override {
        cout << "Gas car runs by burning gasoline." << endl;
    }
};
class ElectricCar : public Car {
public:
    void run() override {
        cout << "Electric car runs by using battery power." << endl;
    }
};
int main() {
    GasCar myGasCar;
    ElectricCar myElectricCar;
    Car* p1 = &myGasCar;
    Car& r1 = myElectricCar;
    p1->run(); // Output: Gas car runs by burning gasoline.
    r1.run(); // Output: Electric car runs by using battery power.
    return 0;
}

上述代码中,Car类是一个抽象基类,其中run()函数被声明为纯虚函数。GasCarElectricCar类都从Car类派生而来,重新实现了run()函数。在主函数中,我们分别创建了两个不同的子类对象,并将它们的地址存储到类型为基类Car*Car&的指针和引用中。通过这些指针和引用我们调用了虚函数run()。因为run()是纯虚函数,所以编译器会根据其动态类型来选择调用正确版本的函数。

除了上述场景,还有一些其他的情况下适合使用虚函数:

  1. 实现接口

在C++中,可以通过纯虚函数来定义接口规范,然后让子类来实现这些函数。使用纯虚函数定义接口可以使得代码更加清晰和易于维护。

考虑如下代码:

class Shape {
public:
    virtual double getArea() const = 0;
};
class Rectangle : public Shape {
private:
    double width_;
    double height_;
public:
    Rectangle(double width, double height) :
        width_(width), height_(height) {}
    double getArea() const override {
        return width_ * height_;
    }
};
class Circle : public Shape {
private:
    double radius_;
public:
    Circle(double radius) : radius_(radius) {}
    double getArea() const override {
        return 3.14159 * radius_ * radius_;
    }
};
int main() {
    Shape* shapes[2] = { new Rectangle(4, 3), new Circle(2) };
    cout << "The areas of the shapes are:" << endl;
    for (int i = 0; i < 2; ++i) {
        cout << "Shape " << i + 1 << ": " << shapes[i]->getArea() << endl;
        delete shapes[i];
    }
    return 0;
}

上述代码中,Shape类是一个抽象基类,其中getArea()函数被声明为纯虚函数。RectangleCircle类都从Shape类派生而来,并重新实现了该纯虚函数。在主函数中,我们创建了一个包含两个不同形状对象的数组,并调用它们的虚函数getArea(),计算它们的面积。

  1. 实现析构函数

当类中涉及到继承时,子类中可能会定义一些新的资源。为了确保在释放基类时也能够正确地释放这些资源,可以将基类的析构函数定义为虚函数。这样,在删除通过指向基类指针或引用指向其派生类的对象时,会首先调用其派生类的析构函数,然后再调用基类的析构函数。

考虑如下代码:

class Base {
public:
    virtual ~Base() { cout << "Base destructor." << endl; }
};
class Derived : public Base{
private:
    int* ptr_;
public:
    Derived(int value) : ptr_(new int {value}) {}
    ~Derived() override {
        delete ptr_;
        cout << "Derived destructor." << endl;
    }
};
int main() {
    Base* base = new Derived(42);
    delete base;
    return 0;
}

上述代码中,Base类是一个简单的基类,其中的析构函数被声明为虚函数。Derived类从Base类派生而来,重载了该析构函数。在主函数中,我们使用类型为指向基类Base*的指针指向类型为派生类Derived的对象,并对其进行删除操作。由于Base的析构函数被声明为虚函数,因此在删除派生类对象时会先调用其析构函数,然后再调用Base类的析构函数。

需要注意的是,虚函数存在虚表(Virtual Table)和虚指针(Virtual Pointer),这些数据结构占用了额外的内存空间。如果在程序中定义过多的虚函数或虚函数的层级比较深,将可能导致性能下降。因此,在使用虚函数时也要注意其对程序性能的影响。

目录
相关文章
|
5月前
|
C++
C++一分钟之-虚函数与抽象类
【6月更文挑战第21天】在C++中,虚函数与抽象类是多态的基础,增进类间耦合与灵活性。虚函数实现动态绑定,抽象类定义不可实例化的接口。关键点包括:记得使用`virtual`,避免滥用虚函数,确保派生类实现纯虚函数。抽象类不能直接实例化,派生类必须实现所有纯虚函数。通过实例代码学习和实践,能更好地掌握这些概念以优化代码设计。
50 2
|
6月前
|
C++
9. C++虚函数与多态
9. C++虚函数与多态
61 0
|
6月前
|
设计模式 编解码 算法
【C/C++ 虚函数以及替代方案】C++ 虚函数的使用开销以及替代方案(三)
【C/C++ 虚函数以及替代方案】C++ 虚函数的使用开销以及替代方案
141 0
|
6月前
|
存储 设计模式 编译器
【C/C++ 虚函数以及替代方案】C++ 虚函数的使用开销以及替代方案(一)
【C/C++ 虚函数以及替代方案】C++ 虚函数的使用开销以及替代方案
367 0
|
6月前
|
算法 安全 编译器
【C++ 关键字 override】C++ 重写关键字override(强制编译器检查该函数是否覆盖已存在的虚函数)
【C++ 关键字 override】C++ 重写关键字override(强制编译器检查该函数是否覆盖已存在的虚函数)
102 0
|
6月前
|
算法 Java 编译器
【C++ 关键字 virtual 】C++ virtual 关键字(将成员函数声明为虚函数实现多态
【C++ 关键字 virtual 】C++ virtual 关键字(将成员函数声明为虚函数实现多态
61 0
|
3月前
|
编译器 C++ 索引
C++虚拟成员-虚函数
C++虚拟成员-虚函数
|
6月前
|
存储 编译器 C++
【C++练级之路】【Lv.13】多态(你真的了解虚函数和虚函数表吗?)
【C++练级之路】【Lv.13】多态(你真的了解虚函数和虚函数表吗?)
|
6月前
|
Serverless C++
C++多态性、虚函数、纯虚函数和抽象类知识网络构造
C++多态性、虚函数、纯虚函数和抽象类知识网络构造
|
6月前
|
C++ 开发者
C++程序中利用虚函数实现动态多态性
C++程序中利用虚函数实现动态多态性
57 2