Cpp虚函数相关知识点

什么是虚函数?析构函数什么时候该声明为虚函数?什么是虚基类?

虚函数

虚函数是Cpp用来实现多态的一种机制,但如何理解多态呢?人要工作,人派生出多个子类后,一个作家工作就是写文章,一个程序员工作却是写代码。工作的执行者不同,工作的内容也不同。
在类中成员函数前面加一个virtual,这个函数就变成了虚函数。如下代码:

1
2
3
4
5
6
class base{
public:
virtual void print(){
cout<<"this is base class"<<endl;
}
};

那么怎样让它起作用呢?当子类child继承base的时候,创建一个base类的指针,让它
指向子类对象,这时候用基类指针调用print(),此时会自动判断,然后调用子类中的函数,
如果基类不加virtual,基类指针就直接调用基类的print了。代码如下:

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
#include <iostream>

using namespace std;

class base{
public:
virtual void print(){
cout<<"this is base class"<<endl;
}
};

class child:public base{
public:
void print(){
cout<<"this is child class"<<endl;
}
};

int main()
{
child c;
base *ptr=&c;
ptr->print();
return 0;
}

在Java中,类中的成员函数默认都是虚函数。

虚函数表

如果想知道是怎么实现的呢,就要说起虚函数表了。
如果一个类中有虚函数,那么编译器会在类的开始位置设置一个虚函数指针,指向一个数组(每一个元素都是函数指针),
这个数组就是虚函数表,它存储着每个虚函数的地址。
当子类继承基类的时候,也会连这个虚函数表一块继承过来,然后把里面的函数指针改成子类中的虚函数地址。
由于使用虚函数会导致建立虚函数表,所以会使程序内存消耗变大,效率降低。
可以看这一篇文章探索C++虚函数在g++中的实现,讲的很详细。

析构函数为什么是虚函数

明白了虚函数的特点,这个问题就不难了。
当基类指针指向子类对象的时候,在对象使用完毕需要释放时,肯定需要调用子类对象的析构函数呀,所以这种情况下析构函数也得是虚函数。
也可以看这段话:

1
2
3
4
5
6
7
基类指针可以指向派生类的对象(多态性),如果删除该指针delete []p;
就会调用该指针指向的派生类析构函数,而派生类的析构函数又自动调用
基类的析构函数,这样整个派生类的对象完全被释放。
如果析构函数不被声明成虚函数,则编译器实施静态绑定,
在删除基类指针时,只会调用基类的析构函数而不调用派生类析构函数,
这样就会造成派生类对象析构不完全。所以,将析构函数
声明为虚函数是十分必要的。

虚基类

先看实现形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Base{
public:
virtual void print(){
cout<<"this is Base class"<<endl;
}
};

class Child1:virtual public Base{
public:
void print(){
cout<<"this is Child1 class"<<endl;
}
};

当Child1虚继承Base时,就说Base是Child1的虚基类。
那这样做能解决什么问题呢?
有这样一种情况:
首先我们知道,当子类继承父类的时候,子类中会有父类的成员的一份拷贝。
当Child1,Child2继承Base,然后grandson又多继承自两个Child时,grandson中
就会有Child1,Child2,Base中的多份成员了。但是我们其实根本不需要这么多份
拷贝。先看代码:

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
#include <iostream>

using namespace std;

class Base{
public:
int a=1;
};

class Child1:public Base{
public:
};

class Child2:public Base{
public:
};

class Grandson:public Child1,public Child2{
public:
void print(){
cout<<a<<endl;//此行报错
}
};

int main()
{
Grandson grandson;
grandson.print();
return 0;
}

当我们使用虚继承的时候,就不会有多份拷贝了:

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
#include <iostream>

using namespace std;

class Base{
public:
int a=1;
};

class Child1:virtual public Base{
public:
};

class Child2:virtual public Base{
public:
};

class Grandson:public Child1,public Child2{
public:
void print(){
cout<<a<<endl;
}
};

int main()
{
Grandson grandson;
grandson.print();
return 0;
}

输出结果:

1
1

当使用多重继承的时候很容出现二义性问题,一般不推荐使用多重继承。
Java中就直接删除了多重继承的特性。

纯虚函数

1
virtual <类型><函数名>(<参数表>)=0;

在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。
纯虚函数会让类成为抽象类,即不能被实例化。

  • 声明为虚函数只是要求子类必须给出实现,但基类中也可以写出纯虚函数的实现。
    析构函数可以是纯虚函数。

参考

如有错误,还请指正。
欢迎与我分享你的看法。
转载请注明出处:http://taowusheng.cn/