[C++学习笔记13]虚函数与多态
- 多态
多态性是面向对象程序设计的重要特征之一;
多态性是指发出同样的消息被不同类型的对象接收时有可能导致完全不同的行为;
多态的实现方式:
静态绑定
函数重载
运算符重载
模板
动态绑定
虚函数 - 静态绑定与动态绑定
静态绑定
绑定过程出现在编译阶段,在编译期就已确定要调用的函数
动态绑定
绑定过程在程序运行是执行,在程序运行时才确定要调用的函数。 - 虚函数
虚函数概念
在基类中冠以关键字virtual的成员函数
虚函数的定义
virtual 函数类型 函数名称(参数列表);
如果一个函数在基类中被声明为虚函数,则他在所有派生类中都是虚函数
注:
只有基类指针或引用调用虚函数才能引发动态绑定;
虚函数不能声明为静态与有元;
虚函数实例
class Base { public: virtual void Func1() { cout << "Base::Func1() ... " << endl; } void Func2() { cout << "Base::Func2() ... " << endl; } }; class Derived : public Base { public: void Func1() { cout << "Derived::Func1() ... " << endl; } void Func2() { cout << "Derived::Func2() ... " << endl; } }; int main(void) { Derived d; Base *pb = &d; // 基类指针指向派生类对象,引发动态绑定 pb->Func1(); // Derived::Func1() pb->Func2(); // Base::Func2() /*Base &rb = d; rb.Func1(); rb.Func2();*/ // 引用与指针一样 return 0; }
- 虚析构函数
何时需要虚析构函数?
当你可能通过基类指针删除派生类对象时
如果你打算允许其他人通过基类指针调用对象的析构函数(通过delete这样做是正常的),并且被析构的对象是有重要的析构函数的派生类的对象
虚析构函数实例
#include <iostream> using namespace std; class Base { public: virtual void Func1() { cout << "Base::Func1() ... " << endl; } void Func2() { cout << "Base::Func2() ... " << endl; } Base() { cout << "Base::Base() ... " << endl; } virtual ~Base() { cout << "Base::~Base() ... " << endl; } }; class Derived : public Base { public: void Func1() { cout << "Derived::Func1() ... " << endl; } void Func2() { cout << "Derived::Func2() ... " << endl; } Derived() { cout << "Derived::Derived() ... " << endl; } ~Derived() { cout << "Derived::~Derived() ... " << endl; } }; int main(void) { Base *pb = new Derived; // 未将析构函数声明为virtual之前 //delete pb; // 只调用Base类的析构函数 // 这样当导致在Derived构造函数中分配的内存不能正确释放,导致内存泄漏 // 将基类析构函数声明为virtual之后 delete pb; // 先调用Derived析构函数再调用Base析构函数 return 0; }
- 虚表指针
虚函数的动态绑定是通过虚表来实现的
包含虚函数的类头4个字节存放指向虚表的指针
虚表指针实例
class Base { public: virtual void Func1() { cout << "Base::Func1() ... " << endl; } virtual void Func2() { cout << "Base::Func2() ... " << endl; } int data1_; }; class Derived : public Base { public: void Func2() { cout << "Derived::Func2() ... " << endl; } virtual void Func3() { cout << "Derived::Func3() ... " << endl; } int data2_; }; typedef void(*FUNC)(); int main(void) { cout << sizeof(Base) << endl; // 8,虚表指针+data1_ cout << sizeof(Derived) << endl; // 12, 虚表指针+data1_+data_2 // 探究Base基类对象的内存结构 Base b; cout << &b << endl; cout << &b.data1_ << endl; long **p = (long **)&b; FUNC func; func = (FUNC)p[0][0]; func(); // Base::Func1(); func = (FUNC)p[0][1]; func(); // Base::Func2(); cout << endl; // 探究Derived派生类对象的内存结构 Derived d; cout << &d << endl; cout << &d.data1_ << endl; cout << &d.data2_ << endl; p = (long **)&d; func = (FUNC)p[0][0]; func(); // Base::Func1(); func = (FUNC)p[0][1]; func(); // Derived::Func2(); func = (FUNC)p[0][2]; func(); // Derived::Func3(); /* Base *pp = &d; // 这样才会动态绑定 pp->Func1(); d.Func1() // 这样是静态绑定,编译器就指定了 */ return 0; }
虚表结构图
- object slicing与虚函数
MFC简单原型实例
class CObject { public: virtual void Serialize() { cout << "CObject::Serialize() ..." << endl; } }; class CDocument : public CObject { public: int data1_; void func() { cout << "CDocument::func() ..." << endl; Serialize(); // 在基类中的非虚函数中调用虚函数 } virtual void Serialize() { cout << "CDocument::Serialize() ..." << endl; } CDocument() // 提供了默认拷贝构造,必须提供默认构造函数 { cout << "CDocument()" << endl; } CDocument(const CDocument &other) { cout << "CDocument(const CDocument &other)" << endl; } }; class CMyDoc : public CDocument { public: int data2_; virtual void Serialize() { cout << "CMyDoc::Serialize() ..." << endl; } }; int main() { CMyDoc mydoc; CMyDoc *pmydoc = new CMyDoc; cout << "#1 testing" << endl; mydoc.func(); // CDocument::func() \n CMyDoc::Serialize() cout << "#2 testing" << endl; ((CDocument *)pmydoc)->func(); // CDocument::func() \n CMyDoc::Serialize() cout << "#3 testing" << endl; pmydoc->func(); // CDocument::func() \n CMyDoc::Serialize() cout << "#4 testing" << endl; ((CDocument)mydoc).func(); // CDocument::func() \n CDocument::Serialize() // 向上转型,完完全全将派生类对象转换成了基类对象,虚表也发生了变化 // 要调用拷贝构造函数 return 0; }
- overload、overwrite、override
成员函数被重载的特征
相同的范围(在同一个类中);
函数名字相同;
参数不同;
virtual关键字可有可无。
覆盖是指派生类函数覆盖基类函数,特征是
不同的范围(分贝位于派生类与基类);
函数名字相同;
参数相同;
基类函数必须有virtual关键字。
重定义(派生类与基类)
不同的范围(分别位于派生类与基类);
函数名与参数都相同,无virtual关键字;
函数名相同,参数不同,virtual可有可无。 - 纯虚函数
纯虚函数是实现多态性的前提
需要在基类中定义共同的接口;
接口要定义为虚函数
如果基类的接口没办法实现怎么办?
如形状类Shape
解决办法
将这些接口定义为纯虚函数--抽象类
在基类中不能给出有意义的纯虚函数定义,这时可以把它说明成纯虚函数,把它的定义留给派生类来做
定义纯虚函数
class 类名 {
virtual 返回值类型 函数名(参数表) = 0;
};
纯虚函数一般不需要实现,注意纯虚析构函数 - 抽象类
作用
抽象类为抽象和设计的目的而声明,将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为;
对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现。
注意
抽象类只能作为基类来使用
不能声明抽象类的对象,可以声明抽象类的指针和引用
构造函数不能是虚函数,析构函数可以是虚函数
派生类中必须实现基类中的纯虚函数,否则它仍将被看作一个抽象类 - 多态优点
多态性有助于更好地对程序进行抽象
控制模块能专注于一般性问题的处理
具体的操作交给具体的对象去做
多态性有助于提高程序的可扩展性
可以把控制模块与被操作的对象分开
可以添加已定义类的新对象,并能管理该对象
可以添加新类(已有类的派生类)的新对象,并能管理该对象
多态优点实例
#include <iostream> #include <vector> #include <string> using namespace std; class Shape { public: virtual void Draw() = 0; virtual ~Shape() {} }; class Circle : public Shape { public: void Draw() { cout << "Circle Draw ... " << endl; } ~Circle() { cout << "~Circle ... " << endl; } }; class Rectangle : public Shape { public: void Draw() { cout << "Rectangle Draw .." << endl; } ~Rectangle() { cout << "~Rectangle ... " << endl; } }; class Square : public Shape { public: void Draw() { cout << "Square Draw ..." << endl; } ~Square() { cout << "~Square ... " << endl; } }; void drawAllShapes(const vector<Shape *> v) { vector<Shape *>::const_iterator it; for (it = v.begin(); it != v.end(); ++it) { (*it)->Draw(); } } void deleteAllShapes(const vector<Shape *> v) { vector<Shape *>::const_iterator it; for (it = v.begin(); it != v.end(); ++it) { delete (*it); } } // 简单的工厂模式 class ShapeFactory { public: static Shape * createShapes(const string &name) { Shape *ps = NULL; if (name == "Circle") ps = new Circle; else if (name == "Rectangle") ps = new Rectangle; else if (name == "Square") ps = new Square; return ps; } }; int main(void) { //Shape s; // Error, 抽象类不能实例化对象 vector<Shape *> v; Shape *ps = NULL; /*ps = new Circle; v.push_back(ps); ps = new Rectangle; v.push_back(ps); ps = new Square; v.push_back(ps);*/ // 将此封装成函数,使用简单的工厂模式 ps = ShapeFactory::createShapes("Circle"); v.push_back(ps); ps = ShapeFactory::createShapes("Rectangle"); v.push_back(ps); ps = ShapeFactory::createShapes("Square"); v.push_back(ps); drawAllShapes(v); deleteAllShapes(v); // 未将Shape基类的析构函数声明为虚函数之前,并不会调用各个派生类的析构函数 // 声明为虚函数之后,就会调用了 return 0; }
- 虚析构函数
析构函数可以声明为虚函数
delete 基类指针;程序会根据基类指针指针指向的对象的类型确定要调用的析构函数;
基类的析构函数为虚函数,所有派生类的析构函数都是虚函数
如果要操作具有继承关系的类的动态对象,最好使用虚析构函数,特别是在析构函数需要玩笑恒一些有意义的操作--比如释放内存
析构函数可以是纯虚的
虚析构函数实例
// 当一个类是个空的,我希望它是个抽象类,不能实例化,将析构函数声明为纯虚的 // 通常情况下在基类中纯虚函数不需要实现 // 例外是纯虚析构函数要给出实现(给出一个空的实现即可) class Base { public: virtual ~Base() = 0 { } }; class Derived : public Base { }; int main(void) { Derived d; // 当基类析构函数是纯虚函数,当d析构的时候, // 先调用Derive的析构函数再调用Base析构函数就会出错 // 在基类的析构函数给出函数体就没问题了 return 0; }