C++ 多态

C++ 多态

 

多态 多态性能够简单地概括为“一个接口,多种方法”,程序在执行时才决定调用的函数,

 

 

   C++多态性是通过虚函数来实现的,虚函数同意子类又一次定义成员函数,而子类又一次定义父类的做法称为覆盖(override),或者称为重写。

   重写的话分为有两种,直接重写成员函数和重写虚函数,仅仅有重写了虚函数的才干算作是体现了C++多态性,而重载则是同意有多个同名的函数,而这些函数的參数列表不同,同意參数个数不同,參数类型不同,或者两者都不同。编译器会依据这些函数参数的不同列表,将同名的函数的名称做修饰,从而生成一些不同名称的预处理函数,来实现同名函数调用时的重载问题。但这并没有体现多态性。
  多态与非多态的实质差别就是函数地址是早绑定还是晚绑定。假设函数的调用,在编译器编译期间就能够确定函数的调用地址,并生产代码,是静态的,就是说地址是早绑定的。而假设函数调用的地址不能在编译器期间确定,须要在执行时才确定,这就属于晚绑定。
  那么多态的作用是什么呢,封装能够使得代码模块化,继承能够扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。也就是说,不论传递过来的到底是那个类的对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。

  最常见的使用方法就是声明基类的指针,利用该指针指向随意一个子类对象,调用对应的虚函数,能够依据指向的子类的不同而实现不同的方法。假设没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用对应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。由于没有多态性,函数调用的地址将是一定的,而固定的地址将始终调用到同一个函数,这就无法实现一个接口,多种方法的目的了。

静态多态 

  静态多态是编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型的转换),可推断出要调用哪个函数,如果有对应的函数就调用该函数,否则出现编译错误

我们以前说过的函数重载就是一个简单的静态多态。

 1 int Add(int left, int right)
 2 {
 3     return left + right;
 4 }
 5 double Add(double left, int right)
 6 {
 7     return left + right;
 8 }
 9 
10 int main()
11 {
12     Add(10, 20);
13     //Add(10.0, 20.0);  //这是一个问题代码
14     Add(10.0,20);  //正常代码
15     return 0;
16 }

可以看出来,静态多态是编译器在编译期间完成的,编译器会根据实参类型来选择调用合适的函数,如果有合适的函数可以调用就调,没有的话就会发出警告或者报错。

动态多态

(1)动态多态的实现条件:

--基类中必须包含虚函数,并且派生类一定要对基类中的虚函数进行重写

 --通过基类对象的指针或引用调用虚函数

 

所谓重写是:

a.基类函数必须为虚函数

 b.派生类函数必须与基类中的虚函数保持一致(包括返回类型、函数名称、参数列表)

 

但是有例外:即协变:

第一种是:基类虚函数必须返回基类对象的指针/引用,派生类虚函数返回派生类对象的指针/引用   

 第二种是:虚拟的析构函数(基类和派生类的析构函数名字不相同)  

 第三种是:派生类的虚函数可以与基类中的虚函数访问限定符不一样

 

总结一道面试题:那些函数不能定义为虚函数? 
经检验下面的几个函数都不能定义为虚函数: 
1)友元函数,它不是类的成员函数 
2)全局函数 
3)静态成员函数,它没有this指针 
3)构造函数,拷贝构造函数,以及赋值运算符重载(可以但是一般不建议作为虚函数)

 

C++ 动态多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。

 1 #include <iostream> 
 2 using namespace std;
 3  
 4 class Shape {
 5    protected:
 6       int width, height;
 7    public:
 8       Shape( int a=0, int b=0)
 9       {
10          width = a;
11          height = b;
12       }
13       int area()
14       {
15          cout << "Parent class area :" <<endl;
16          return 0;
17       }
18 };
19 class Rectangle: public Shape{
20    public:
21       Rectangle( int a=0, int b=0):Shape(a, b) { }
22       int area ()
23       { 
24          cout << "Rectangle class area :" <<endl;
25          return (width * height); 
26       }
27 };
28 class Triangle: public Shape{
29    public:
30       Triangle( int a=0, int b=0):Shape(a, b) { }
31       int area ()
32       { 
33          cout << "Triangle class area :" <<endl;
34          return (width * height / 2); 
35       }
36 };
37 // 程序的主函数
38 int main( )
39 {
40    Shape *shape;
41    Rectangle rec(10,7);
42    Triangle  tri(10,5);
43  
44    // 存储矩形的地址
45    shape = &rec;
46    // 调用矩形的求面积函数 area
47    shape->area();
48  
49    // 存储三角形的地址
50    shape = &tri;
51    // 调用三角形的求面积函数 area
52    shape->area();
53    
54    return 0;
55 }

当上面的代码被编译和执行时,它会产生下列结果:

Parent class area
Parent class area

导致错误输出的原因是,调用函数 area() 被编译器设置为基类中的版本,这就是所谓的静态多态,或静态链接 - 函数调用在程序执行前就准备好了。有时候这也被称为早绑定,因为 area() 函数在程序编译期间就已经设置好了。

但现在,让我们对程序稍作修改,在 Shape 类中,area() 的声明前放置关键字 virtual,如下所示:

 1 class Shape {
 2    protected:
 3       int width, height;
 4    public:
 5       Shape( int a=0, int b=0)
 6       {
 7          width = a;
 8          height = b;
 9       }
10       virtual int area()
11       {
12          cout << "Parent class area :" <<endl;
13          return 0;
14       }
15 };

修改后,当编译和执行前面的实例代码时,它会产生以下结果:

Rectangle class area
Triangle class area

此时,编译器看的是指针的内容,而不是它的类型。因此,由于 tri 和 rec 类的对象的地址存储在 *shape 中,所以会调用各自的 area() 函数。

正如您所看到的,每个子类都有一个函数 area() 的独立实现。这就是多态的一般使用方式。有了多态,您可以有多个不同的类,都带有同一个名称但具有不同实现的函数,函数的参数甚至可以是相同的。

另一个例子:

 

 1 class TakeBus
 2 {
 3 public:
 4     void TakeBusToSubway()
 5     {
 6         cout << "go to Subway--->please take bus of 318" << endl;
 7     }
 8     void TakeBusToStation()
 9     {
10         cout << "go to Station--->pelase Take Bus of 306 or 915" << endl;
11     }
12 };
13 //知道了去哪要做什么车可不行,我们还得知道有没有这个车
14 class Bus
15 {
16 public:
17     virtual void TakeBusToSomewhere(TakeBus& tb) = 0;  //???为什么要等于0
18 };
19 
20 class Subway:public Bus
21 {
22 public:
23     virtual void TakeBusToSomewhere(TakeBus& tb)
24     {
25         tb.TakeBusToSubway();
26     }
27 };
28 class Station :public Bus
29 {
30 public:
31     virtual void TakeBusToSomewhere(TakeBus& tb)
32     {
33         tb.TakeBusToStation();
34     }
35 };
36 
37 int main()
38 {
39     TakeBus tb;
40     Bus* b = NULL;
41     //假设有十辆公交车,如果是奇数就是去地铁口的,反之就是去火车站的
42     for (int i = 1; i <= 10; ++i)
43     {
44         if ((rand() % i) & 1)
45             b = new Subway;
46         else
47             b = new Station;
48     }
49     b->TakeBusToSomewhere(tb);
50     delete b;
51     return 0;
52 }

 

这就是一个简单的动态多态的例子,它是在程序运行时根据条件去选择调用哪一个函数。 
而且,从上面的例子我们还发现了我在每一个函数前都加了virtual这个虚拟关键字,想想为什么?如果不加会不会构成多态呢? 
干想不如上机实践:

 

 

 1 class Base
 2 {
 3 public:
 4     virtual void Funtest1(int i)
 5     {
 6         cout << "Base::Funtest1()" << endl;
 7     }
 8     void Funtest2(int i)
 9     {
10         cout << "Base::Funtest2()" << endl;
11     }
12 };
13 class Drived :public Base
14 {
15     virtual void Funtest1(int i)
16     {
17         cout << "Drived::Fubtest1()" << endl;
18     }
19     virtual void Funtest2(int i)
20     {
21         cout << "Drived::Fubtest2()" << endl;
22     }
23     void Funtest2(int i)
24     {
25         cout << "Drived::Fubtest2()" << endl;
26     }
27 };
28 void TestVirtual(Base& b)
29 {
30     b.Funtest1(1);
31     b.Funtest2(2);
32 }
33 int main()
34 {
35     Base b;
36     Drived d;
37     TestVirtual(b);
38     TestVirtual(d);
39     return 0;
40 }

 

结果:

Base::Funtest1()
Base::Funtest2()
Drived::Funtest1()
Base::Funtest2()

在调用FuncTest2的时候我们看出来他并没有给我们调用派生类的函数

 

虚函数

虚函数 是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。

我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定

纯虚函数

您可能想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。

我们可以把基类中的虚函数 area() 改写如下:

 1 class Shape {
 2    protected:
 3       int width, height;
 4    public:
 5       Shape( int a=0, int b=0)
 6       {
 7          width = a;
 8          height = b;
 9       }
10       // pure virtual function
11       virtual int area() = 0;
12 };

= 0 告诉编译器,函数没有主体,上面的虚函数是纯虚函数

 

posted @ 2019-03-29 12:34  书院二层楼  阅读(90)  评论(0)    收藏  举报