C++ 虚函数

发布于:2025-06-24 ⋅ 阅读:(11) ⋅ 点赞:(0)

1、引言

  • 为什么需要虚函数?
  • 因为我要将派生类对象赋值给基类的指针或者引用调用派生类的方法
  • 那你可以将派生类的对象赋值给派生类的指针或引用,然后调用派生类的方法,为什么非要赋值给基类的指针或者引用呢?
  • 比如有一个函数func(S* s),用于调用s中的一系列成员函数。假设S有3个派生类S1S2S3,那么需要分别实现func(S1* s1)func(S2* s2)func(S3* s3)三个函数,也就是函数重载,代码量是原本的三倍。而如果S有30个派生类、有50个派生类呢?另外,如果要对func()做简单的修改,那么也需要同时修改所有的函数重载,极大地提高了程序员的工作量。
  • 所以,如果基类能够动态确定其实际所指向的派生类对象,并调用合适版本的函数,那么一个函数就可以解决上面的问题?
  • 是的。所以,我需要虚函数!

2、虚函数与多态

2.1、概念

  • 面向对象编程语言三大特征:封装、继承、多态。
  • C++虚函数是多态性实现的重要方式,当通过指针或者引用调用某个虚函数时,编译器产生的代码直到运行时才能确定到底调用哪个版本的函数。被调用的函数是与绑定到指针或者引用上的对象的动态类型相匹配的那个。
  • 匹配的规则是相同的函数签名(函数名,参数个数与类型)以及返回类型(返回类型可以不相同,但必须存在派生关系)

2.2、代码示例

class Animal {
   
 public:
  Animal(const string& name) : m_name{
   name} {
   }

  const string& getName() const {
    return m_name; }

  virtual string speak() const {
    return "???"; }

 private:
  string m_name;
};

class Cat : public Animal {
   
 public:
  Cat(const string& name) : Animal(name) {
   }

  virtual string speak() const {
    return "Meow"; }
};

class Dog : public Animal {
   
 public:
  Dog(const string& name) : Animal(name) {
   }

  virtual string speak() const {
    return "Woof"; }
};

int main(int argc, char** argv) {
   
  Cat cat{
   "Fred"};
  cout << "Cat is named " << cat.getName() << ", and it says " << cat.speak()
       << endl;

  Dog dog{
   "Carbo"};
  cout << "Dog is named " << dog.getName() << ", and it says " << dog.speak()
       << endl;

  Animal* catAnimal = &cat;
  cout << "Cat is named " << catAnimal->getName() << ", and it says "
       << catAnimal->speak() << endl;

  Animal& dogAnimal = dog;
  cout << "Dog is named " << dogAnimal.getName() << ", and it says "
       << dogAnimal.speak() << endl;

  return 0;
}

2.3、代码输出

Cat is named Fred, and it says Meow
Dog is named Carbo, and it says Woof
Cat is named Fred, and it says Meow
Dog is named Carbo, and it says Woof

2.4、virtual 标识符

  • 从2.2的代码示例中可以看出,添加virtual关键字后,这个函数便成了虚函数
  • 事实上,派生类中的virtual关键字并不是必要的。
  • 一旦基类中的方法打上了virtual标签,那么派生类中匹配的函数也是虚函数。
  • 但是,还是建议在后面的派生类中加上virtual关键字,作为虚函数的一种提醒,以便后面可能还会有更远的派生。
  • 注意千万不要在构造函数与析构函数中调用虚函数!!!
    1. 我们知道派生类对象在创建时,首先基类部分先被创建。
    2. 如果你在基类构造函数调用虚函数时,它此时将无法调用派生类版本的函数,因为派生类对象还未创建,此时派生类虚函数没有作用的对象。
    3. 那么,它只能调用基类版本的虚函数。
    4. 对于析构函数,派生类对象中的派生部分先被析构,如果你在基类析构函数中调用了虚函数,它也只能调用基类版本的虚函数,因为派生类对象已经不存在了。
  • 什么时候使用虚函数?
    • 大部分时候,我们希望派生类是真正的“重写”基类函数,而不是“隐藏”。
    • 所以一般建议将所有方法都声明为virtual
    • 既然如此,为什么编译器不默认这样做呢?
    • 其实对于Java语言来说,所有的方法默认是虚函数。
    • 但是使用虚函数是有代价的,相对于普通函数,虚函数的调用代价稍高,但是这种差别不会太大,所以还是建议所有方法都使用virtual关键字。

2.5、override 标识符

  • 前文说道,派生类重写基类的虚函数时,需要遵守匹配规则
  • 否则编译器会认为派生类创建了一个新方法,而不是重写基类的函数。
  • 因此,为了避免程序员因为粗心大意导致没有重写基类的虚函数,而是创建了一个新的函数,我们可以使用overide关键字
  • 使用这个标识符告诉编译器这是重写的方法,如果找不到匹配的基类虚函数,那么将无法通过编译。
    class Animal {
         
     public:
      Animal(const string& name) : m_name{
         name

网站公告

今日签到

点亮在社区的每一天
去签到