虚函数
在这之前,我们先聊聊虚函数。虚就是代表不是真实的,可以灵活的,函数就是方法,虚函数就是用不同的策略实现共同的方法。虚函数是在基类中被声明为virtual,并在派生类中重新定义的成员函数,可以实现成员函数的动态重载。通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。
纯虚函数
语法
将成员函;数声明为virtual
后面加上=0
该函数没有函数体
C++中的纯虚函数,一般在函数名后使用=0作为此类函数的标志。Java,C#等语言中,则直接使用abstract作为关键字修饰这个函数名,表示这是抽象函数。所以说,纯虚函数不仅仅在C++中,在整个软件语言与程序代码里头,是一个很重要的思想方式。
在基类中不对虚函数实例化,声明为纯虚函数,在派生类里去实现。这个好处就是留下了比较大的空间,让你能够在后续自由发挥完善你的业务逻辑。
这也是面向对象的多态特性。多态性是指相同对象收到不同消息或不同对象收到相同消息产生不同的实现,虚函数和继承就很好的诠释了运行时的多态性。
抽象类
包含纯虚函数的类成为抽象类。由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。抽象类不能被实例化,既然无法创建该对象,为什么要去定义这个东西呢。
其实这就是抽象类的精华,我们知道Java里有interface,它是一个接口规范,凡是遵循此规范的类,都必须实现指定的函数接口,通常是一系列接口。
可以看到代码里f2是虚函数,f3是纯虚函数。
内存分布
首先明确的是在类里面成员函数是不占内存的,看内存你就要牢记抓住内存的入口地址,或者说首地址,然后是偏移地址。
从以上内存结构分布图可以看出,上面是内存分布,下面是虚表。vs编译器把虚表指针放在了内存的开始处,也就是0地址偏移处,然后再是成员变量,这里还包含了字节对齐的内容。
下面生成了虚表,这张虚表对应的是虚指针在内存中的分布,下面列出了虚函数,左侧的0是这个虚函数的序号,这里只有一个虚函数,所以只有一项,如果有多个虚函数,会有序号为1,为2的虚函数列出来。编译器是在构造函数创建这个虚表指针以及虚表的。
编译器当创建一个含有虚函数的父类的对象是,编译器在对象构造时将虚表指针指向父类的虚函数;同样,当创建子类的对象时,编译器在构造函数里将虚表指针(子类只有一个虚表指针,它来自父类)指向子类的虚表(这个虚表里面的虚函数入口地址是子类的)。