多态初步

来源:互联网 发布:黑莓解网络锁 编辑:程序博客网 时间:2024/05/01 20:29

多态初步

1.   什么是多态

多态就是同一个处理手段可以用来处理多种不同的情况。

同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。

2.   多态作用

C++中,就是相同代码,实现不同功能,从而简化编程。

在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。

1)应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。这一招叫以不变应万变,可以大大提高程序的可复用性。

 

2)派生类的功能可以被基类指针引用,这叫向后兼容,可以提高程序的可扩充性和可维护性。将来写的程序可以被以前写的程序调用。

3.   多态分类

多态分为三种:

第一种是函数重载;(静态多态)

第二种是模板函数;(静态多态)

第三种是虚函数。(动态多态)

静态绑定是指在编译时确定函数调用,而动态绑定是指在运行时确定调用函数。

4.   虚函数

动态多态是依靠虚函数实现的,在C++中用关键字virtual实现虚函数。

4.1    对象模型

 对象模型

单一继承情况下,对拥有虚函数的类来说,都会为该类的对象添加一个虚函数表指针(vptr),该指针指向该类唯一的虚函数表(vtable)。

 

例:

Base *pb = new Derived;

pb->print(); // print()为虚函数

会被转换为

(*pb->vptr[ix])(); // ix为虚函数print在虚函数表中的偏移量

(*pb->vptr[ix])(pb); // 也有可能是这样,还会传递对象的地址来充当this指针

以上调用中,如果print有参数,则可能会是这样的(*pb->vptr[ix])(pb, args_list);

派生类会继承基类的虚函数,如果自己覆盖了基类的虚函数,则在自己的虚函数表中指向自己定义的虚函数体,否则指向基类定义的虚函数体。假如基类有两个虚函数,TestNotOverride,则如下图

派生类覆盖基类虚函数

4.2    使用虚函数,要遵循以下重要规则:

.如果虚函数在基类与派生类中出现,仅仅是名字相同,而形式参数不同,或者是返回类型不同,那么即使加上了virtual关键字,也是不会进行动态编联的。

 

.只有类的成员函数才能说明为虚函数,因为虚函数仅适合用与有继承关系的类对象,所以普通函数不能说明为虚函数。

 

.静态成员函数不能是虚函数,因为静态成员函数的特点是不受限制于某个对象。

 

.内联(inline)函数不能是虚函数,因为内联函数不能在运行中动态确定位置。

 

.构造函数不能是虚函数,因为构造的时候,对象还是一片未定型的空间,只有构造完成后,对象才是具体类的实例。

 

.析构函数可以是虚函数,而且通常声名为虚函数。

4.3    虚析构函数

通过基类指针删除派生类对象,只会调用基类的析构函数,而不会调用派生类析构函数。如果将基类的析构函数声明为虚的,则会先调用派生类析构函数,然后调用基类析构函数,从而完全析构对象。

例:

class Base

{

public:

       Base() {}

       //virtual

       ~Base() { cout<<"~Base/n"; }      

};

 

class Derived : public Base

{

       int *pa;

public:

       Derived() { pa = new int[100]; }

       ~Derived() {

              delete []pa;

              cout<<"~Derived/n";

       }    

};

 

int main()

{    

       Base *pb = NULL; 

       pb = new Derived();

       delete pb;

}

4.4    纯虚函数

有纯虚函数的类被称为抽象类,不能被实例化。纯虚函数定义形式如下

virtual void vfun() = 0;

4.5    基类的private虚函数

基类可以将虚函数定义为private,并且在派生类中定义,再在基类中调用在派生类中实现的private方法。

例:

class Base

{

private:

    /*

    这种写法的语意是:告诉派生类,你最好覆盖基类的Test()函数,

    但是你不要管它如何使用,也不要自己调用这个函数。

    */

    virtual void Test()

    {

           cout << "call Base Test()" << endl;

    }    

public:

    void CallTest()

    {

           Test();

    }

};

 

class Derived : public Base

{

private:

    virtual void Test()

    {

           cout << "call Derived Test()" << endl;

    }

};

 

int main()

{ 

    Base b;

    Derived d;

    Base *pb = NULL;

 

    pb = &b;

    pb->CallTest();

   

    pb = &d;

    pb->CallTest();

}

4.6    构造函数和析构函数中的虚函数调用

一个类的虚函数在它自己的构造函数和析构函数中被调用时,它们就变成了普通函数,不起作用了。也就是说不能在构造函数和析构函数中让自己“多态”。

例:

class A

{

public:

       A() { // 在这里,无论如何都是A::foo()被调用!

              cout<<"A() call ";

              foo();

       }       

    ~A() { // 在这里,无论如何都是A::foo()被调用!

              cout<<"~A() call ";

              foo();

       }      

    virtual void foo() { cout<<"A::foo()/n"; }

};

 

class B: public A

{

public:

    virtual void foo() { cout<<"B::foo()/n"; }

};

 

int main()

{    

    A * a = new B;

    delete a;

}

5.   容易混淆的概念

(OverLoad)成员函数被重载的特征:

1)相同的范围(在同一个类中);

2)函数名字相同;

3)参数不同;

4virtual关键字可有可无。

(OverRide)覆盖是指派生类函数覆盖基类函数,特征是:

1)不同的范围(分别位于派生类与基类);

2)函数名字相同;

3)参数相同;

4)基类函数必须有virtual关键字。

5)返回值也必须相同

(OverWrite)重写或隐藏(Hide)

这里“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:

1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。

2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

例:(这个例子来自高质量C/C++编程)

class Base

{

public:

  virtual void f(float x) {

      cout << "Base::f(float) " << x << endl;

  }

  void g(float x) {

      cout << "Base::g(float) " << x << endl;

  }

  void h(float x) {

      cout << "Base::h(float) " << x << endl;

  }

};

 

class Derived : public Base

{

public:

  virtual void f(float x) {

      cout << "Derived::f(float) " << x << endl;

      //return 0;

  }

 

  void g(int x) {

      cout << "Derived::g(int) " << x << endl;

  }

  void h(float x) {

      cout << "Derived::h(float) " << x << endl;

  }

};

 

void main(void)

{

  // pb,pd指向同一个派生类对象

  Derived  d;

  Base *pb = &d;

  Derived *pd = &d;

  // Good : behavior depends solely on type of the object

  pb->f(3.14f);   // Derived::f(float) 3.14

  pd->f(3.14f);   // Derived::f(float) 3.14

 

  // Bad : behavior depends on type of the pointer

  pb->g(3.14f);   // Base::g(float) 3.14

  pd->g(3.14f);   // Derived::g(int) 3        (surprise!)

 

  // Bad : behavior depends on type of the pointer

  pb->h(3.14f);   // Base::h(float) 3.14      (surprise!)

  pd->h(3.14f);   // Derived::h(float) 3.14

}

6.   模板函数(静态多态)

通过模板来实现多态。

实现的方式有:

Ø         编写不同的代码,可以是函数,也可以是类。

Ø         模板特化,编写某个函数模板的特例。

例:

// 为类CircleRectangle定义同名的成员函数名draw

class Circle

{

public:

    void draw() {

           cout<<"draw circle/n";

    }

};

 

class Rectangle

{

public:

    void draw() {

           cout<<"draw Rectangle/n";

    }

};

 

// 通过引用shape来画任何图形

template <typename Shape>

void drawShape(Shape& shape)

{

    shape.draw(); // 根据shape的具体类型调用对应的draw()

}

 

 

// 模板特化方式

template <class T>

T Max( T t1, T t2 )

{

    cout<<"geneal template/n";

    return (t1 > t2 ? t1 : t2);

}

 

// 特化

template<> int Max<int>(int t1, int t2)

{

    cout<<"my int template/n";

    return (t1 > t2 ? t1 : t2);

}

 

int main()

{

    // 自己写的具体类

    Circle c;

    Rectangle r;

    drawShape(c); // 调用Circle::draw()

    drawShape(r); // 调用Rectangle::draw()

 

    cout<<"/n";

    // 模板特化形成的多态      

    char ch1 = 'a';

    char ch2 = 'b';

    Max(ch1, ch2); // 调用通用模板

 

    int a = 1;

    int b = 2;

    Max(a, b); // 调用特化模板

}

7.   补充说明

本文中并没有讨论多重继承下的情况,这个时候的对象模型将会非常复杂,包含多个vptr。具体情况可以参考侯捷的《深度探索C++对象模型》一书。

 

 

原创粉丝点击