C++ 虚函数和函数重写

虚函数是基类中我们期望在派生类中重新定义的成员函数

例如,

class Base {
   public:
    void print() {
        // code
    }
};

class Derived : public Base {
   public:
    void print() {
        // code
    }
};

派生类中的 print() 方法隐藏了基类中的 print() 方法。

但是,如果我们创建一个类型为 Base 的指针指向 Derived 类的对象并调用 print() 函数,它会调用 Base 类的 print() 函数。

换句话说,Base 的成员函数没有被重写。

int main() {
    Derived derived1;
    Base* base1 = &derived1;

    // calls function of Base class
    base1->print();

    return 0;
}

为了避免这种情况,我们使用 virtual 关键字将 Base 类的 print() 函数声明为虚函数。

class Base {
   public:
    virtual void print() override {
        // code
    }
};

如果派生类中重新定义了虚函数,即使是通过指向派生类对象的基类指针调用,也会执行派生类中的函数。在这种情况下,该函数被称为被重写。

虚函数是 C++ 多态的重要组成部分。要了解更多信息,请参阅我们的 C++ 多态教程。


示例 1:C++ 虚函数

#include <iostream>
using namespace std;

class Base {
   public:
    virtual void print() {
        cout << "Base Function" << endl;
    }
};

class Derived : public Base {
   public:
    void print() override {
        cout << "Derived Function" << endl;
    }
};

int main() {
    Derived derived1;

    // pointer of Base type that points to derived1
    Base* base1 = &derived1;

    // calls member function of Derived class
    base1->print();

    return 0;
}

输出

Derived Function

在这里,我们将 Baseprint() 方法声明为 virtual

因此,即使我们使用指向 Derived 对象 derived1Base 类型指针,该函数也会被重写。

C++ Working of Virtual Functions
C++ 中虚函数的工作原理

C++ override 标识符

C++ 11 提供了一个新的标识符 override,它对于避免使用虚函数时常见的错误非常有用。

override 标识符指定了派生类中重写基类成员函数的成员函数。

例如,

class Base {
   public:
    virtual void print() {
        // code
    }
};

class Derived : public Base {
   public:
    void print() override {
        // code
    }
};

如果我们使用 Derived 类中的函数原型,并在类外定义该函数,则使用以下代码

class Derived : public Base {
   public:
    // function prototype
    void print() override;
};

// function definition
void Derived::print() {
    // code
}

这里,void print() override;Derived 类中的函数原型。override 标识符确保 Derived 类中的 print() 函数重写了 Base 类中的 print() 函数。

如果在 Derived 类中使用函数原型,则仅在函数声明中使用 override 标识符,而在定义中不使用。


C++ override 的用法

在使用虚函数时,可能会在声明派生类的成员函数时出错。

使用 override 标识符可以提示编译器在发生这些错误时显示错误消息。

否则,程序将仅仅编译通过,但虚函数不会被重写。

一些可能的错误包括

  • 函数名称不正确:例如,如果基类中的虚函数名为 print(),但我们在派生类中不小心将重写函数命名为 pint()
  • 函数返回类型不同:如果虚函数是 void 类型,但派生类中的函数是 int 类型。
  • 函数参数不同:如果虚函数的参数与派生类中的函数不匹配。
  • 基类中未声明虚函数。

虚析构函数

当派生类涉及动态内存分配时,我们必须在其析构函数中释放内存。

#include <iostream>

using namespace std;

class Shape {
    public:
        Shape() = default;

        virtual double get_area() const {
            return 0.0;
        }

        ~Shape() = default;
};

class Square: public Shape {
    private:
        double* length;

    public:
        Square(double len = 1.0): length(new double{len}) {
        }

        double get_area() const override {
            return *length * *length;
        }

        ~Square() {
            delete length;
             cout << "deleted length." << endl << endl;
        }
};

int main() {
    // Pointer to Square class pointing to Square object
    Square* shape1 = new Square(1.5);
    cout << "area of square: " << shape1->get_area() << endl;
    // invokes Square class destructor
    delete shape1;

    // pointer to Shape class pointing to Square object
    Shape* shape2 = new Square(2.5);
    cout << "area of square: " << shape2->get_area() << endl;
    // invokes Shape class destructor
    delete shape2;
    return 0;
}

输出

area of square: 2.25
deleted length.

area of square: 6.25

当我们删除指向 Square 对象的 Square* 类型的 shape1 时,会调用 Square 类的析构函数,它会删除 Square 对象占用的动态内存。

但是,如果我们删除指向 Square 对象的 Shape* 类型的 shape2,则会调用 Shape 类的析构函数,它不会释放 Square 对象占用的动态内存。

如果我们将 Shape 类的析构函数声明为 virtual,那么当我们删除指向 Square 对象的 Shape 类指针时,会调用 Square 类的析构函数。

#include <iostream>

using namespace std;

class Shape {
    public:
        Shape() = default;

        virtual double get_area() const {
            return 0.0;
        }

        virtual ~Shape() = default;
};

class Square: public Shape {
    private:
        double* length;

    public:
        Square(double len = 1.0): length(new double{len}) {
        }

        double get_area() const override {
            return *length * *length;
        }

        ~Square() {
            delete length;
             cout << "deleted length." << endl << endl;
        }
};

int main() {
    // Pointer to Square class pointing to Square object
    Square* shape1 = new Square(1.5);
    cout << "area of square: " << shape1->get_area() << endl;
    // invokes Square class destructor
    delete shape1;

    // pointer to Shape class pointing to Square object
    Shape* shape2 = new Square(2.5);
    cout << "area of square: " << shape2->get_area() << endl;
    // invokes square class destructor
    delete shape2;
    return 0;
}

输出

area of square: 2.25
deleted length.

area of square: 6.25
deleted length.

C++ 虚函数的用法

当我们需要存储一组对象时,虚函数非常有用。

假设我们有一个基类 Employee 和派生类 HourlyEmployeeRegularEmployee。我们可以将指向两个派生类对象的 Employee* 指针存储在一个 Employee* 的集合中。

然后,我们可以使用 Employee* 指针调用虚函数。当我们使用 Employee* 指针调用虚函数时,编译器会根据相应派生类中定义的函数来调用该函数。

// C++ program to demonstrate the use of virtual function

#include <iostream>
#include <vector>
using namespace std;

class Employee {
    private:
        string first_name;
        string last_name;
    public:
        Employee(string fname, string lname): first_name(fname), last_name(lname) {
        }
        string get_full_name() {
            return first_name + " " + last_name;
        }
        // virtual function to allow overriding
        virtual void print_wage() {
            cout << "The employee's wage structure is not declared yet" << endl;
        }
        // virtual destructor
        virtual ~Employee() {
        }
};

class HourlyEmployee: public Employee {
    private:
        double hourly_wage;
    public:
        HourlyEmployee(string fname, string lname, double wage_per_hour): Employee(fname, lname), hourly_wage(wage_per_hour) {
            
        }
        // overrides the function and provides proper implementation
        // according to the wage structure of HourlyEmployee
        void print_wage() override {
            cout << "The hourly wage of " << get_full_name() << " is " << hourly_wage << endl;
        }
};

class RegularEmployee: public Employee {
    private:
        double monthly_wage;
    public:
        RegularEmployee(string fname, string lname, double wage_per_month): Employee(fname, lname), monthly_wage(wage_per_month) {
            
        }
        // overrides the function and provides proper implementation
        // according to the wage structure of RegularEmployee
        void print_wage() override {
            cout << "The monthly wage of " << get_full_name() << " is " << monthly_wage << endl;
        }
};

int main() {
    // declare a vector to store Employee* pointers pointing to dervied class objects
    vector<Employee*> employees {new HourlyEmployee("John", "Doe", 13.5), new RegularEmployee("Jane", "Smith", 3000.7)};
    for(Employee* employee: employees) {
        employee->print_wage();
    }
    return 0;
}

输出

The hourly wage of John Doe is 13.5
The monthly wage of Jane Smith is 3000.7

在这里,基类 Employee 有一个虚函数 print_wage()。派生类 HourlyEmployeeRegularEmployee 有自己实现的 print_wage() 函数。

我们将指向 HourlyEmployeeRegularEmployee 对象的 Employee* 指针存储在一个 vector 中。

vector<Employee*> employees {new HourlyEmployee("John", "Doe", 13.5), new RegularEmployee("Jane", "Smith", 3000.7)};

然后,我们使用范围 for 循环遍历 vector 中的指针。

for(Employee* employee: employees) {
    employee->print_wage();
}

这里,employee->print_wage() 根据指针 employee 所指向的对象的类型来调用 print_wage() 函数。

你觉得这篇文章有帮助吗?

我们的高级学习平台,凭借十多年的经验和数千条反馈创建。

以前所未有的方式学习和提高您的编程技能。

试用 Programiz PRO
  • 交互式课程
  • 证书
  • AI 帮助
  • 2000+ 挑战