只有用virtual声明类的成员函数,称之为虚函数。
就是一句话:实现多态的基石
实现多态的三大步:
1.存在继承关系 子类继承父类
2.子类重写父类的virtual function
3.子类以父类的指针或者是引用的身份出现
相信很多人都能说出来其中实现关键原理,就是两点:虚函数表指针(vptr),虚函数表(vftable)
1 虚函数指针在哪? 干什么用的?
2 什么是虚函数表? 表-》信息 虚函数表-》什么信息的? 有什么用?
3 A 类 a 对象 b c 成员 a对象内存布局是什么样?
gdb一个普通类 内存布局
一个类里面如果有了虚函数 内存布局?
一个类继承一个类 内存布局是什么样子?
一个类继承并且重写虚函数 内存布局是什么样 虚函数表发生什么变化呢?
我们首先来看下没有虚函数的情况下 一个普通的类的实例对象在内存中的分布
demo.cpp
#include
using namespace std;
class Base{
public:
Base():m_base(0),m_base1(' '){};
void test() const { cout<<"Base print()"<
在终端输入如下命令
g++ demo.cpp -g
gdb ./a.out
(gdb) list
(gdb) b 14
(gdb) p b
$1 = {m_base = 0, m_base1 = 32 ' '}
可以看到对象b的内存布局是由成员数据构成。
test.cpp
#include
using namespace std;
class Base{
public:
Base():m_base(0),m_base1(' '){}
virtual void print() const { cout<<"Base print()"<
在终端输入如下命令
g++ test.cpp -g
gdb ./a.out
gdb过程如下:
[echoqian@cvm-10_4_1_62 virtual]$ gdb a.out
(gdb) list
4 public:
5 Base():m_base(0),m_base1(' '){}
6 virtual void print() const { cout<<"Base print()"<, m_base = 0, m_base1 = 32 ' '}
此时我们可以看到对象的布局中多了一个虚函数。并且这个虚函数位于这个对象的开头
我们可以打印出虚函数表
(gdb) info vtbl b
vtable for 'Base' @ 0x400a60 (subobject @ 0x7fffffffe420):
[0]: 0x400986
此时虚函数表中有一个函数地址。虚函数表中就存放了这个函数的地址。
test.cpp
#include
using namespace std;
class Base{
public:
Base():m_base(0),m_base1(' '){}
virtual void print() const { cout<<"Base print()"<
gdb过程如下:
[echoqian@cvm-10_4_1_62 virtual]$ g++ -g test.cpp
[echoqian@cvm-10_4_1_62 virtual]$ gdb test.cpp
(gdb) list
(gdb) b 21
(gdb) r
(gdb) p b
$1 = {_vptr.Base = 0x400b10 <vtable for Base+16>, m_base = 0, m_base1 = 32 ' '}
(gdb) p d
$2 = {<Base> = {_vptr.Base = 0x400af0 <vtable for Derive+16>, m_base = 0, m_base1 = 32 ' '}, m_dirive = 4196928}
(gdb) p &d.m_base
$3 = (int *) 0x7fffffffe408
(gdb) p &d.m_base1
$4 = 0x7fffffffe40c " "
(gdb) p &d.m_dirive
$5 = (int *) 0x7fffffffe410
(gdb) quit
test.cpp
#include
using namespace std;
class Base{
public:
Base():m_base(0),m_base1(' '){}
virtual void print() const { cout<<"Base print()"<
gdb过程如下:
[echoqian@cvm-10_4_1_62 virtual]$ g++ -g test1.cpp
[echoqian@cvm-10_4_1_62 virtual]$ gdb ./a.out
(gdb) list
(gdb) b 22
Breakpoint 1 at 0x400960: file test1.cpp, line 22.
(gdb) r
(gdb) p b
$1 = {_vptr.Base = 0x400b40 <vtable for Base+16>, m_base = 0, m_base1 = 32 ' '}
(gdb) p d
$2 = {<Base> = {_vptr.Base = 0x400b20 <vtable for Derive+16>, m_base = 0, m_base1 = 32 ' '}, <No data fields>}
(gdb) info vtbl b
vtable for 'Base' @ 0x400b40 (subobject @ 0x7fffffffe420):
[0]: 0x4009e2 <Base::print() const>
(gdb) info vtbl d
vtable for 'Derive' @ 0x400b20 (subobject @ 0x7fffffffe410):
[0]: 0x400a0c <Derive::print() const>
我们把对象从首地址开始的4个字节或者是8个字节,这个位置我们称之为虚函数表指针(vptr),它里面包含一个地址指向的就是虚函数表(vftable)的地址。
虚函数表说白了就是里面是一组地址的数组(就是函数指针数组),他所在的位置就是虚函数表指针里面所存储的地址,它里面所包含的地址就是我们重写了父类的虚函数的地址(没有重写父类的虚函数那么默认的就是父类的函数地址)。