泛型编程在非C++语言中的实现之探讨

原创 2001年09月22日 16:56:00

泛型编程在非C++语言中的实现之探讨
左轻侯
2001.9.22

  GP(Generic Programming,泛型编程)号称编程思想的又一次革命。但是,在论述GP的资料中,一般都是以C++语言为基础来讨论。那么,GP是否可以在其它的编程语言中实现呢?这是作者一直在思考的一个问题,因为水平有限和资料匮乏,收获甚微。现将一些不成熟的想法整理出来,请方家不吝指教。
  本文以Delphi为例(Java的情况与此类似,可参照),讨论GP的另一种实现思路。代码是随手写出的,未经验证。
  根据作者的理解,实现GP的关键之处,在于实现ADT(Abstract Data Type,抽象数据类型)。只有实现了ADT,才能够将具体的数据类型与通用的算法分离开来。
  在C++中,ADT的存储是通过模板来实现的。举一个最简单的栈的例子(没有给出实现部分):
  
template <class Type> class Stack{
public:
  void Push(const Type &item);
  Type Pop;
  ...
}

  栈的应用:
  
Stack <int> s;
int data;
data = 1;
s.Push(data);  file://入

int out;
out = s.Pop;  file://出

  通过建立一个int类型的Stack对象,实现了对int类型数据的存储。
  但是,在Delphi/Java中,并没有模板这种机制。那么如何实现ADT呢?与C++不同的是,Delphi/Java的类继承体系是单根结构的,也就是说,在Delphi中,所有的class都通过编译器强制而保证成为TObject的子类(Java中是Object)。这个TObject可以看成是一切类的最高层次的抽象。那么,是否可以认为Delphi/Java中已经先天地提供了对ADT的支持呢?
  试着用这种思路建立一个栈:
  
TStack = class
  public
    procedure Push(item:TObject);
    function Pop:TObject;
    ...
end;

  这个TStack类针对TObject类型的对象进行操作。但是,这个类还不能立即应用,因为在Delphi中,简单数据类型并不是对象。所以必须再建立一个自定义的数据类型。下面建立了只有一个integer成员的自定义数据类型:
  
TADT = class
  public
    data:integer;
end;
  
  再来看栈的应用:
  
var
  stack:TStack;
  adt1,adt2:TADT;
begin
  stack := TStack.create;
  adt1 := TADT.create;
  stack.Push(adt1);  file://入
 
  adt2 := stack.Pop as TADT;  file://出
 
  stack.free;
 
  这样就完成了对ADT对象的存储。必须注意到,在入栈时,是将adt对象直接入栈的,因为TStack类是对TObject进行操作,由于Delphi的单根结构,可以将任何类型的对象赋给TObject类型的变量。但是,出栈时,返回的也是一个TObject的变量,因此必须用as完成一次向下映射。同样由于单根结构,Delphi/Java提供了对RTTI的强大支持,因此这种向下映射是轻而易举的事情。但是,毫无疑问,RTTI在效率上有所损失。
  在实现了ADT的存储之后,如何才能实现对ADT的操作呢?
  在C++中,通过运算符重载来解决这个问题。看一个例子:  
  在一个List类中,执行查找操作:
  
template <class Type> class List{
  Type* find(Type &value);  file://查找指定数据项,找到则返回其地址,否则返回NULL
  ...
}

template <class Type> Type* List<Type>::find(Type &value){
  Type* p = first->link;
  where(p!=NULL&&!(p->data==value)){
    p=p->link;
  }
  return p;
}

  在List类的find函数的实现中,代码抄自链表结构的实现,不需要关心其细节。需要注意的地方只有一个,即判断条件中的p->data==value。由于p->data和value都是ADT,在建立一个List类的对象时,它们可能是简单数据,也可以是任何的自定义数据类型。但是,仍然可以统一使用==这样的运算符,对它们进行操作。为什么呢?原因在于运算符重载。
  下面用一个Point类来说明这一点:

class Point{
private:
  int x,y;
public:
  int operator ==(const Point &point);  file://判断两个Point对象是否相等
  ...
}

int Point::operator ==(const Point &point){
  if(x==point.x&&y==poing.y){
    return 1;
  }else{
    return 0;
  };
}

  可以看到,由于重载了==运算符,两个Point对象之间可以进行比较。同理,任何数据类型,只要重载了相应的运算符,就可以被容器类进行统一的操作。
  当目光重新转向非C++的时候,问题又出现了:Delphi/Java不支持运算符重载。做为补救措施,可以用函数来代替运算符。例如,统一使用equals函数来代替==。可是,更大的问题在于:容器类操作的对象是TObject,而TObject并没有equals这样的方法。(在Java中,object有equals方法,但并没有其它的完整的运算方法。)
  在不改变Delphi/Java现有语法的前提下,作者能想到的解决办法是,建立一个TADT类,作为所有数据结构的基类。TADT中定义了很多象equals、add之类的抽象方法。自定义的数据类型一律从TADT派生,并重载相应方法。而容器类则只需要对TADT进行操作,即可实现通用的算法了。但是,这种解决方法并不理想。
  (补充一点,其实Delphi自己提供了一些通用的容器类,如TList、TCollection、TStack、TQueue等。但与本文中所说的不同,它们存储的不是TObject,而是pointer。由于Delphi的“引用对象模型”机制,存储TObject对象,其实也就等于存储一个pointer,不同之处在于,pointer不但可以存储对象,而且可以存储基本数据类型。这应该也是Borland如此设计的原因。但是,这些容器类只提供了add、delete之类的管理方法,并没有提供通用的算法,因为对pointer不能进行复杂的操作。实际操作中,往往是程序员从容器类派出一个新类,或者在自己的类中维护一个容器类。这也是一种解决办法,但算法就不能独立出来了。)
  
  综上所述,作者的观点如下:
  1、C++中通过模板机制来实现ADT的存储,Delphi/Java同样可以通过单根结构+RTTI的机制来实现。其不同之处在于,C++的实现是语法上的,而Delphi/Java是逻辑上的,也就是说,C++是通过一套特殊的语法来实现的,而Delphi/Java是根据OOP的理论和本身的类库体系自然地实现的。作者个人以为,从这个角度来看,Delphi/Java的实现机制更加简单和直观。
  2、从运行效率来算,C++肯定要强于Delphi/Java,因为C++的实现是编译期的,而Delphi/java是运行期的。RTTI的使用将对效率造成不小的影响。但是,从另一个角度来考虑,ADT在运行期的实现也带来了好处,那就是可以在运行期随意地改变容器类所存储的数据结构类型。作者还没有考虑过这种“数据类型的多态”会对编程带来什么实质性的后果。不过大胆地设想一下,以后也许可以将标准算法封装在DLL之类已编译的模块中,甚至封装在OS提供的COM对象里,程序员只需要创建自己的数据类型,将其提交给相应的接口?……
  3、C++中通过运算符重载,来实现对ADT的操作。在不支持运算符重载的Delphi/Java中,作者未能发现好的代替方法。建立统一的ADT抽象类的解决方法,只能说是一个馊主意。:-(有程序员倾向于Delphi中应该增加运算符重载,这应该是理由之一,不知道Borland是否重视。另外,据说下一版的Java将会全面支持GP,不知道是通过什么机制来实现?请了解内幕的高手介绍一下。

泛型编程在非C++语言中的实现之探讨

   GP(Generic Programming,泛型编程)号称编程思想的又一次革命。但是,在论述GP的资料中,一般都是以C++语言为基础来讨论。那么,GP是否可以在其它的编程语言中实现呢?这是作者...
  • rosered2
  • rosered2
  • 2008年04月23日 16:38
  • 389

泛型编程在非C++语言中的实现之探讨

  • zgqtxwd
  • zgqtxwd
  • 2008年04月30日 23:55
  • 139

C语言中的泛型编程(void *)

C语言中的泛型编程(void *)简介:之前有写过关于C++的泛型编程,使用template来实现的,也包括了一些函数模板,类模板的一些概念,那么在纯C的实现中,能不能也有这样的泛型?答案当然是可以的...
  • qq_29924041
  • qq_29924041
  • 2017年07月22日 15:54
  • 527

《C++设计新思维——泛型编程与设计模式之应用》读后感

这是我第三遍学习这本书了,第一遍是大概是在06年,当时在公司的图书馆中无意中看到这本书,打算学习一下,结果,只看了前几章就看不下去了,实在太难了。07年我在福州路的清华书店又看到这本书,考虑再三,觉得...
  • swordmanwk
  • swordmanwk
  • 2011年01月18日 19:37
  • 4978

C++学习 - 泛型编程基础

C++作为兼容C语言却又更强大的语言来说,C++真正强大且区别于C的地方就是泛型编程了。在C++中,模板是泛型编程的基础,模板是创建类和函数的蓝图。模板定义假设要写一个函数比较两个值,指出第一个值是小...
  • chenfs1992
  • chenfs1992
  • 2015年12月31日 23:53
  • 672

C++泛型编程思想方法总结

C++用模板来实现泛型编程,模板分为函数模板和类模板。 基本概念:泛型编程范式GP:模板也叫参数类型多态化。 在编译时期确定,相比面向对象的虚函数多态,能够有更高的效率。 泛型编程是从一个抽象层面描述...
  • Blues1021
  • Blues1021
  • 2015年08月15日 15:39
  • 4802

《C++程序设计语言》5.9_11输入读一系列的单词,使用quit作为输入的结束单词

/*----------------------------------------------------- 从输入读一系列的单词,使用quit作为输入的结束单词。按照读入的顺序打印出这 些单词,但...
  • yss28
  • yss28
  • 2013年09月22日 00:06
  • 1495

C++语言中的引用的使用方法

1.什么是“引用”?申明和使用“引用”要注意哪些问题?引用就是某个目标变量的“别名”(alias),对应用的操作与对变量直接操作效果完全相同。申明一个引用的时候,切记要对其进行初始化。引用声明完毕后,...
  • liulongling
  • liulongling
  • 2016年04月20日 23:25
  • 1221

“懒人”的福音---泛型编程

懒得一步一步走楼梯,于是有了电梯;懒得走路,于是他们制造出了汽车、火车、飞机;懒得去计算,于是发现了计算器;懒得重复写代码,于是有了C++之中的泛型编程!        当然,上面那段话是我瞎掰的,真...
  • Loving_Forever_
  • Loving_Forever_
  • 2016年06月28日 22:37
  • 2607

C语言的按位 或,与,非,异或 运算符

注意与常用逻辑运算符的区别 位运算符 常用逻辑运算符 按位与:&   与:&& 按位或:| 或:|| 按位异或:^ 非:! 非运算:~ 按位与&:其功能是参与运算的两数各对应的二进位相与。只有对...
  • u010275850
  • u010275850
  • 2015年04月17日 15:01
  • 1955
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:泛型编程在非C++语言中的实现之探讨
举报原因:
原因补充:

(最多只允许输入30个字)