C/C++——多态性和虚函数

面向对象程序设计有4个特点:抽象、封装、继承和多态性。其中多态性是面向对象程序设计的重要特征。本文主要讲解一下C++多态性的一些基本知识,以便于大家在程序设计中更好地利用多态性。

 

1.    多态性

多态性可以这样概括:不同对象接受到同样的消息后,产生不同的动作。封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。也就是说,不论传递过来的究竟是那个类的对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。多态性分为静态多态性和动态多态性。静态多态性又称编译时多态性,包括函数重载和运算符重载,采用迟绑定(late binding)的技术,要求在编译的时候就知道调用函数的信息决定要调用的函数,所以静态多态性效率高但灵活性差。动态多态性又称运行时多态性,主要通过虚函数实现,采用早绑定(late binding)的技术,是在程序运行过程中动态确定调用的函数,所以灵活性高但效率差。

先看下面的一个例子:

#include

using namespacestd;

 

class animal

{public:

virtual voidbreath()

// void breath()

     {  cout<<"animal"<

};

 

classfish:public animal

{public:

     void breath()       {     cout<<"fish"<

};

 

int main()

{   animal ani;             //定义一个animal对象ani

     fish fi;                      //定义一个fish对象fi

     ani.breath();          //调用animal的breath方法

     fi.breath();             //调用fish的breath方法

 

     animal* pt1=&ani;      //定义一个animal类型的指针,并用animal对象初始化

     pt1->breath();              //通过指针调用animal的breath方法

 

     fish* pt3=&fi;               //定义一个fish类型的指针,并用fi对象初始化

     pt3->breath();             //通过指针调用fish的breath方法

 

     animal* pt=&fi;           //定义一个animal类型的指针,并用fi对象初始化

     pt->breath();               //通过指针调用fish的breath方法

 

//   fish* pt2=&ani;           //定义一个fish类型的指针,并用animal对象初始化,想通过指针调用fish的breath方法,编译错误

//   pt2->breath();    

 

     return 0;

}

上面定义了两个类,类animal和类fish,类fish是从类animal继承得到的。两个类里都定义了方法breath()。在主函数里分别实例化了两个对象ani和fi,分别用不同方法调用相应breath()函数。

①通过点操作符“.”调用函数,即 ani.breath();     和    fi.breath();。    

②通过指针和指向操作符,即animal* pt1=&ani; pt1->breath();     和    fish*pt3=&fi;  pt3->breath();

上面这两种方法的意图都很明显,通过相应对象或相应对象的指针调用函数,指针类型和初始化指针的对象的类型相同,不会产生歧义。

③ animal* pt=&fi;      pt->breath();  定义一个animal类型的指针,用fish类的对象fi初始化指针,然后通过指针调用fish的breath方法。这种方式可以调用成功,是因为在类animal里定义breath()时,用了关键字virtual,表示这是一个虚函数,如果不加这个关键子,则结果就会调用animal的breath()方法,读者可以试一下,这相当于是函数的覆盖,即两个类里函数名、返回值和参数表都相同。当我们用fi对象初始化animal指针时,指针指向的是基类的地址,所以调用基类的breath()方法。而加了virtual关键字后,用fi对象初始化话animal指针,指针虽指向基类的地址,但调用的是fish类的breath()方法。当C++编译器在编译的时候,发现Animal类的breathe()函数是虚函数,这个时候C++就会采用迟绑定(late binding)的技术,在运行时,依据对象的类型(在程序中,我们传递的Fish类对象的地址)来确认调用的哪一个函数,这种能力就做C++的多态性。也就是说,用哪个派生类对象初始化基类指针,通过这个指针调用的函数就是哪个派生类的相应函数。

同样的道理解释析构函数。析构函数是对象撤销时的做一些清理工作,一般会先调用派生类的析构函数,再调用基类的析构函数。但是当我们用派生类对象的地址赋值给基类指针,在删除这个指针撤销对象时,只调用了基类的析构函数。这是因为,用派生类对象的地址赋值给基类指针,对象撤销时,通过指针只能调用基类的析构函数,要解决这个问题,可以把基类的析构函数声明为虚函数,这样通过这个指针就可以调用到派生类的析构函数,然后再由派生类的析构函数调用基类的析构函数。所以一般将基类的析构函数声明为虚函数,这样如果用派生类对象的地址赋值给基类指针,通过delete手动删除派生类对象,就可以调用到派生类的析构函数。

 

这里有几点需要注意:

(1)在基类的函数前加virtual,变为虚函数,声明的时候加virtual,实现的时候不加virtual,这类似友元函数。当传递子类对象的地址调用函数,如果子类中有函数就调用子类的,子类没有的就调用基类的。

(2) 当子类继承父类的虚函数,则继承下来就是虚函数,不管子类里函数前是否加virtual。

(3) 重载和派生是不同的概念。重载发生在同一个类里,函数名相同,参数表不同;派生发生在不同类里(基类和派生类),函数名和参数表都相同。

(4) 这是讲重载、覆盖和隐藏的一篇文章:

http://www.cppblog.com/lingyun1120/archive/2011/04/27/145135.html

 

2.    纯虚函数

被标明不具体实现的虚成员函数,可以让类先有一个名称,没有函数体,让派生类在定义的时候给出具体的定义,称这种函数为纯虚函数。纯虚函数声明形式:virtual 函数类型  函数名(参数表)=0; 后面的“=0”是专门用来声明纯虚函数的,没有什么实际意义。因为没有函数体,所以有纯虚函数的类是一个“不完整的类” ,不能实例化对象。纯虚函数作用是为派生类保留一个函数名,派生类根据需要自己定义这个函数,实现特定操作。

 

3.    抽象类

一个基类如果包含一个或一个以上的纯虚函数,就是抽象类,由于这样的类常作为基类,所以又称抽象基类。如果在基类声明了虚函数,在派生类中不管这个函数前面是否有关键字virtual,继承下来的这个函数都是虚函数,但一般为了清晰明了,会在虚函数前virtual。而在基类声明的纯虚函数,只有在派生类里在用“=0”再次声明为纯虚函数,在派生类中才是纯虚函数,负责就是虚函数了。因为纯虚函数实际上只有函数名而已,所以抽象类不能实例化对象。抽象基类也是这个类的公共接口,从同一个类派生出的类有同一接口,因此能相应同一形式的消息,即将派生类对象的地址赋值给基类指针,通过基类指针访问派生类成员函数。

 


参考文献:

http://blog.csdn.net/hackbuteer1/article/details/7475622

http://my.oschina.net/hnuweiwei/blog/280894



 

你可能感兴趣的:(C/C++,C-C++,多态性,面向对象,虚函数,抽象类)