C++ 虚函数

本文详细介绍了C++中的虚函数概念,包括其与非虚函数的区别、派生类中的处理、构造析构函数的特殊性,以及纯虚函数和抽象类的使用。重点讲解了动态绑定与静态绑定在虚函数调用中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、什么是虚函数

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

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

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

注意: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 <iostream>
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.2 纯虚函数的特定

  1. 含有纯虚函数的类称为抽象类,抽象类不能被实例化。
    与纯虚函数不同的是,包含虚函数的类可以被实例化。

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

#include <iostream>

class Animal {
	// 这里的 = 0 没有任何实际意义,只起形式上的作用,告诉编译系统"这是纯虚函数"
	virtual void makeSound() = 0{}
};

int main(){
	Animal animal();   // 报错:不能实例化抽象类
	return 0;
}
  1. 纯虚函数只需要声明,不需要定义。
    因为纯虚函数一定会被重新定义 ,所以在基类中声明即可,不需要定义。

6、父类指针指向子类对象的问题

父类指针指向子类实例对象,对于普通重写函数时,会调用父类中的函数。而调用被子类重写虚函数时,会调用子类中的函数。
这是因为子类中被重写的虚函数的运行方式是动态绑定的,与当前指向类实例的父类指针类型无关,仅和类实例对象本身有关。

  1. 静态绑定发生在编译期,动态绑定发生在运行期
  2. 对象的动态类型可以更改,但是静态类型无法更改;
  3. 在继承体系中只有虚函数使用的是动态绑定,其他的全部是静态绑定(成员变量也是静态绑定);
 //对象的动态类型可以更改,但是静态类型无法更改;
class A{
public:
    void func_a();
    virtual void func_b();
    int n;
}

class B : public A{
public:
    void func_a();
    virtual void func_b();
    int n;
}

class C : public A{
public:
    void func_a();
    virtual void func_b();
    int n;
}

B* b = new B();
C* c = new C();
A* a = b;    // 此时,a的静态类型是A*, 动态类型是B*

a->func_a(); // 指向的是基类A的func_a, 因为func_a是普通函数,是静态绑定的
a->func_b(); // 指向B中的func_b, virtual修饰的虚函数,是动态绑定,在运行时确定所属具体的实例
a->n;		 // 指向基类的n,成员变量是静态绑定
a = c;       // 动态类型是可以修改的,此时是C*
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值