虚函数的重写![]()
所以虚函数的重写实际上在重写的是函数体,多态的实现也是靠重写虚函数实现的。注意上边的ptr始终是Person类型的指针,图片里说的ptr的指向是指的其指向的对象,因为传给ptr的是指向Student的指针,ptr指向的对象是Student,指向哪个对象,调用的就是该对象里的函数体。
一道恶心的题
问程序运行结果是什么?
class A { public: virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; } virtual void test() { func(); } }; class B : public A { public: void func(int val = 0) { std::cout << "B->" << val << std::endl; } }; int main(int argc, char* argv[]) { B* p = new B; p->test(); p->func(); return 0; }首先明确继承不是拷贝,说白了只是改变了编译器的查找逻辑,让编译器多到一个地方去找,然后定义派生类的对象的时候,在内存里,上边先有一份基类,下边才是自己的成员。p是指向B的指针,p调用test函数,是先在B里没找到,再去A里找到了,因此就调用A里的test,A里的test的this指针指向的自然就是A*,this指针去调用func,由于this真正指向的是B,因此就去调用B里的func,跟上边同理,仅仅只是使用函数体,函数声明还是A里的函数声明,因此val的缺省值是1。所以程序运行结果就是B->1。
注意上边的分析都是建立在多态的前提之下的,func构成多态,满足多态的2个条件。这里别看两个func的参数缺省值不一样,但它们是构成重写的,重写有3同,只要是返回值类型,函数名,函数参数列表相同(参数列表的类型相同),跟缺省值什么的都没关系。
改编:此时运行结果又如何。
B* p = new B; p->func();结果是B->0,这里的调用就是普通调用,根本不构成多态,调用func的仅仅是B*类型的指针,也就是派生类自己本类型的指针在调用自己类里边的成员函数。
析构函数的重写
class A { public: ~A() { cout << "~A()" << endl; } }; class B : public A { public: ~B() { cout << "~B()" << endl; } }; int main() { //普通调用就是直接看类型就可以了 A* p1 = new A; A* p2 = new B; delete p1; delete p2; return 0; }由于普通调用直接看类型,p1和p2都是A类型的指针,delete的时候自然就是调用了两次A类里的析构函数,没有调用B类的析构,此时造成内存泄露。delete的原理是先调用对应类的析构函数,再调用operator delete,底层用free释放资源。所以我们需要将~A()变成虚函数,实现多态,解决内存泄露。
虚函数表(本质是函数指针数组)
当派生类继承含有虚函数的基类时,其虚函数表并非直接拷贝基类虚函数表,而是以基类虚函数表为模板进行构建:
编译器先将基类虚函数表的所有条目完整继承到派生类虚函数表中;
若派生类重写了基类的某个虚函数,会将派生类虚函数表中该虚函数对应位置的函数指针,替换为派生类自身重写后的函数地址(即实现 “覆盖”)。
派生类独有的、基类中没有声明的虚函数,会按声明顺序追加到派生类虚函数表的末尾。