程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> 泛型編程在非C++語言中的實現之探討

泛型編程在非C++語言中的實現之探討

編輯:Delphi
  GP(Generic Programming,泛型編程)號稱編程思想的又一次革命。但是,在論述GP的資料中,一般都是以C++語言為基礎來討論。那麼,GP是否可以在其它的編程語言中實現呢?這是作者一直在思考的一個問題,因為水平有限和資料匮乏,收獲甚微。現將一些不成熟的想法整理出來,請方家不吝指教。
  本文以Delphi為例(Java的情況與此類似,可參照),討論GP的另一種實現思路。代碼是隨手寫出的,未經驗證。
  根據作者的理解,實現GP的關鍵之處,在於實現ADT(Abstract Data Type,抽象數據類型)。只有實現了ADT,才能夠將具體的數據類型與通用的算法分離開來。
  在C++中,ADT的存儲是通過模板來實現的。舉一個最簡單的棧的例子(沒有給出實現部分):
  
  template class Stack{
  public:
  void Push(const Type &item);
  Type Pop;
  ...
  }
  
  棧的應用:
  
  Stack s;
  int data;
  data = 1;
  s.Push(data); //入棧
  
  int out;
  out = s.Pop; //出棧
  
  通過建立一個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); //入棧
  
  adt2 := stack.Pop as TADT; //出棧
  
  stack.free;
  
  這樣就完成了對ADT對象的存儲。必須注意到,在入棧時,是將adt對象直接入棧的,因為TStack類是對TObject進行操作,由於Delphi的單根結構,可以將任何類型的對象賦給TObject類型的變量。但是,出棧時,返回的也是一個TObject的變量,因此必須用as完成一次向下映射。同樣由於單根結構,Delphi/Java提供了對RTTI的強大支持,因此這種向下映射是輕而易舉的事情。但是,毫無疑問,RTTI在效率上有所損失。
  在實現了ADT的存儲之後,如何才能實現對ADT的操作呢?
  在C++中,通過運算符重載來解決這個問題。看一個例子:  
  在一個List類中,執行查找操作:
  
  template class List{
  Type* find(Type &value); //查找指定數據項,找到則返回其地址,否則返回NULL
  ...
  }
  
  template Type* List::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); //判斷兩個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,不知道是通過什麼機制來實現?請了解內幕的高手介紹一下。
  
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved