C++学习之路:纯虚函数
背景:
当有些方法无法继承,或者说无意义的时候,例如shape类,那么基类的接口便无法实现。
那么这时候就需要引入纯虚函数。
几何基类:
Shape 拥有Draw方法,三角,圆形,菱形等Draw方法各不相同。只能使用纯虚函数,
拥有纯虚函数的基类称为抽象类,抽象类无法被实例化,纯虚函数也不需要实现。
@纯虚函数的定义
#纯虚函数一般不需要实现。
@抽象类
作用:抽象类作为抽象和设计的目的而声明,将有关的数据和行为组织在一个集成层次结构中,保证派生类具有要求的行为。
对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现。
Warning
1.抽象类智能作为基类来使用。
2.不能声明抽象的对象。
3.构造函数不能是虚函数,析构函数可以是虚函数。
@为什么析构函数不能是虚函数?
如果构造函数是虚函数,那么在构造函数调用之前,虚函数表是不能确定的,在实例化之前是找不到虚函数入口的,也就是无法动态绑定,那么该类就无法构造。
那么析构函数呢?
实际上抽象类的析构函数,理应是一个虚析构函数。
#include <iostream> #include <string> #include <vector> using namespace std; class Shape{ public: ~Shape(){ cout << "~Shape" << endl; } /*virtual */void Draw() { cout << "hehe" << endl; } }; class Circle : public Shape { public: void Draw(){ cout << "Draw()..." << endl; } }; class Square: public Shape { public: void Draw(){ cout << "Square()..." << endl; } }; void DrawAllShapes(const std::vector<Shape*>& v) { std::vector<Shape*>::const_iterator it; for(it=v.begin(); it != v.end(); it++) { (*it)->Draw(); } } void DeleteAllShapes(const std::vector<Shape*>& v) { std::vector<Shape*>::const_iterator it; for(it=v.begin(); it != v.end(); it++) { delete(*it); } } int main(int argc, const char *argv[]) { //Shape s;//不能实例化一个抽象类 std::vector<Shape*> v; Shape* ps; ps = new Circle; v.push_back(ps); ps = new Square; v.push_back(ps); DrawAllShapes(v); return 0; }
如果基类的Draw方法不是虚的,那么便是静态绑定,main函数中的指针编译期间就确定了该调用基类的Draw方法,我们看结果打印:
➜ cpp ./a.out hehe hehe
我们把基类的Draw方法改为虚的,那么派生类同名的Draw方法也是虚的,那么便在派生类中维护一个虚函数表,那么便可以再运行时确定调用哪个Draw方法。
#include <iostream> #include <string> #include <vector> using namespace std; class Shape{ public: ~Shape(){ cout << "~Shape" << endl; } virtual void Draw() =0 ; }; class Circle : public Shape { public: void Draw(){ cout << "Draw()..." << endl; } }; class Square: public Shape { public: void Draw(){ cout << "Square()..." << endl; } }; void DrawAllShapes(const std::vector<Shape*>& v) { std::vector<Shape*>::const_iterator it; for(it=v.begin(); it != v.end(); it++) { (*it)->Draw(); } } void DeleteAllShapes(const std::vector<Shape*>& v) { std::vector<Shape*>::const_iterator it; for(it=v.begin(); it != v.end(); it++) { delete(*it); } } int main(int argc, const char *argv[]) { //Shape s;//不能实例化一个抽象类 std::vector<Shape*> v; Shape* ps; ps = new Circle; v.push_back(ps); ps = new Square; v.push_back(ps); DrawAllShapes(v); return 0; }
结果打印:
➜ cpp ./a.out Draw()... Square()...
tip:若基类析构函数不为虚函数,那么派生类的虚构函数就也不是虚函数
同理我们可以想象一下,如果在main中用基类指针ps 持有一个派生类。
然后delete(ps);
Shape* ps; ps = new Square; delete(ps);
像上述代码,结果只会调用基类的析构函数,而不会调用派生类的析构函数,如果派生类在堆上有资源,那么就面临内存泄露的风险。
看看结果。
#include <iostream> #include <string> #include <vector> using namespace std; class Shape{ public: ~Shape(){ cout << "~Shape" << endl; } virtual void Draw() =0 ; }; class Circle : public Shape { public: void Draw(){ cout << "Draw()..." << endl; } }; class Square: public Shape { public: void Draw(){ cout << "Square()..." << endl; } }; void DrawAllShapes(const std::vector<Shape*>& v) { std::vector<Shape*>::const_iterator it; for(it=v.begin(); it != v.end(); it++) { (*it)->Draw(); } } void DeleteAllShapes(const std::vector<Shape*>& v) { std::vector<Shape*>::const_iterator it; for(it=v.begin(); it != v.end(); it++) { delete(*it); } } int main(int argc, const char *argv[]) { //Shape s;//不能实例化一个抽象类 std::vector<Shape*> v; Shape* ps; ps = new Circle; v.push_back(ps); ps = new Square; v.push_back(ps); DrawAllShapes(v); DeleteAllShapes(v); return 0; }
结果打印:
Draw()... Square()... ~Shape ~Shape
然后我们将基类的析构函数设置为虚函数,再看看打印结果
➜ cpp ./a.out Draw()... Square()... ~Circle ~Shape ~Square ~Shape
如果基类析构函数为虚构函数,便会正确的析构。先调用派生类自身的析构函数,再调用基类的析构函数。
总结:
1.抽象类不能用于直接创建对象实例,但是可以申明抽象类的指针和引用。
2.可使用指向抽象类的指针支持运行时的多态。
3.派生类中必须实现基类中的纯虚函数,否则仍会成为一个抽象类。
下面列出一个抽象类常用的例子,@看注释
#include <iostream> using namespace std; // 对于一个没有任何接口的类,如果想要将它定义成抽象类,只能将虚析构函数声明为纯虚的 // 通常情况下在基类中纯虚函数不需要实现 // 例外是纯虚析构函数要给出实现。(给出一个空的实现即可) class Base { public: virtual ~Base() = 0 { } }; class Drived : public Base { }; int main(void) { Drived d; return 0; }
如果基类虚析构函数不实现,编译无法通过。