polymorphism(多态): 虚函数 + 抽象基础类

来源:互联网 发布:c语言指针例子 编辑:程序博客网 时间:2024/06/05 19:33

C++中, 多态(polymorphism)是三大关键词(分别是encapsulation, inherittance, polymorphsim)之一。

所谓的多态, 简单的说就是多种形态。

OOP支持如下的两种多态的实现方式:

(1) static binding, 又称为early binding, 就是在compile time 实现的绑定。  主要通过函数重载(function overriding)和运算元(operator  overloading)重载实现。

(2) dynamic binding, 又称late binding, 就是在run time 的时候实现的绑定。 对同一个函数赋予其多种含义(associtate mamy meanings to one function)。 又称为运行时多态(run time polymorphisim),或者funcion overiding。 正是因为有了动态绑定, 才使得程序员可以为多个不同类的对象(但是这些对象是相关的, 比如源于继承, 即类有共同的祖先)设计出相同的接口(common interface), 从而节省复杂度和开发时间。

什么事绑定?? 我们知道, 编译器为所有user defined  function 留一个内存空间去存放, 然后通过函数code所在的起始内存地址去跟踪这些函数。 函数名同函数所在的内存中的地址绑定起来(或者说对应起来), 就是绑定的含义了。

静态绑定就是编译器在编译期间(compile time), 将所有的函数调用的位置同函数在内存中的地址绑定起来。

动态绑定就是程序运行的时候, 调用的函数才绑定到合适的函数上。 即运行时才决定呼叫哪一个函数。 当然究竟呼叫哪一个函数取决于调用者(user)。  

静态绑定(编译时多态)的优点就是速度比较快, 通过函数重载和运算元重载实现

动态绑定(运行时多态)的优点是比较flexibility, 通过继承 +虚函数(inherittance + virtual function) 实现。

今天主要讲动态绑定, 或者说运行时多态。

虚函数(virtual function)

为了了解虚函数的作用, 我们首先看看如果没有虚函数会发生什么。


首先, 我们知道, 可以实现base class和 derived class之间的转型(casting)。

(1) 可以把一个derived class的object赋值给一个base class的object。 因为派生类具有基类的所有properties。

(2)可以把一个派生类对象(derived class object)的地址copy 到一个 base class 的pointer。 

(2) 一个派生类对象可以取成base class 型的别名(reference)。 

但是, 出现这种转型的时候, 我们只能access到base class的那一部分的members, 而无法access到派生类的成员。

#include <iostream>#include <string>using namespace std;class CPoint { // base class: class CPointprivate:    double x, y;public:    CPoint(double a = 0,double b = 0): x(a),y(b){}    void setPoint(double a = 0,double b = 0) {x = a; y = b;}    double getX() const {return x;}    double getY() const {return y;}    friend ostream& operator<<(ostream &, const CPoint&);    string toString() const {return "CPoint";}};// friend function is not member functionostream& operator<<(ostream& out, const CPoint& p) {    out << p.x << " " << p.y;    return out;}class CRect: public CPoint {    double lg, wd; // new private data memeberspublic:    CRect(double a, double b, double c, double d):lg(c), wd(d) {        setPoint(a, b);    }    void setRect(double a, double b, double c, double d) {        setPoint(a, b); lg = c; wd = d;    }    double getL() const {return lg;}    double getW() const {return wd;}    double Area() const {return lg * wd;}    friend ostream& operator<<(ostream&, const CRect&);    string toString() const {return "CRect";}};ostream& operator<<(ostream& out, const CRect& r) {    out << r.getX() << " " << r.getY() << " area " << r.Area();    return out;}int main() {    CRect cr1(2, 3, 20, 10);    cout << "old: " << cr1 << endl;    cr1.setRect(5, 5, 9, 7);    cout << "new: " << cr1 << endl;    CPoint &rRf = cr1;    cout << "rRf: " << rRf << endl; // CPoint 型别的别名呼叫的时候回按照自己的型别(CPoint)调用, 而不是其指向的实际的类型(CRect)下面的函数    cout << rRf.toString() << endl; // rRF看不到其指向的对象的对象自己的属性, 只能看到继承的属性    return 0;}
运行结果如下:


再比如下面:

#include <iostream>#include <string>using namespace std;class CPoint { // base class: class CPointprivate:    double x, y;public:    CPoint(double a = 0,double b = 0): x(a),y(b){}    void setPoint(double a = 0,double b = 0) {x = a; y = b;}    double getX() const {return x;}    double getY() const {return y;}    friend ostream& operator<<(ostream &, const CPoint&);    string toString() const {return "CPoint";}};// friend function is not member functionostream& operator<<(ostream& out, const CPoint& p) {    out << p.x << " " << p.y;    return out;}class CRect: public CPoint {    double lg, wd; // new private data memeberspublic:    CRect(double a, double b, double c, double d):lg(c), wd(d) {        setPoint(a, b);    }    void setRect(double a, double b, double c, double d) {        setPoint(a, b); lg = c; wd = d;    }    double getL() const {return lg;}    double getW() const {return wd;}    double Area() const {return lg * wd;}    friend ostream& operator<<(ostream&, const CRect&);    string toString() const {return "CRect";}};ostream& operator<<(ostream& out, const CRect& r) {    out << r.getX() << " " << r.getY() << " area " << r.Area();    return out;}class CCuboid: public CRect {protected:    double ht;public:    CCuboid(double a, double b, double c, double d, double e): CRect(a, b, c, d) {        ht = e;    }    void setHeight(double d = 1.0) {ht = d;}    double getHeight() const {return ht;}    double Area() const {        return (2 * CRect::Area() + 2 * getL() * ht                + 2 * getW() * ht);    }    friend ostream& operator<<(ostream &, const CCuboid&);    string toString() const {        return "CCuboid";    }};ostream& operator<<(ostream& out, const CCuboid& C) {    out << C.getX() << " " << C.getY() << " surface " << C.Area();    return out;}void DisplayObject(const CPoint &p) {    cout << p.toString() << endl;}int main() {    CPoint o1(5, 7);    CRect o2(2, 4, 5, 7);    CCuboid o3(1, 3, 5, 7, 9);    DisplayObject(o1);    DisplayObject(o2);    DisplayObject(o3);    return 0;}
运行结果如下:


上述结果显然不好。

而使用虚函数就能解决基类的指针指向派生类对象或者基类别名指向派生对象无法access某些成员的问题了上述的问题。 

在类中声明虚函数, 要加virtual关键字。 虚函数告诉compiler在编译时:

(1)不知道这个函数是如何实现的, 只有等到程序运行的时候才知道。

(2) 根据物件(object instance)取用函数的implementation。

这就是动态绑定。

修改如下:

#include <iostream>#include <string>using namespace std;class CPoint { // base class: class CPointprivate:    double x, y;public:    CPoint(double a = 0,double b = 0): x(a),y(b){}    void setPoint(double a = 0,double b = 0) {x = a; y = b;}    double getX() const {return x;}    double getY() const {return y;}    friend ostream& operator<<(ostream &, const CPoint&);    virtual string toString() const {return "CPoint";}  <span style="font-family: Arial, Helvetica, sans-serif;">// 为virtual了</span>};// friend function is not member functionostream& operator<<(ostream& out, const CPoint& p) {    out << p.x << " " << p.y;    return out;}class CRect: public CPoint {    double lg, wd; // new private data memeberspublic:    CRect(double a, double b, double c, double d):lg(c), wd(d) {        setPoint(a, b);    }    void setRect(double a, double b, double c, double d) {        setPoint(a, b); lg = c; wd = d;    }    double getL() const {return lg;}    double getW() const {return wd;}    virtual double Area() const {return lg * wd;} <span style="font-family: Arial, Helvetica, sans-serif;">// 为virtual了</span>    friend ostream& operator<<(ostream&, const CRect&);    virtual string toString() const {return "CRect";} <span style="font-family: Arial, Helvetica, sans-serif;">// 为virtual了</span>};ostream& operator<<(ostream& out, const CRect& r) {    out << r.getX() << " " << r.getY() << " area " << r.Area();    return out;}class CCuboid: public CRect {protected:    double ht;public:    CCuboid(double a, double b, double c, double d, double e): CRect(a, b, c, d) {        ht = e;    }    void setHeight(double d = 1.0) {ht = d;}    double getHeight() const {return ht;}    virtual double Area() const {   <span style="font-family: Arial, Helvetica, sans-serif;">// 为virtual了</span>        return (2 * CRect::Area() + 2 * getL() * ht                + 2 * getW() * ht);    }    friend ostream& operator<<(ostream &, const CCuboid&);    virtual string toString() const {  <span style="font-family: Arial, Helvetica, sans-serif;">// 为virtual了</span>        return "CCuboid";    }};ostream& operator<<(ostream& out, const CCuboid& C) {    out << C.getX() << " " << C.getY() << " surface " << C.Area();    return out;}void DisplayObject(const CPoint &p) {    cout << p.toString() << endl;}int main() {    CPoint o1(5, 7);    CRect o2(2, 4, 5, 7);    CCuboid o3(1, 3, 5, 7, 9);    DisplayObject(o1);    DisplayObject(o2);    DisplayObject(o3);    return 0;}
运行结果如下:


再比如下面(其实和上面差不多, 只不过这里使用基类的指针):

#include <iostream>#include <string>using namespace std;class CPoint { // base class: class CPointprivate:    double x, y;public:    CPoint(double a = 0,double b = 0): x(a),y(b){}    void setPoint(double a = 0,double b = 0) {x = a; y = b;}    double getX() const {return x;}    double getY() const {return y;}    friend ostream& operator<<(ostream &, const CPoint&);    virtual string toString() const {return "CPoint";}};// friend function is not member functionostream& operator<<(ostream& out, const CPoint& p) {    out << p.x << " " << p.y;    return out;}class CRect: public CPoint {    double lg, wd; // new private data memeberspublic:    CRect(double a, double b, double c, double d):lg(c), wd(d) {        setPoint(a, b);    }    void setRect(double a, double b, double c, double d) {        setPoint(a, b); lg = c; wd = d;    }    double getL() const {return lg;}    double getW() const {return wd;}    virtual double Area() const {return lg * wd;}    friend ostream& operator<<(ostream&, const CRect&);    virtual string toString() const {return "CRect";}};ostream& operator<<(ostream& out, const CRect& r) {    out << r.getX() << " " << r.getY() << " area " << r.Area();    return out;}class CCuboid: public CRect {protected:    double ht;public:    CCuboid(double a, double b, double c, double d, double e): CRect(a, b, c, d) {        ht = e;    }    void setHeight(double d = 1.0) {ht = d;}    double getHeight() const {return ht;}    virtual double Area() const {        return (2 * CRect::Area() + 2 * getL() * ht                + 2 * getW() * ht);    }    friend ostream& operator<<(ostream &, const CCuboid&);    virtual string toString() const {        return "CCuboid";    }};ostream& operator<<(ostream& out, const CCuboid& C) {    out << C.getX() << " " << C.getY() << " surface " << C.Area();    return out;}void DisplayObject(const CPoint &p) {    cout << p.toString() << endl; // 必须使用.而不是 ->(不是指针去用啊)}void DisplayObject(const CPoint * p) {    cout << p -> toString() << endl; // 必须用 -> 取用成员, 使用p.toString()就出错, 说是指针取用只能用 ->}int main() {    CPoint o1(5, 7);    CRect o2(2, 4, 5, 7);    CCuboid o3(1, 3, 5, 7, 9);    DisplayObject(o1);    DisplayObject(o2);    DisplayObject(o3);    // 调用void DisplayObject(const CPoint * p)    cout << endl;    DisplayObject(&o1);    DisplayObject(&o2);    DisplayObject(&o3);    return 0;}
运行结果如下:



如果类C1, C2, C3,,,,,,均继承了C0类, 而且C0有一个virtual function f()(存取修饰字为public or protected), 那么:

(1) 虚函数f()可以在C1, C2, ..类下面redefined。 注意函数的prototype是完全相同的,一模一样。  这不同于重载, 这就是function overriding(函数覆盖), 而重载的时候, 只是函数名相同, 参数类别不同, 或者参数个数不同, 或者回传型态不同等等, 即函数的prototype 是不同的。

#include <iostream>using namespace std;class A {public:    virtual void showFun() { // 不是virtual的函数        cout << "A::showFun()" << endl;    }};class C:public A {public:    virtual void showFun(int i) { // 不是虚函数, 因为prototype 不是和基类的一模一样        cout << "C::showFun()" << endl;    }};int main() {    C c;    A *pa = &c; // A 类的指向指向C类的对象    A &ra = c; // A 类的别名代表C类的对象    A a= c; // C类的对象转型成A类    a.showFun();    pa -> showFun();    ra.showFun();    return 0;}

运行结果如下:


再比如下面:

#include <iostream>using namespace std;class B {public:    virtual void showFun(char c) { // 是virtual的函数        cout << "B::showFun()" << endl;    }};class D:public B {public:    virtual void showFun(int i) { // 不是虚函数, 因为prototype 不是和基类的一模一样        cout << "D::showFun()" << endl;    }};int main() {    D d;    B *pd = &d; // B 类的指向指向D类的对象    B &rd = d; // B 类的别名代表D类的对象    B b= d; // D类的对象转型成B类    b.showFun(0);    pd -> showFun(0);    rd.showFun(0);    return 0;}

运行结果:


(2)可以通过基类类型的指针调用虚函数f()的时候, 基类指针会根据自己指向的实际对象的型别进行选择调用那个f()的实现。 这种决定调用哪一个f()的实现发生在run-time。

(3)如果基类中的函数f()是虚函数, 那么所有继承这个基类的派生类里面的所有的f()都默认是virtual的, 无论在函数前面加不加这个virtual(不过建议均加上).

#include <iostream>using namespace std;class B0 {public:    void showFun() { // 不是virtual的函数        cout << "B0::showFun()" << endl;    }};class C0:public B0 {public:    virtual void showFun() {        cout << "C0::showFun()" << endl;    }};class C1:public C0 {public:    virtual void showFun() { // 由于继承了C0, 故而默认为virtual的        cout << "C1::showFun()" << endl;    }};class C2:public C1 {public:    virtual void showFun() { // 由于继承了C1, 故而默认为virtual的        cout << "C2::showFun()" << endl;    }};void FunPtr(C0 *ptr) {    ptr -> showFun();}int main() {    B0 w, *p;    C0 x, *q;    C1 y;    C2 z;    p = &w; p -> showFun();    p = &y;  p -> showFun();    q = &x; FunPtr(q);    q = &y; FunPtr(q);    q = &z; FunPtr(q);    return 0;}
运行结果如下:


可见, p 的型别是B0的指针, 但是在B0 中showFun()不是virtual的。 q是C0型的指针, showFun()从C0开始包括以下均为virtual函数。 所以根据指向的对象overriding showFun()。

如果我们要在派生类中C1, C2..里重新定义f(), 那么所有重新定义得f()均有相同的protype。 这些f()前面无论加不加virtual关键字, 都是virtual的。 建议加上virtual.

(4)virtual function必须是类的成员函数, 不能是global的, 不能是static的, 也不能是friend

最后举一个虚函数的例子:

#include <iostream>using namespace std;class B {public:    void f() {cout << " Bf ";} // 普通函数    virtual void g() {cout << " Bg ";} // 虚函数, 入虚函数表    void h() {g(); f();}    virtual void m() {g(); f();}};class D:public B {public:    void f() {cout << " Df ";}    void g() {cout << " Dg ";} // 继承的, 默认为虚函数, 不管有没有virtual 修饰    void h() {f(); g();}};int main() {    D d;    B *pB = &d;     pB -> f(); // 普通函数, 看不到D类中新定义的成员, 只能看到该指针 类型的成员    pB -> g();    pB -> h();    pB -> m();    return 0;}
运行结果如下:


(5) destrucors(解构子)可以是虚函数, 但是建构子不能是虚函数,

使用虚函数的主要的缺点就是 more storage overhead + runing slower, 建议use virtual function only if necessary。


关于为什么解构子可以是虚函数的问题??

如果使用一个base class型的指针去指向一个derived class的对象。 如果我们删除了这个指针, 那么base class 的析构函数只是作用于基类属性的那一部分析构掉。所以删除不完全, 导致内存泄露。

CBase *pB = new CDerived;

.......................

delete pB;

所以我们需要把基类的解构子什么为virtual的, 从而使得我们delete pB的时候, 会自动在运行的时候选择合适的destructor(派生类的)。

Good to always have base class destructors as virtual destructors。

另外, 注意destructor不能被继承。


pure virtual function(纯虚函数)

有的时候, 基类(base class)的某些members没有meaningful 的definition. 这样的基类只能被其他的类去继承, 这样的类称为抽象类。 这中没有定义的函数称为纯虚函数(被virtual修饰)。 既然纯虚函数没有定义。例如如下的CPoint类中定义:

virtual void area() = 0;

纯虚函数没有定义, 就要求每一个派生类(如CRect, CCuboid)都给出这个函数的有效的定义。  否则, 如果没有给出基类的纯虚函数的定义, 那么这个派生类也是抽象类。

有一个或者多个纯虚函数的类被称为抽象基类。抽象类智能被其他类继承, 不能用抽象基类声明任何的对象(或者物件), 只能被继承。 


虚函数的工作机制: 符合information hiding的原则。 编译器在看到虚函数的时候, 会为这个虚函数创建一个virtual function table。 这个表存有指向每个虚成员函数的correct code 的正确地址。此类具有虚函数的类  声明出来的对象都有一个指针, 指向虚函数表。

我们可以从派生类对象转型为基类对象, 就会发生slicing problem。 也就是派生类的某些属性被切掉了。  反过来, 很危险,不要做。


0 0
原创粉丝点击