C++的多态及实现

C++的多态及实现

 

1.多态的概念及分类

面向对象思想的三大特征是封装,继承和多态,这里主要说说多态。

 

不同对象调用相同名称的成员函数时,可能引起不同的行为(即执行不同的代码)。这种现象称为多态性。将函数调用连接相应函数体的代码的过程称为函数联编(简称联编)。C++中根据联编时刻不同,分为静态联编和动态联编。

 

静态联遍:不同的类可以有相同名称的成员函数,编译器在编译时对他们进行函数联编,这种在编译时刻进行的联编称为静态联编。静态联编所支持的多态性就是编译时多态性(也称编译期多态性、静态多态性)。函数重载就属于编译时多态性。

 

动态联编:在动态联编中,在程序运行时才能确定调用哪个函数。这种在运行时的函数联编称为动态联编。动态联编所支持的多态性就是运行时多态性(也称运行期多态性、动态多态性)C++中,只有虚函数才可能是动态联编的。可以通过定义类的虚函数和创建派生类,然后在派生类中重新实现虚函数,实现具有运行时的多态性。

  

多态也有简言之就是函数的多态性和类的多态性。函数的多态性是指一个函数被定义成多个不同参数的函数,它们一般被存在头文件中,当你调用这个函数,针对不同的参数,就会调用不同的同名函数。函数的多态性是函数的重载。类的多态性,是指用虚函数和延迟绑定来实现的。

 

扫描二维码关注公众号,回复: 2208075 查看本文章

2.多态的实现

 静态多态性的实现,它主要是通过函数的重载和运算符重载,函数模板。在string类自己实现的那一篇中说了关于重载,这里就不多说如何实现重载(string类链接)。

<span style="font-size:14px;">#include <iostream>
#include <string>
// 定义两个重载函数
int my_add(int a, int b)
{
    return a + b;
}

double my_add(double a, double b,double c)
{
    return a +b+c ;
}

int main()
{
    int i = my_add(1,2);               // 两个整数相加
    int d = my_add(1.1,1.2,1.3);             // 三个double型加
    std::cout << "i = " << i <<"\n";
    std::cout << "d= " << d <<"\n";
}</span>

 

而动态多态性是通过重写虚函数实现的。所以先提一下重载和重写的区别:

   重载是编写一个已有函数同名但参数列表不同的方法,特征如下:

(1)     方法名必须相同

(2)     参数列表必须不同,与参数列表的顺序无关

(3)     返回值类型可以不同

重写(也称覆写)是派生类重写基类的虚函数,特征如下:

(1)     只有虚函数和抽象方法才能被重写

(2)     只有相同函数名

(3)     具有相同的参数列表

(4)     具有相同的返回值类型

重载是由编译器在编译阶段完成;而重写是由运行阶段决定的。

下面主要说虚函数实现的动态多态性。

 

3. 虚函数

用virtual关键字申明的函数叫做虚函数,虚函数是为了实现多态而存在的,必须 有函数体,而且虚函数肯定是类的成员函数。当一个成员函数被定义为虚函数后,其派生类的同名函数自动成为虚函数(虚函数继承后默认还是虚函数)

注意:不能声明为虚函数的:普通函数(非成员函数),静态成员函数,内联成员函数,友元函数,构造函数

虚函数的动态绑定仅在基类指针或引用绑定派生类对象时发生, 因为引用或者指针既可以指向基类对象,也可以指向派生类对象的基类部分,用引用或者指针调用的虚函数。用基类指针指向派生类的对象,若指针调用的函数在派生类中存在,且在基类中声明为虚函数,则调用的函数是派生类中的函数。(析构函数总是要声明为虚函数,这样析构时,先调用派生类的析构函数,再调用基类的析构函数,防止内存造成泄露)。

 

简单说,虚函数是通过虚函数表实现的,存在虚函数的类都有一个一维的虚函数表叫做虚表,表中每一项都指向一个虚函数的地址,实际上就是一个函数指针的一维数组。类的对象有一个指向虚函数表的虚表指针。虚表是和类对应的,虚表指针是和对象对应的。

虚函数表既有继承性又有多态性。每个派生类的虚函数表继承了它所有基类的虚函数表,如果基类虚函数表中包含某一项,则派生类的虚函数表中也包含同一项,但两项的值可能不同。如果派生类覆盖(重写)了该项对应的虚函数,则派生类虚函数表的该项指向重写后的虚函数,如果没覆盖,则沿用基类的值。派生类的虚表中虚函数地址的排列顺序和基类的虚表中虚函数地址排列顺序相同。如果派生类有自己的虚函数,那么虚表中就会添加该项

 简言之:虚表可以继承基类定义的虚函数,如果子类没有重写该虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的是基类的虚函数实现;在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数,如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。

 

每个对象调用的虚函数都是通过虚表指针来索引的,而在构造函数中进行虚表的创建和虚表指针的初始化。构造函数的调用顺序:在构造子类对象时,要先调用父类的构造函数,此时编译器只“看到了”父类,并不知道后面是否后还有继承者,它初始化父类对象的虚表指针,该虚表指针指向父类的虚表。当执行子类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表。

 

下面例子比较典型:

<span style="font-size:14px;">#include <iostream>
using namespace std;
class A
{
public:
    virtual void f(){ std::cout<<"A.f()is used"<<endl;}
};
class B:public A
{
public:
    void f(){ std::cout<<"B.f()is used"<<endl;}

};

int main()
{
    B b;
    A *a;
    a=&b;
    a->f(); //a看到的是B的f(),父类指针指向子类对象,实现了动态绑定,调用子类的f()
   a->A::f();  //a看到的是A的f(), 父类调用父类

B b1;
    b1->f() // 调用 B::fun,子类调用子类
    b1->A::f(); // 指示调用 A::fun, 派生类调用父类的虚函数(这里是通过静态绑定实现)</span>
}

 

静态绑定即在编译时进行调用的绑定,而动态就是运行时绑定。

 

4. 纯虚函数和抽象类

纯虚函数就是基类只定义了函数体,没有实现过程。如果一个基类含有一个或多个纯虚函数,那它就属于抽象类,不能被实例化。所以抽象类一定有纯虚函数

引入纯虚函数和抽象类的就是为了更方便使用多态特性,而且抽象基类定义的纯虚函数相当于接口,能把派生类的共同行为提取出来。

虚函数和纯虚函数的区别:

(1)    虚函数可以被直接使用,也可以被子类重载以后以多态的形式调用,而纯虚函数必须在子类中实现该函数才可以使用,因为纯虚函数在基类只有声明而没有定义,它本身就是一个接口。

(2)    虚函数在子类里可以不重载,但纯虚函数必须在子类里实现

(3)     虚函数和纯虚函数通常存在于抽象基类之中,被继承的子类重载,目的是提供一个统一的接口。

(4)    虚函数的定义形式:virtual {method body}

     纯虚函数的定义形式:virtual { } = 0;

注意:在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时候要求前期绑定,然而虚函数却是动态绑定,而且被两者修饰的函数生命周期也不一样。虚函数必须实现,如果不实现,编译器将报错。如果一个类中含有纯虚函数,那么任何试图对该类进行实例化的语句都将导致错误的产生,因为抽象基类是不能被直接调用的。必须被子类继承重载以后,根据要求调用其子类的方法。

下面例子:

<span style="font-size:14px;">#include<iostream>
#include<memory.h>
#include<assert.h>
using namespace std;
class animal{
public:
virtual voidsleep()=0;
virtual voideat()=0;
};

class Tiger:public animal
{
 public:
     void sleep() {cout<<”tiger sleep”<<endl;}
     void eat(){cout<<”tiger eat”<<endl;} 
};

class Lion:public animal
{
 public:
     void sleep() {cout<<”lion sleep”<<endl;}
     void eat(){cout<<”lion eat”<<endl;} 
};

int main()
{
 animal*P;
 Tigertiger;
 Lionlion;
 p=&tiger;
 p->sleep(); //这里只能通过指针指向子类对象。Animal抽象类不可以实例化
 p->eat();
 p=&lion;
 p->sleep();
 p->eat();
 return 0;
}</span>

猜你喜欢

转载自blog.csdn.net/zcyzsy/article/details/52463355