C++ 虚函数 (virtual function) 介绍

文章目录

    • 1. 什么是虚函数
    • 2. 虚函数与非虚函数的区别
    • 3. 派生类中的虚函数
    • 4. 构造/析构函数可以是虚函数吗?
    • 5. 纯虚函数
      • 5.1 纯虚函数的定义
      • 5.1 纯虚函数的特定

        1. 什么是虚函数

        C++ 对象有三大特性:继承、封装、多态;虚函数就是实现多态的一种方式。

        虚函数是指加了 virtual 修饰词的类的成员函数,但 virtual 关键字并非强制必须要有的。

        对于某些函数,当基类希望派生类重新定义合适自己的版本时,基类就把这些函数声明为虚函数

        以下是一个虚函数的简单定义:

        class Animal {public:
        	virtual void makeSound() {std::cout << "Animal makes a sound" << std::endl;
        	}
        };
        

        注意:virtual 关键字只能出现在类内部的函数声明中,不能用于类外部的函数定义。

        2. 虚函数与非虚函数的区别

        在 C++ 中,基类必须将它的两种成员函数区分开:

        • 基类希望直接继承给派生类而不需要改写的函数。静态绑定,即解析过程发生在编译而非运行时
        • 基类希望派生类进行覆盖的函数:定义为虚函数。动态绑定,即根据对象类型不同,调用该虚函数时可能执行基类的版本,也可能执行某个派生类的版本;因此需要在程序运行时确定。

          3. 派生类中的虚函数

          由于只有在程序运行时才知道调用哪个版本的虚函数,因此所有虚函数都必须有定义,就是不使用虚函数,也必须定义它。

          基类定义的虚函数在所有派生类中都是虚函数

          C++11 允许派生类使用 override 关键字显式地注明哪个成员函数是改写的基类的虚函数,代码示例如下:

          class Animal {virtual void makeSound() {std::cout << "Animal makes a sound." << std::endl;
          	}
          };
          class Dog :public Animal {void makeSound() override {std::cout << "Dog makes a sound." << std::endl;
          	}
          };
          

          完整代码示例如下:

          #include class Animal {private:
          	int nums;
          public:
          	Animal() = default;
          	Animal(int nums_) : nums(nums_) {};
          	virtual void printNum() {std::cout << "The number of animals is: " << nums << std::endl;
          	}
          };
          class Dog :public Animal {private:
          	int nums;
          public:
          	Dog() = default;
          	Dog(int nums_) : nums(nums_) {};
          	void printNum() override {std::cout << "The number of dog is: " << nums << std::endl;
          	}
          };
          int main(){Animal animal(100);
          	animal.printNum();   // The number of animals is: 100
          	Dog dog(5);
          	dog.printNum();      // The number of dog is: 5
          	return 0;
          }
          

          4. 构造/析构函数可以是虚函数吗?

          答案:

          构造函数不能是虚函数,析构函数可以是虚函数且最好设置为虚函数

          (1)构造函数不可以是虚函数

          构造函数是在创建对象时执行的,而虚函数是程序运行时执行的;也就是说在创建对象时虚函数还没确定用那个版本呢,所以构造函数不可以是虚函数。

          (2)析构函数可以是虚函数且最好写成虚函数

          如果析构函数不是虚函数,则容易造成内存泄露。原因为:

          若有父类指针指向子类对象存在,需要析构的是子类对象;但父类析构函数不是虚函数,则只析构了父类,造成子类对象没有及时释放,引起内存泄漏。

          5. 纯虚函数

          5.1 纯虚函数的定义

          虚函数与纯虚函数的区别如下:

          • 虚函数:子类可以(也可以不)重新定义基类的虚函数,在基类中定义为 virtual void func() {}
          • 纯虚函数:子类必须提供纯虚函数的个性化实现,在基类中定义为 virtual void func() = 0 {} 或 virtual void func() const = 0 {}

            以下是一个纯虚函数的简单定义(声明):

            class Animal {public:
            	virtual void makeSound() = 0 {}
            };
            

            5.1 纯虚函数的特定

            (1)含有纯虚函数的类称为抽象类,抽象类不能被实例化。

            与纯虚函数不同的是,包含虚函数的类可以被实例化。

            如下面的代码中对 Animal 抽象类的实例化会编译报错。

            #include class Animal {// 这里的 = 0 没有任何实际意义,只起形式上的作用,告诉编译系统"这是纯虚函数"
            	virtual void makeSound() = 0{}
            };
            int main(){Animal animal();   // 报错:不能实例化抽象类
            	return 0;
            }
            

            (2)纯虚函数只需要声明,不需要定义。

            因为纯虚函数一定会被重新,所以在基类中声明即可,不需要定义。