虚函数是基类中我们期望在派生类中重新定义的成员函数。
例如,
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
在这里,我们将 Base
的 print()
方法声明为 virtual
。
因此,即使我们使用指向 Derived
对象 derived1 的 Base
类型指针,该函数也会被重写。

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 和派生类 HourlyEmployee 和 RegularEmployee。我们可以将指向两个派生类对象的 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()。派生类 HourlyEmployee 和 RegularEmployee 有自己实现的 print_wage() 函数。
我们将指向 HourlyEmployee 和 RegularEmployee 对象的 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()
函数。