为什么会有虚函数

我们先来看一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Employee{
	  string first_name, family_name;
public:
	  void print() const{
        cout<<"E"<<endl;
	  }; 
};

class Manager: public Employee{
	   short level;
public:
	   void print() const{
         cout<<"M"<<endl;
	   };
};
void print_info( Employee& p ){
	 p.print();
}
int main(){
     Employee  e;
     Manager  m;
     print_info( e );
     print_info( m );
  	 return 0;
}

在实际运行中,我们发现,print_info()函数执行的都是Employee的print()函数。其实上篇文章,我们讲到,基类和派生类有同名函数时,子类对象直接调用的总是派生类的那个同名函数,如果要调用基类的同名函数,需要借助基类指针或者引用,你看这里print_info()的参数不刚好就是基类Employee的引用吗?所以会出现这种结果。

那么能否用同一个调用形式(比如p->print()),既可以调用派生类的函数实现版本,也可以调用基类的实现版本呢?C++通过虚函数来解决这个问题的。

什么是虚函数

  • 可以将基类与派生类中的原型相同的函数声明为虚函数

  • 声明方式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    class A{
      public:
      	//virtual 返回值类型 成员函数名(形参表);
      	virtual void show(){};
    }
    //在基类的类体内声明虚函数,即在返回值类型前加virtual,派生类相应的函数可以不加
    class B:public A{
      public:
      	void show();
    }
    
  • 虚函数应用的具体例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class A{
  public:
  	//virtual 返回值类型 成员函数名(形参表);
  	virtual void show(){
      cout<<"A"<<endl;
  	};
};
//在基类的类体内声明虚函数,即在返回值类型前加virtual,派生类相应的函数可以不加
class B:public A{
  public:
  	void show(){
      cout<<"B"<<endl;
    };
};

class C:public A{
  public:
  	void show(){
      cout<<"C"<<endl;
    };
};

void showdata(A &p){
  p.show();
}

int main(){
  A a;
  B b;
  C c;
  A &p1 = b;
  A *P2 = &a;
  p1.show();
  p2->show();
  p2 = &c;
  p2->show();
  return 0;
}
/*
运行结果
B
A
C
*/
  • 这个例子也反映了虚函数的作用不仅仅限于灵活调用基类和派生类之间的同名函数,也可以灵活调用同级派生类的同名函数。

为什么要有虚析构函数

  • 解决两个问题。虚析构函数是什么。为什么会有这个机制

  • 虚析构函数:顾名思义,将基类的析构函数加上virtual关键字。

  • 首先,我们明确,构造函数是不可以声明为虚函数的。原因如下。

    构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间确定实际类型的。而在构造一个对象时,由于对象还未构造成功。编译器无法知道对象的实际类型,是该类本身,还是该类的一个派生类,或是更深层次的派生类。无法确定。。。

  • 那为什么析构函数可以被声明为虚函数呢?原因如下

    在类的继承中,如果有基类指针指向派生类,那么用基类指针delete时,如果不定义成虚函数,派生类中派生的那部分无法析构。

    因此在类的继承体系中,基类的析构函数不声明为虚函数容易造成内存泄漏。所以如果你设计的类可能是基类的话,并使用了动态分配 ,必须要声明其析构函数为虚函数(最好就是含有虚函数的基类,就要声明虚析构函数)

为什么要有纯虚函数和抽象类

  • 解决两个问题。纯虚函数和抽象类是什么。为什么会有这个机制

  • 纯虚函数:将基类的某个成员函数声明为如下格式

    virtual 返回值类型 函数名(形参表) = 0;

  • 当基类的某个成员函数为纯虚函数时,该基类则被称为抽象类或者抽象基类

  • 纯虚函数的函数体为空,因此含有纯虚函数的基类不可被实例化,不可创建基类对象,但是可以创建该类型的引用或者指针,指向派生类对象

  • 既然它不可以实例化,那么它存在的意义在哪呢?

1.从生活上分析,在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。

2.定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。类纯虚函数的声明就是在告诉子类的设计者,“你必须提供一个纯虚函数的实现,但我不知道你会怎样实现它”。

3.纯虚函数只是提供接口,并没有给出实现,虚函数是可以在基类给出实现的。

  • 涉及动态分配的话,别忘了在包含纯虚函数的类中写虚析构函数

扩展阅读

关于虚函数的其他事情

为什么要用虚函数,指针,引用来实现多态