注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

数据挖掘

学习数据挖掘

 
 
 

日志

 
 

C + + 中多态性的实现方式  

2012-04-07 22:15:40|  分类: C++基本技巧 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

百度文库http://wenku.baidu.com/view/d9be1922aaea998fcc220e67.html,原作者:张 莉 (陕西师范大学计算机科学学院,陕西西安710062)


多态性是面向对象程序设计的一大支柱,它指的是在基类中定义的属性或服务被派生类继承之后,可以表现出不同的行为. 也就是指一段程序能够处理不同类型对象的能力. 在面向对象程序设计语言C + + 中,这种多态性的实现方式有4 种,分别是强制多态、重载多态、类型参数化多态和包含多态. 其中,类型参数化多态和包含多态称为一般多态性,用来系统地描述语义上相关的一组类型;强制多态和重载多态称为特殊多态性,用来描述语义上无关联的类型间 的关系.

下面详细介绍这4 种多态性的具体应用.

1  包含多态

在C + + 中公有继承关系是一种包含关系. 派生类直接公有继承基类定义中的属性或服务,如果1个程序段既能处理基类的对象也能处理基类的派生类的对象,该程序段称为多态程序段. C + + 采用虚函数实现包含多态. 一个函数一旦声明为虚函数,在编译阶段,编译器并不按照它的静态类型为它生成调用此函数的版本,而只为它生成虚函数表(表中存放与此函数同名、同参数、同 返回值的虚函数的地址) . 在程序运行时,再根据实际对象的类型,查虚函数表,找出相应版本的函数后,才能使用它. 因此,这种不是在编译阶段而是在运行阶段动态确定应使用哪一个虚函数的方式叫动态束定.

要把一个函数声明为虚函数,只要在原函数的声明之前加上virtul 关键字即可. 利用虚函数进行动态束定,必须用指向基类的指针或引用来访问它,这是因为C + + 是类型系统,在编译阶段,C + + 的变量名或函数名就与相应的存储单元联系起来,使用名字也就是使用对应的存储单元. 这有利于检查类型系统,并可产生高效代码. 但这种类型的限制缺乏灵活性,不能在运行时改变名字的含义,然而多态要求在不同的上下文中,同一名字有多种含义,C + + 引入虚函数的目的是告诉编译器在运行时才能确定要调用哪一个虚函数. 为了把变量名与相应的存储单元分开,它用指针来调用虚函数. 这样,只要改变指针所存地址的值,也就间接地改变了调用对象. 例:

# include < iostream. h >

class Point            / / 定义基类Point

 { private :

  float x ,y;

  public :

  Point( ) { } ;

  Point( float i ,float j)

   { x = i;

    y = j ; }  

   virtul float area( )     / / 声明为虚函数

   {return 0. 0 ; } } ;

const float Pi = 3. 141593;

class Circle : public Point      / / 定义派生类Circle

 {  private :

   float radius;

 public :

  Circle( float r)

    { radius = r ; }  

  virtul float area( )

    {return Pi 3 radius 3 radius; } } ;

void main( )

{  Point 3 pp;       / / 指向基类的指针pp

  Circle c( 5. 4321) ;

  pp = &c ;

  cout < < pp2 > area( ) < < endl ; }    / / 调用虚函数,执行派生类中定义的函数area( )

从上面例子可看出,利用虚函数可在基类和派生类中使用相同的函数名定义函数的不同实现,从而实现“一个接口,多种方式”. 一般说来,外部函数不能声明为虚函数,成员函数(除构造函数) 都可以声明为虚函数. 然而,在处理虚函数时,要动态确定应该选用哪一版本函数,故它比标准函数需要更多的存储空间. 因此,虚函数仅用于处理派生类中一系列同名、同参数和同返回值的函数.

2  类型参数化多态

类型参数化多态是指当1 个函数(或类) 对若干个类型参数操作时,这些类型具有某些公共的语义特性,可以用该函数(或类) 来描述这些公共语义特性. C + + 中的模板是实现类型参数化多态的工具,分为类模板和函数模板.

2. 1  类模板

一个类模板可以表示一组类. 下面是一个通用栈类,它可以存放整数、字符或类对象.

# include < stdlib. h >

template < class T> class stack    / / stack 类模板

{ T 3 v;

 T 3 p;

 int sz ;

public :

 stack( int s) { v = p = new T[ sz = s] ; }

 ~stack( ) { delete [ ] v;}

void push( T a) { 3 p + + = a ;}

T pop( ) { return 3 22p;}

int size( ) const {return p2v;} }

 void main( )

 { stack < char > sch( 20) ;     / / stack 类模板用char 实例化后创建对象

stack < int > si( 20) ;     / / stack 类模板用int 实例化后创建对象

sch. push( ’a’) ;

si. push( 10) ;

}

其中,stack 类模板中带有一个类型参数T ,表示栈中存放对象的类型,它不是实际类型,因此不能用类模板直接生成实例对象. 通过对类模板的实例化(即给类模板的参数指定具体类型的过程) ,类模板实例化后的结果是类而不是实例对象,因此可用类模板实例化后的结果类产生实例对象. 在main ( ) 函数中,从stack 类模板实例化了两个模板类: stack < char > 和stack < int > ,然后又由这两个模板类分别实例化各自对象:sch 和si.

2. 2  函数模板

与类模板相似,可以定义操纵一组类型的函数. 下面定义了一个求两对象间的最大值的函数模板.

template < class T > T max ( T a , T b) {return a > b ? a :b ;}

该函数模板可以求int , char , float 指针或任何重载了> 运算符的类对象间的最大值. 函数模板也要实例化,实例化后就生成了具体的函数代码(即特定于参数的类型) ,与类模板不同的是,它的实例化不需要用户显式进行,而是在函数调用时由编译器来处理. 例如:

int a , b;

char c , d;

int m1 = max( a , b) ;     / / 调用max( int a , int b) ;

int m2 = max( c , d) ;     / / 调用max( char c , char d) ;

事实上,函数模板表示了一组名字相同的函数,这些函数之间以及这些函数与其它同名函数之间是重载函数的关系. 在使用函数模板时,应保证函数的参数与模板函数的参数正好相配,因为编译器不会给模板函数的参数提供任何形式的转换.

总之,模板描述了一组类或一组函数,避免了为各种不同的数据类型进行重复的编码工作.

3  重载多态

重载是多态性中最简单的形式. 它是指用同一名字表示不同的函数或运算符,从而使C + + 具有更大的灵活性和扩展性. 它分为运算符重载和重载函数两种.

3. 1  重载函数

重载函数是指同一作用域内名字相同、但参数不同的函数. 例如:

# include < iostream. h >

int func( int x , int y) {return x < y ? y :x;}

float func( float x , float y) {return x < y ? y :x;}

double func( double x , double y) {return x < y ? y :x;}

void main( )

{ int n1 = 8 , n2 = 10 ;

 cout < <“the max is :”< < func( n1 ,n2) < < endl ;

 float m1 = 4. 3 , float m2 = 2. 6 ;

 cout < <“the max is :”< < func( m1 ,m2) < < endl ;

 double f1 = 2. 0 ,f2 = 4. 9 ;

 cout < <“the max is :”< < func( f1 ,f2) < < endl ;}

运行结果为:

 the max is :10

 the max is :4. 3

 the max is :4. 9

上述3 个函数,函数名相同,函数参数不同,因为编译器是根据参数来识别重载函数,所以必须保证重载函数的参数有所不同,即两重载函数必须具有以下两种差别之一才能分辨.

(1) 函数的参数个数不同;

(2) 一个或多个参数的类型不同.

3. 2  运算符重载

C + + 的基本类型(int , char , float 等以及它们的派生类型) 既能描述数据的存储格式,又能描述施加在数据上的操作,这种操作用运算符来指定. 在基本类型中运算符都按系统预定义好的方式来工作.

为了使用户定义的类型与基本类型一样,C + + 也允许用户定义类型使用运算符来表示操作. 实质上,运算符可以看成是一种函数,即运算符函数,只是对于基本类型,函数都是编译器给定的,不能加以改动. 但对于类对象,用户却可以重新定义运算符函数,以便设置运算符在类对象中新的含义. 因此,定义运算符在某类对象操作的做法即所谓的运算符重载.

运算符函数可以是类的成员函数,也可以是非成员函数,如果是非成员函数,一般将它声明为该类的友员. 例:

class complex         / / 复数类

{ / / . . .

public :

 Complex operator + ( const Complex & com)

 {Complex temp( rpart + com. rpart , ipart + com. ipart) ;

 return temp;}

/ / . . . } ;

运算符函数operator + 被定义为公有的,程序中的其它函数可以调用它,在定义了该函数之后,就可以像基本类型一样对复数对象用+ 表达式实施运算. 当程序中有语句

Complex a( 10 ,7) ,b( 3 ,5) ,c ;

c = a + b;

时,C + + 编译器把表达式a + b 解释为函数调用a. operator + (b) ,在调用时,operator + 成员函数首先创建一个临时Complex 类对象temp ,然后把出现在加法表达式中的两个复数之和暂存其内,最后将这个临时对象返回.

4  强制多态

强制也称类型转换,是指将一种类型的值转换成另一种类型的值而进行的语义操作,从而防止类型错误. 类型转换可以是隐式的,在编译阶段完成;也可以是显式的,在运行阶段完成.

4. 1  基本数据类型之间的类型转换

C + + 定义了基本数据类型之间的转换原则,即

char short int unsigned long unsigned long float double long double

低                                                                           高

当两个操作对象类型不一致时,在算术操作之前级别低的自动转换成级别高的类型.

上述规则不适用于赋值操作. 当赋值运算符右端的类型与左端的类型不同时,右端的值要转换成左端类型,然后将转换后的值赋值给左端.

类型转换可以使用下面3 种强制类型转换表达式,从而可以改变编译器所使用的规则,可以按程序员自己的意愿进行所需的类型转换.

(1) static-cast < T > ( E) ;

(2) T ( E) ;

(3) ( T) E ;

其中,E 表示一个运算表达式,T 表示一个类型表达式,第三种表达式是C 语言中所使用的风格,在C + + 中,建议不要使用,应选择使用第一种形式. 例如:设对象f 的类型为double ,且其值为5. 26 ,则表达式static-cast < int > (f) 的值为5 ,类型为int .

4. 2  用户定义类型的转换

着重介绍类类型与其它数据类型之间的转换.

(1) 在C + + 中,把其它数据类型转换成类对象是通过转换构造函数来完成的. 要求的前提是此类的转换构造函数是只带1 个非缺省参数的构造函数.

(2) 把类对象转换成其它数据类型是通过转换运算符函数来完成的. 它是一种类似显示类型转换的机制,它的设计需要注意两点:第一,转换运算符函数必须是类的成员函数;第二,转换运算符函数没有参数和返回值.

例:

class integer

{ int i;

public :

 integer( int a)        / / 转换构造函数,把int a 转换为类对象

  { i = a ;}

  operator int ( )        / / 转换运算符函数,把类对象转换为整型数

  {return i;}

上例可以在integer 类对象与整型数之间相互转换.

integer i1( 10) , i2( 20) ;

int a = i1 ;    / / 使用转换运算符函数,将类对象i1 转换为int 后,再进行赋值

i1 = a ; / / 使用转换构造函数,将int a 转换为integer 类对象后赋给i1 ;

i2 = 10 + i1 3 2 ; / / 由于没有重载3 运算符,所以首先把i1 通过转换运算符函数转换为int 后与2 进行整数乘法运算,然后与整数10 进行整数加法运算,最后使用转换构造函数把最终结果转换为integer类对象后赋给i2.

借助用户定义的类型转换,可以在多种不同类型对象之间进行混合运算,然而强制类型使类型检查复杂化,尤其在允许重载的情况下,可能会产生二义性. 因此,在程序设计中要注意避免由于强制带来的二义性.

从上面可看出,一般多态性是真正的多态性,特殊多态性只是表面的多态性. 因为重载只允许某一个符号有多种类型,而它所表示的值分别具有不同的类型. 类似地,隐式类型转换也不是真正的多态,因为在运算开始前,各值必须转换为所要求的类型,而输出类型也与输入类型无关. 相比之下,派生类与继承却是真正的多态,类型参数化也是一种纯正的多态,同一对象或函数在不同的类型上下文中统一使用而不需采用隐式类型转换、运行时检测 或其它限制.

  评论这张
 
阅读(342)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017