C++ 虚拟函数

在本教程中,我们将通过示例了解 C++ 虚函数及其使用。

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

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

例如,考虑下面的代码:

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

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

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

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

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

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

    return 0;
}

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

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

虚函数是 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() {
        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 类中声明了 virtual 函数 print()

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

C++ 的虚拟函数的工作原理
C++ 的虚拟函数的工作原理

C++ override 标识符

C++ 11 为我们提供了一个新标识符 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
}

使用 C++ 覆盖

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

当出现这些错误时,使用 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 << "Animal: " << ani->getType() << endl;
}

这将使代码更短更清晰重复更少

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

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

class Animal {
   private:
    string type;

   public:
    // constructor to initialize type
    Animal() : type("Animal") {}

    // declare virtual function
    virtual string getType() {
        return type;
    }
};

class Dog : public Animal {
   private:
    string type;

   public:
    // constructor to initialize type
    Dog() : type("Dog") {}

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

class Cat : public Animal {
   private:
    string type;

   public:
    // constructor to initialize type
    Cat() : type("Cat") {}

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

void print(Animal* ani) {
    cout << "Animal: " << 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: Animal
Animal: Dog
Animal: Cat

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

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

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

// dynamically create objects using Animal pointers
Animal* animal1 = new Animal();
Animal* dog1 = new Dog();
Animal* cat1 = new Cat();

然后我们使用这些指针调用 print() 函数:

  1. print(animal1) 被调用时,指针指向一个 Animal 对象。因此, 在 print() 函数中执行了 Animal 类中的虚函数.
  2. print(dog1) 被调用时,指针指向一个 Dog 对象。因此,虚函数被 Dog 覆盖。
  3. print(cat1) 被调用时,指针指向一个 Cat 对象。因此,虚函数被 Cat 覆盖。