C++学习笔记四、C++多态和虚函数的实现机制

4.1 虚析构函数的必要性

将基类的析构函数声明为虚函数后,派生类的析构函数也会自动成为虚函数。这个时候编译器会忽略指针的类型,而根据指针的指向来选择函数;也就是说,指针指向哪个类的对象就调用哪个类的函数。pb、pd 都指向了派生类的对象,所以会调用派生类的析构函数,继而再调用基类的析构函数。如此一来也就解决了内存泄露的问题。

4.2 纯虚函数和抽象类

纯虚函数没有函数体,只有函数声明,在虚函数声明的结尾加上=0,表明此函数为纯虚函数。包含纯虚函数的类称为抽象类(Abstract Class)。之所以说它抽象,是因为它无法实例化,也就是无法创建对象。原因很明显,纯虚函数没有函数体,不是完整的函数,无法调用,也无法为其分配内存空间。抽象类通常是作为基类,让派生类去实现纯虚函数。派生类必须实现纯虚函数才能被实例化。

4.3 C++的虚函数表,多态的实现机制

如果在一个类中包含有虚函数,那么在创建该类对象时就会额外的增加一个数组,数组中的每一个元素都是虚函数的入口地址,不过数组和对象是分开存储的,对象中包含了数组的首地址,这个数组就是虚函数表(virtual function table),简写为vtable。
例子:

#include <stdio.h>
#include <string>
using namespace std;

class People
{
public:
    virtual void display()
    {
		printf("People::display()\n");
	}
    virtual void eating()
    {
		printf("People::eating()\n");
	}
    
    string m_name;
    int m_age;
};

class Student: public People
{
public:
    virtual void display()
    {
		printf("Student::display()\n");
	}
    virtual void examing()
    {
		printf("Student::examing()\n");
	}
    float m_score;
};

class Senior: public Student
{
public:
    virtual void display()
    {
		printf("Senior::display()\n");
	}
    virtual void partying()
    {
		printf("Senior::partying()\n");
	}
    bool m_hasJob;
};
int main()
{
    People *p = new People();
    p -> display();

    p = new Student();
    p -> display();

    p = new Senior();
    p -> display();
    
    return 0;
}

输出:

People::display()
Student::display()
Senior::display()

各个类的对象内存模型如下所示:

通过上图可以发现,虚函数表在对象内存中的索引是不变的,总是在第一个位置,编译器如果发现派生类中有同名虚函数遮蔽了基类中的虚函数,那么将使用派生类的虚函数地址替换基类的虚函数,这样具有遮蔽关系的虚函数在vtable中只会出现一次。修改一下main函数:
int main()
{
	People *pPe = new People();
	People *pSt = new Student();
	People *pSe = new Senior();
		
	void (*pFunc)();
		
	printf("-------------------People对象------------------\n");
	pFunc = (void (*)())*(*(long **)pPe);
	pFunc();
	pFunc = (void (*)())*(++(*(long **)pPe));
	pFunc();

	printf("-------------------Student对象------------------\n");
	pFunc = (void (*)())*(*(long **)pSt);
	pFunc();
	pFunc = (void (*)())*(++(*(long **)pSt));
	pFunc();
	pFunc = (void (*)())*(++(*(long **)pSt));
	pFunc();

	printf("-------------------Senior对象------------------\n");
	pFunc = (void (*)())*(*(long **)pSe);
	pFunc();
	pFunc = (void (*)())*(++(*(long **)pSe));
	pFunc();
	pFunc = (void (*)())*(++(*(long **)pSe));
	pFunc();
	pFunc = (void (*)())*(++(*(long **)pSe));
	pFunc();

	return 0;
}

输出:

-------------------People对象------------------
People::display()
People::eating()
-------------------Student对象------------------
Student::display()
People::eating()
Student::examing()
-------------------Senior对象------------------
Senior::display()
People::eating()
Student::examing()
Senior::partying()

刚好和上面的对象模型符合。

发布了21 篇原创文章 · 获赞 63 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/u014783685/article/details/84989014