C++ 学习笔记----多态篇

 

(一)基本概念

 

1、入口地址:每个函数在内存中拥有一段存储空间,在内存的起始地址就是这个函数的入口地址;

 

2、编辑期绑定和运行期绑定:

 

  编译期绑定:指的是程序运行哪一段代码,由编译器在编译阶段就确定了;

  运行期绑定:一个函数的绑定发生在运行时刻;

  我们也称运行期绑定的函数是多态的;

 

3、在C++中,只有满足一定的条件,才可能是多态的,这些条件是:

  (1)必须有一个继承体系结构;

  (2)继承体系结构中的一些类必须具有同名(相同函数签名)的virtual成员函数;

  (3)至少有一个基类类型的指针或基类类型的引用。这个指针或引用可对virtual成员函数进行调用;

  例如:

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 class TradesPerson {
 6 public :
 7     virtual void sayHi()
 8     {
 9         cout<<"Just Hi ."<<endl;
10     }
11 };
12 
13 class Tinker : public TradesPerson {
14 public :
15     virtual void sayHi()
16     {
17         cout<<"Hi, I tinker ."<<endl;
18     }
19 };
20 
21 class Tailer : public TradesPerson {
22 public :
23     virtual void sayHi()
24     {
25         cout<<"Hi, I tailer ."<<endl;
26     }
27 };
28 
29 int main(int argc, char argv[])
30 {
31     TradesPerson* p,*p1,*p2;
32 
33     p = new TradesPerson;
34     p->sayHi();            //Just Hi .
35     delete p;
36 
37     p1 = new Tinker;
38     p1->sayHi();           //Hi, I tinker .
39     delete p1;
40 
41     p2 = new Tailer;
42     p2->sayHi();
43     delete p2;             //Hi, I tailer .
44 
45         
46     system("PAUSE");
47     return 0;
48 }

  在此代码中,派生类的virtual关键词不是必须的,若有和基类同签名的函数,基类中声明是virtual的,则派生类会自动将其变成虚函数,但加上关键字,方便检查,因此建议在派生类中加上virtual;若在外部定义函数,不用加关键字virtual,只要在声明时加上virtual即可;同样,虚函数也可从基类中继承;

 

4、运行期绑定和虚成员函数表:

  在C++中使用vtable(虚成员函数表)来实现成员函数的运行期绑定;虚成员函数表,存在的用途是支持运行时查询,使得系统可以将某一函数名绑定到虚成员函数表的特定入口地址;使用虚成员函数表会影响效率,因为它需要额外的存储空间,对虚成员函数表进行查询也需要额外的时间;

 

5、构造函数与析构函数:

  (1)构造函数不能使虚成员函数;

  (2)析构函数可以使虚成员函数,在一定情况下,把析构函数设为虚函数是有必要的;

    假设有一个基类A和它的一个继承类Z,定义了一个:A* ptr ; ptr = new Z();  delete ptr; 运行发现只有A的析构函数被调用,因为它们的析构函数不是虚函数,所以编译器实施的是静态绑定,编译器根据ptr的数据类型A*来决定调用哪一个析构函数;因此,仅调用了A的析构函数。若把A和Z的析构函数都设为虚函数,因为它们是运行期绑定,所以A和Z的析构函数都会被调用,这样,就不会发生内存泄露了。

 

6、注意一点:静态成员函数不能设置为虚成员函数

 

(二)C++编译期多态机制:重载、覆盖和遮蔽

 

1、重载:具有相同的函数名,但函数签名不同,即“函数的参数的个数、类型和顺序不同”;成员函数和顶层函数都能构成重载;

 

2、覆盖:假定基类B有一个成员函数m,其派生类D也有一个具有相同函数签名的成员函数m,如果这个成员函数是虚函数,则任何通过指针或引用对m的调用都会激活运行期绑定;对于这种情况,我们说D::m覆盖了基类的成员函数B::m。如果成员函数不是虚函数,对m的任何调用都是编译期绑定

 

3、遮蔽:假设基类B拥有一个非虚函数m,其派生类D也有一个成员函数m,我们说D::m遮蔽了基类B::m;如果它们具有不同的函数签名,这个情况就比较复杂了。

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 class B {
 6 public :
 7     void m(int x)
 8     {
 9         cout<<x<<endl;
10     }
11 };
12 
13 class D : public B {
14 public :
15     void m()
16     {
17         cout<<"Hi"<<endl;
18     }
19 };
20 
21 
22 int main(int argc, char argv[])
23 {
24     D d1;
25     d1.m();  //OK
26     d1.m(20);  //Error, 因为D的m遮蔽了B的m。
27     d1.B::m(20);  //改成这样,OK;
28         
29     system("PAUSE");
30     return 0;
31 }

  虚函数和非虚函数都有可能产生名字遮蔽,实际上一旦派生类的虚函数不能覆盖基类的虚函数,就会产生遮蔽;

  像上述程序中,d1.B::m(20)虽然消除了编译错误,但不是好的编程风格,为了发挥多态性的作用,B::m和D::m应该具有相同的函数签名;

 

4、名字共享:

  以下几种情况下,需要共享函数名:

  (1)重载函数名的顶层函数;

  (2)重载构造函数;

  (3)重载成员函数;

  (4)继承层次下的同名函数;等

 

 

(三)抽象基类:类必须至少有一个纯虚成员函数

 

1、综述:不能实例化抽象基类的对象,用来指明某些必须被派生类覆盖的虚函数,否则不能实例化对象;从这个意思上说,抽象基类实际上定义了一个公共接口;

  只有虚函数才可以成为纯虚成员函数;

1 // =0 表示这个函数被定义为纯虚成员函数
2 class ABC {
3       public :
4             virtual void open() = 0; 
5             //...... 
6 };

 

(四)运行期类型识别(RTTI)

 

1、功能:

  (1)在运行期对类型转换进行检查;

  (2)在运行期确定对象的类型;

  (3)扩展C++提供的RTTI;

 

2、dynamic_cast操作符:省略

 

3、static_cast操作符:省略

 

4、typeid操作符

  功能:确定某个表达式的类型。头文件 #include <typeinfo>;

  例如:float x;  typeid(x) == typeid(float)   //返回true

 

5、扩展RTTI

  可通过从标准基类type_info派生出新类,对RTTI进行扩展。头文件:#include <typeinfo>

 

(五)强多态与弱多态

 

  强多态是指:对覆盖的虚函数进行运行期绑定处理;

  弱多态:顶层函数或成员函数的重载和类层次中非虚函数的函数名共享;

 

 

 

1 ////////////////////////////////////////////////////////////////////////////////
2 //C++学习笔记
3 ////////////////////////////////////////////////////////////////////////////////

 

posted @ 2012-11-19 16:39  china_victory  阅读(349)  评论(0编辑  收藏  举报