程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 對於C/C++等命令式語言(相對於函數式語言)

對於C/C++等命令式語言(相對於函數式語言)

編輯:C++入門知識

為C語言添加OO能力的嘗試從上世紀70年代到現在一直沒有停止過,除了大獲成的C++/Objective-C以外,還有很多其它的成功案例,比如GTK在libg中實現了一個對象系統,還有前幾年一個OOC,以及很多用宏實現的所謂輕量級OO系統。上周在網上發現了又一個自稱為OOC系統,我決定總結一下這方面的內容。   大部分面向對象系統可以分成兩類,一類是基於原型的設計,類似javascript;另一類是基於類模板的設計,比如C++/Java。當然,這不是絕對化,近幾年,在很多動態語言實現中,有很多混搭的實現,例如Dart。因為有C++的例子,基於類模板的對象系統可能對C語言程序員更自然一些,我們以此為例。這個系統要改成一個基於原型的系統也非常簡單。   對象系統中最核心的概念當然是對象。對象在C語言中沒有直接的對應成份(沒有內置於語言),我們可以選擇這麼幾種來表示對象,一是無類型的指針,二是結構體指針,三是表示為int的ID。從本質上來看,這些並沒有區別,無非是語法上簡潔和復雜。我們選擇一個結構指針類型struct object*來表示對象.   對象之間的消息傳遞,對於C/C++等命令式語言(相對於函數式語言)來說,都對應於一個函數調用。像C++語言一樣,我們可以定義一個虛表;也可以像Objective-C一樣定義一個消息轉發鏈。從實際效果上來說,都有以下過程:   struct object* a;   member_function* pf = find_function(a, the-function-id);   pf(a, other-param);   以上偽代碼合並成一行調用,我們定義一個向對象a發送為function_id的消息,使用如下語法:   interface(a)->function_id(a, other-param);   這裡引入了一個概念interface(接口),接口是一組消息的集合,對象可以接受的消息由它實現的接口定義,發送消息即變成取得相應接口,並調用接口上的函數。這個設計綜合了虛表和消息轉發鏈的設計,比較接近於COM中的接口概念。接口以類似虛表的形式定義:   struct XXX_interface {     struct object* kclass;     int (*XXX_function)(struct object* this_object, other-param);     …   };   接口實際上暗示了我們的實現是對象->接口表->接口->類(運行時信息)。借用下圖,左側藍色為對象,這兩個對象是屬於同一個類,它有一個成員_vtab指向右側黃色的一個虛表或者說是接口表,接口表有一個成員_class指向相應的類數據。接口表和類數據注冊到類型系統中,而對象由用戶分配內存。   image   有了接口概念我們可以實現接口繼承,但實現繼承需要另一個機制,我們不打算像C++選擇多重繼承,而是直接選擇更為直接的Mixup(混入)方式。我們可以通過在類型構造時直接調用mixup,傳入類型對象和mixup結構。   這裡我們會遇到為C語言添加OO支持最大的困難,我們沒有辦法在編譯期添加特性,比如構造類/生成指針表/混入實現,我們只能選擇在運行時添加一個class_init,類型初始化函數。這個問題帶來了兩個不足之處,一是有很多記簿的工作需要程序員完成;二是無法實現靜態對象。每個類型需要這樣一個初始化過程:   struct klass XXXClass = {   };   void XXX_init() {      declare(&XXXClasss, &baseClass);       XXXClasss.init = XXX_init;     …      struct XXX_interface i* = implement(&XXXClasss, &XXX_interface)      i->function_id = some_implement_function;      …      mixup(&XXXClass, &XXX_mixup);      …      register(&XXXClass);     }   這裡用到一個struct klass結構,它的作用是記錄對象的相關信息,主要內容如下:   struct klass {   int id;   char* name;   size_t object_size;   struct klass* parent;   size_t itable_size;   struct itable* itables;   void                (* init) ( Class this );                        /* class initializer */  void                (* ctor) (Object self, const void * params );    /* constructor */  void                (* dtor) (Object self, Vtable vtab);            /* destructor */  int                   (* copy) (Object self, const Object from);         /* copy constructor */  };   當Declare這個對象時,系統開始記錄它的id/name並計算object_size。後面一系列代碼用於初始化基本的函數指針和接口表指針。最後Register這個類到系統中,用於動態類型查找。每個類這些記簿式的代碼非常類似。   前面用到的interface(a)這個函數就是通過遍歷itables來找到對應的接口虛表,接口表有反向指針指回類說明,因此可以通過一個接口來查詢其它接口。   在main函數的開始部分,需要對整個對象系統手動初始化,這可以說是一段非常不人道的代碼:   int main() {     object_system_init();     XXX_init();     XXX2_init();   …   }   雖然我們可以聲明一個數組來完成對各個init函數的自動調用,但這個聲明過程依然非常不人道。要得到對程序員比較友好的過程,我們需要通過一個額外的源代碼分析過程,自動生成上面class_init函數,以及system_init過程。   分配一個對象,事實上只需要三步,一是找到對應的類型,二是分配空間,三是設置虛表指針。第一步,可以直接使用全局的靜態struct kclass對象,也可通過查找函數find_class("class name")來完成。第二步這步分配空間,可以由用戶完成,只需要下一步調用object_new_at(user_space, class)。如果使用系統分配空間即可由object_new一次完成二、三兩步。   //用戶分配   struct klass XXXClasss;   void* ptr = malloc(XXXClass.object_size);   object_new_at(ptr, &XXXClass);   //系統分配   struct object* ptr = object_new(&XXXClass);   在對象初始化過程中,object_new會調用構造函數,也就是kclass中的init函數,相應的destructor/copy等函數也會在對應的object_destroy/object_copy過程中調用。   以上基本構造了一個簡單的對象系統核心,我們如果再補充一些錯誤處理、內存管理以及多線程處理,一個小型而完整的對象系統就構造出來了,但它最大問題還是語法復雜度比較高。雖然我們可以使用宏來優化語法,但效果不如人意,同時還帶來了理解上的困難。   在一些簡單的應用中,並不需要這樣一個復雜而完整的對象系統,我們更簡單的抽象甚至更好一點。一個對象可以表示如下,vtable可以指向一個函數如f(void* data);   struct object {       void* vtable;       void* data;   };   構造和析構函數都專用函數XXX_new和XXX_destroy即可。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved