C++ Primer 学习笔记_34_面向对象编程(5)--虚函数与多态(二):纯虚函数、抽象类、虚析构函数、动态创建对象
一、纯虚函数
1、虚函数是实现多态性的前提
需要在基类中定义共同的接口
接口要定义为虚函数
2、如果基类的接口没办法实现怎么办?
如形状类Shape
解决方法
将这些接口定义为纯虚函数
3、在基类中不能给出有意义的虚函数定义,这时可以把它声明成纯虚函数,把它的定义留给派生类来做
4、定义纯虚函数:
class <类名> { virtual <类型> <函数名> (<参数表>) = 0; … };
纯虚函数不需要实现
【注解】
含有(或继承)一个或多个纯虚函数的类是抽象基类。除了作为抽象基类的派生类的对象组成部分,不能创建抽象类型的对象。
二、抽象类
定义:
凡是带有纯虚函数的类称为抽象类。抽象类是一种特殊的类,它是为了抽象和设计的目的而建立的,它处于继承层次结构的较上层。
这种类不能声明为对象,只是做为基类为派生类服务。除非在派生类中完全实现基类中所有的虚函数,否则,派生类也是抽象类,不能实例化对象。
只定义了protected型构造函数的类也是抽象类,没有提供public构造函数。如下,Base类也是一个抽象类。
class Base { private: int x; protected: Base(int p = 0) { x = p; } };
作用
抽象类为抽象和设计的目的而声明,将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为。
注意
构造函数不能是虚函数,析构函数可以是虚函数
在实际中为了强调一个类是抽象类,可将该类的构造函数说明为保护的访问控制权限。
1、抽象类不能用于直接创建对象实例,可以声明抽象类的指针和引用
2、可使用指向抽象类的指针支持运行时多态性
【例】
假设A为抽象类,下列声明()是正确的?
A、A fun(int); B、A* p C、int fun(A) D、A obj
解答:B。抽象类不能定义对象,但是可以作为指针或者引用类型使用。若在A和C选项中的A后面加上&或*就是对的。
【例】
抽象类为什么不能实例化?
解答:抽象类中的纯虚函数没有具体的实现,所以没办法实例化。
【例1】
#include<iostream> using namespace std; const double PI=3.14159; class Shapes //抽象类 { protected: int x, y; public: void setvalue(int d, int w=0){x=d;y=w;} virtual void disp()=0;//纯虚函数 }; class Square:public Shapes { public: void disp(){ cout<<"矩形面积:"<<x*y<<endl; } }; class Circle:public Shapes{ public: void disp(){ cout<<"圆面积:"<<PI*x*x<<endl; } }; int main() { Shapes *ptr[2]; //定义对象指针数组 Square s1; Circle c1; ptr[0] = &s1; ptr[0]->setvalue(10, 5); ptr[0]->disp(); ptr[1] = &c1; ptr[1]->setvalue(10); ptr[1]->disp(); return 0; }
【例2】
#include <iostream> #include <vector> #include <string> using namespace std; class Shape { public: virtual void Draw() = 0; virtual ~Shape() { cout << "~Shape..." << endl; } }; class Circle : public Shape { public: void Draw() { cout << "Circle::Draw() ..." << endl; } ~Circle() { cout << "~Circle ..." << endl; } }; class Square : public Shape { public: void Draw() { cout << "Square::Draw() ..." << endl; } ~Square() { cout << "~Square ..." << endl; } }; class Rectangle : public Shape { public: void Draw() { cout << "Rectangle::Draw() ..." << endl; } ~Rectangle() { cout << "~Rectangle ..." << 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 *CreateShape(const string &name) { Shape *ps = 0; if (name == "Circle") { ps = new Circle; } else if (name == "Square") { ps = new Square; } else if (name == "Rectangle") { ps = new Rectangle; } return ps; } }; int main(void) { //Shape s; //Error,不能实例化抽象类 vector<Shape *> v; /* //和下面一样的效果 Shape* ps; ps = new Circle; v.push_back(ps); ps = new Square; v.push_back(ps); ps = new Rectangle; v.push_back(ps); */ Shape *ps; ps = ShapeFactory::CreateShape("Circle"); v.push_back(ps); ps = ShapeFactory::CreateShape("Square"); v.push_back(ps); ps = ShapeFactory::CreateShape("Rectangle"); v.push_back(ps); DrawAllShapes(v); DeleteAllShapes(v); return 0; }
运行结果:
Circle::Draw() ...
Square::Draw() ...
Rectangle::Draw() ...
~Circle ...
~Shape...
~Square ...
~Shape...
~Rectangle ...
~Shape...
解释:Shape类是抽象类,Draw函数是纯虚函数,Circle, Square, Rectangle都重新实现了Draw,在这里把Shape的析构函数声明为虚函数,那么delete 基类指针,会调用派生类的析构函数,这样不容易造成内存泄漏。虚函数可以让我们以一致的观点看待从同一基类继承下来的派生类对象,都是通过Shape* 去调用Draw,但能够实现不同的行为。
三、多态优点
多态性有助于更好地对程序进行抽象
控制模块能专注于一般性问题的处理
具体的操作交给具体的对象去做
多态性有助于提高程序的可扩展性
可以把控制模块与被操作的对象分开
可以添加已定义类的新对象,并能管理该对象
可以添加新类(已有类的派生类)的新对象,并能管理该对象
四、虚析构函数
1、析构函数可以声明为虚函数
delete 基类指针;
程序会根据基类指针指向的对象的类型确定要调用的析构函数
基类的析构函数为虚函数,所有派生类的析构函数都是虚函数
2、构造函数不得是虚函数
3、如果要操作具有继承关系的类的动态对象,最好使用虚析构函数。特别是在派生类析构函数需要完成一些有意义的操作,比如释放内存
4、析构函数还可以是纯虚的。
#include <iostream> using namespace std; // 对于一个没有任何接口的类,如果想要将它定义成抽象类,只能将虚析构函数声明为纯虚的 // 通常情况下在基类中纯虚函数不需要实现 // 例外是纯虚析构函数要给出实现。(给出一个空的实现即可) class Base { public: virtual ~Base() = 0 { } }; class Derived : public Base { }; int main(void) { // Base b; //error, 抽象类 Derived d; return 0; }
五、动态创建对象
上面,实现了一个简单工厂模式来创建不同类对象,但由于c++没有类似new "Circle"之类的语法,导致CreateShape函数中需要不断地if else地去判断,如果有多个不同类对象需要创建,显然这是很费神的,下面通过宏定义注册的方法来实现动态创建对象。
Shape.h:
#ifndef _SHAPE_H_ #define _SHAPE_H_ class Shape { public: virtual void Draw() = 0; virtual ~Shape() {} }; class Circle : public Shape { public: void Draw(); ~Circle(); }; class Square : public Shape { public: void Draw(); ~Square(); }; class Rectangle : public Shape { public: void Draw(); ~Rectangle(); }; #endif // _SHAPE_H_
Shape.cpp:
#include "Shape.h" #include "DynBase.h" #include <iostream> using namespace std; void Circle::Draw() { cout << "Circle::Draw() ..." << endl; } Circle::~Circle() { cout << "~Circle ..." << endl; } void Square::Draw() { cout << "Square::Draw() ..." << endl; } Square::~Square() { cout << "~Square ..." << endl; } void Rectangle::Draw() { cout << "Rectangle::Draw() ..." << endl; } Rectangle::~Rectangle() { cout << "~Rectangle ..." << endl; } REGISTER_CLASS(Circle); REGISTER_CLASS(Square); REGISTER_CLASS(Rectangle);
DynBase.h:
#ifndef _DYN_BASE_H_ #define _DYN_BASE_H_ #include <map> #include <string> using namespace std; typedef void *(*CREATE_FUNC)(); class DynObjectFactory { public: static void *CreateObject(const string &name) { map<string, CREATE_FUNC>::const_iterator it; it = mapCls_.find(name); if (it == mapCls_.end()) return 0; else return it->second(); //func(); } static void Register(const string &name, CREATE_FUNC func) { mapCls_[name] = func; } private: static map<string, CREATE_FUNC> mapCls_; }; // g++ // __attribute ((weak)) __declspec(selectany) map<string, CREATE_FUNC> DynObjectFactory::mapCls_; //头文件被包含多次,也只定义一次mapCls_; class Register { public: Register(const string &name, CREATE_FUNC func) { DynObjectFactory::Register(name, func); } }; #define REGISTER_CLASS(class_name) class class_name##Register { public: static void* NewInstance() { return new class_name; } private: static Register reg_; }; Register class_name##Register::reg_(#class_name, class_name##Register::NewInstance) //CircleRegister #endif // _DYN_BASE_H_
DynTest.cpp:
#include "Shape.h" #include "DynBase.h" #include <iostream> #include <vector> #include <string> using namespace std; 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); } } int main(void) { vector<Shape *> v; Shape *ps; ps = static_cast<Shape *>(DynObjectFactory::CreateObject("Circle")); v.push_back(ps); ps = static_cast<Shape *>(DynObjectFactory::CreateObject("Square")); v.push_back(ps); ps = static_cast<Shape *>(DynObjectFactory::CreateObject("Rectangle")); v.push_back(ps); DrawAllShapes(v); DeleteAllShapes(v); return 0; }
运行结果:
Circle::Draw() ...
Square::Draw() ...
Rectangle::Draw() ...
~Circle ...
~Square ...
~Rectangle ...
在DynBase.h 中#define了一个宏定义REGISTER_CLASS(class_name),且在Shape.cpp 中调用宏定义,拿REGISTER_CLASS(Circle);来说,程序编译预处理阶段会被替换成:
class CircleRegister { public: static void* NewInstance() { return new Circle; } private: static Register reg_; }; Register CircleRegister::reg_("Circle",CircleRegister::NewInstance);
也即定义了一个新类,且由于含有static 成员,则在main函数执行前先执行初始化,调用Register类构造函数,在构造函数中调用DynObjectFactory::Register(name,
func); 即调用DynObjectFactory 类的静态成员函数,在Register函数中通过map容器完成了字符串与函数指针配对的注册,如mapCls_[name] = func;
进入main函数,调用DynObjectFactory::CreateObject("Circle") ,CreateObject函数中通过string找到对应的函数指针(NewInstance),并且调用后返回创建的对象指针,需要注意的是 return
it->second(); 中it->second 是函数指针,后面加括号表示调用这个函数。
这样当需要创建多个不同类对象的时候,就不再需要写很多if else的判断了。
参考:
C++ primer 第四版
C++ primer 第五版
版权声明:本文为博主原创文章,未经博主允许不得转载。