跳到主要内容

C++虚拟函数

提示
  1. 虚函数基本概念:虚函数是基类中的成员函数,预期在派生类中重新定义。当基类指针指向派生类对象时,虚函数确保调用的是派生类中的函数。
  2. 声明和使用虚函数:在基类中使用virtual关键字声明虚函数。这使得派生类可以覆盖基类中的同名函数,实现多态。
  3. override标识符:C++11新增了override标识符,用于明确指出派生类中的函数覆盖了基类的虚函数。这有助于防止在派生类中不正确声明虚函数时产生的错误。

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

基本上,虚函数是在基类中用来确保该函数被覆盖的。这尤其适用于基类的指针指向派生类的对象的情况。

例如,考虑以下代码:

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

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

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

换句话说,Base 的成员函数没有被覆盖。

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

// 调用 Base 类的函数
base1->print();

return 0;
}

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

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

虚函数是 C++ 多态性的一个不可或缺的部分。要了解更多,请查看我们的教程 C++ 多态性

示例 1:C++ 虚函数

#include <iostream>
using namespace std;

class Base {
public:
virtual void print() {
cout << "基类函数" << endl;
}
};

class Derived : public Base {
public:
void print() {
cout << "派生类函数" << endl;
}
};

int main() {
Derived derived1;

// 创建 Base 类型的指针指向 derived1
Base* base1 = &derived1;

// 调用派生类的成员函数
base1->print();

return 0;
}

输出

派生类函数

这里,我们已将 Baseprint() 函数声明为 virtual

所以,即使我们使用指向 Derived 对象 derived1 的 Base 类型的指针,这个函数也会被覆盖。

C++ 虚函数的工作原理

C++ override 标识符

C++ 11 为我们提供了一个新的标识符 override,在使用虚函数时非常有用,以避免出现错误。

这个标识符指定派生类的成员函数覆盖基类的成员函数。

例如,

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

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

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

class Derived : public Base {
public:
// 函数原型
void print() override;
};

// 函数定义
void Derived::print() {
// 代码
}

C++ override 的使用

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

使用 override 标识符时,当这些错误发生时,编译器会显示错误信息。

否则,程序将正常编译,但虚函数不会被覆盖。

这些可能的错误包括:

  • 函数名称不正确: 例如,如果基类中的虚函数命名为 print(),但我们在派生类中将覆盖函数误命名为 pint()
  • 函数返回类型不同: 如果虚函数是 void 类型,但派生类中的函数是 int 类型。
  • 函数参数不同: 如果虚函数和派生类中的函数的参数不匹配。
  • 基类中没有声明虚函数。

C++ 虚函数的使用

假设我们有一个基类 Animal 和派生类 DogCat

假设每个类都有一个名为 type 的数据成员。假设这些变量通过各自的构造函数初始化。

class Animal {
private:
string type;
... .. ...
public:
Animal(): type("Animal") {}
... .. ...
};

class Dog : public Animal {
private:
string type;
... .. ...
public:
Animal(): type("Dog") {}
... .. ...
};

class Cat : public Animal {
private:
string type;
... .. ...
public:
Animal(): type("Cat") {}
... .. ...
};

现在,假设我们的程序需要为每个类创建两个 public 函数:

  1. getType() 返回 type 的值
  2. print() 打印 type 的值

我们可以在每个类中分别创建这两个函数并覆盖它们,这将是漫长而乏味的。

或者我们可以在 Animal 类中将 getType() 声明为虚函数,然后创建一个单独的、单独的 print() 函数,该函数接受 Animal 类型的指针作为其参数。然后我们可以使用这个单一的函数来覆盖虚函数。

class Animal {
... .. ...
public:
... .. ...
virtual string getType {...}
};

... .. ...
... .. ...

void print(Animal* ani) {
cout << "动物: " << ani->getType() << endl;
}

这将使代码更简短干净重复性更少

示例 2:C++ 虚函数演示

// C++ 程序演示虚函数的使用

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

class Animal {
private:
string type;

public:
// 构造函数初始化 type
Animal() : type("Animal") {}

// 声明虚函数
virtual string getType() {
return type;
}
};

class Dog : public Animal {
private:
string type;

public:
// 构造函数初始化 type
Dog() : type("Dog") {}

string getType() override {
return type;
}
};

class Cat : public Animal {
private:
string type;

public:
// 构造函数初始化 type
Cat() : type("Cat") {}

string getType() override {
return type;
}
};

void print(Animal* ani) {
cout << "动物: " << ani->getType() << endl;
}

int main() {
Animal* animal1 = new Animal();
Animal* dog1 = new Dog();
Animal* cat1 = new Cat();

print(animal1);
print(dog1);
print(cat1);

return 0;
}

输出

动物: Animal
动物: Dog
动物: Cat

在这里,我们使用了虚函数 getType() 和一个 Animal 指针 ani 来避免在每个类中重复 print() 函数。

void print(Animal* ani) {
cout << "动物: " << ani->getType() << endl;
}

main() 中,我们创建了 3 个 Animal 指针动态创建 AnimalDogCat 类的对象。

// 使用 Animal 指针动态创建对象
Animal* animal1 = new Animal();
Animal* dog1 = new Dog();
Animal* cat1 = new Cat();

接着,我们使用这些指针调用 print() 函数:

  1. 当调用 print(animal1) 时,指针指向一个 Animal 对象。所以,Animal 类中的虚函数在 print() 内部执行。
  2. 当调用 print(dog1) 时,指针指向一个 Dog 对象。因此,虚函数被重写,Dog 的函数在 print() 内部执行。
  3. 当调用 print(cat1) 时,指针指向一个 Cat 对象。因此,虚函数被重写,Cat 的函数在 print() 内部执行。