多态
1.多态与虚函数
多态性是指同一个操作作用于不同的对象就会产生不同的响应。多态性分为静态多态性和动态多态性,其中函数重载和运算符重载属于静态多态性,虚函数属于动态多态性。
一旦一个函数被声明为虚函数,无论经历多少次派生,都会保持虚函数的特性,即使派生类中没有使用virtual关键字,其仍然是虚函数。
虚函数有以下注意点:
1)virtual应用于修饰public或protect成员函数。使用virtual修饰private成员函数时尽管不会报错,但没有意义。
2)如果派生类中没有对基类虚函数进行重定义,则它将继承基类中的虚函数。
3)友元不能是虚函数,因为友元不是类成员,只有成员才能是虚函数,但可以在友元内调用虚函数解决问题。虚函数可以是另一个类的友元。
2.虚函数的访问
1)基指针访问
/************************classdef.h********************/ #include <iostream.h> using namespace std; class base { public: virtual void disp() { cout<<"hello,base"<<endl; } }; class child:public base { public: void disp() { cout <<"hello,child"<<endl; } };
/************************main1.c********************/ #include "classdef.h" int main() { base obj_base; base *pBase = &obj_base; child obj_child; child *pChild = &obj_child; pBase->disp(); pChild->disp(); pBase = pChild; //将派生类指针赋值给基类函数 pBase->disp(); //使用基类指针调用虚函数 pChild = (child*)&obj_base; //使用基类对象地址为派生类指针赋值 pChild->disp(); //使用派生类指针调用虚函数,只取决于赋值对象 pChild->base::disp(); //使用类名加作用域限定符指明要调用的版本 return 0; }
输出结果:
hello,base hello,child hello,child hello,base hello,base
2)引用访问
/************************main2.c********************/ #include "classdef.h" int main() { base obj_base; base& rBase1 = obj_base; child obj_child; rBase1.disp(); //基类引用调用虚函数,基类中的disp版本 base& rBase2 = obj_child; rBase2.disp(); //基类引用调用虚函数,派生类中的disp版本 return 0; }
输出结果:
hello,base hello,child
3)类内访问,使用this指针。
4)在构造函数或析构函数中进行访问
class base { public: virtual void disp() { cout<<"hello,base"<<endl; } }; class child:public base { public: child() { disp(); } void disp() { cout <<"hello,child"<<endl; } };
在上述代码中,类创建child对象时,输出信息总是“hello,child”。换言之,在child的构造函数中,无论是用disp()还是this->disp()来调用,编译器都将之解释为“child::disp()”,此时,若想在child构造函数中调用base类disp函数,必须使用base::disp()的形式;
2.纯虚函数与抽象类
纯虚函数是一种特殊的虚函数,它是指不必再基类中定义,但必须在派生类中被覆盖的函数。其形式如下:
virtual 类型 函数名 (参数表)=0;
一个类可以包含多种纯虚函数。只要类中含有一个纯虚函数,该类便为抽象类。一个抽象类只能作为基类来派生新类,不能创建抽象类的对象。
1)另一种抽象类:类中只定义了protect型的构造函数。
2)private型构造函数。不能在外部函数和派生类中使用“类名+对象吗”创建实例,但可以通过类的static函数成员来创建类的实例。
3)虚析构函数。虽然构造函数不能被声明为虚函数,但析构函数可以,形式如下:
virtual ~析构函数名();
析构函数调用不当带来的内存泄漏:
class base { private: char* data public: base() { data = new char[64]; cout<<"base类构造函数被调用"<<endl; }; ~base() { delete [] data; cout<<"base类析构函数被调用"<<endl; }; }; class child : public base { private: char* m_data public: child():base() { m_data = new char[64]; cout<<"child类构造函数被调用"<<endl; }; ~child() { delete [] m_data; cout<<"child类析构函数被调用"<<endl; }; }; int main() { base *pB = new child; delete pB; return 0; }
输出结果:
base类析构函数被调用
child类析构函数被调用
base类析构函数被调用
这段程序编译连接没有错,但是在“delete pB”中pB类型决定了调用哪个类的析构函数,因此编译器会调用base类的析构函数,这意味这child类析构函数没有被调用,child类中的m_data指向的内存泄漏了。
解决方案:将基类中的析构函数定义为虚析构函数就能解决这个问题。