用 C 進行 OOP 編程? 在本文中,我們將研究 Glib 對象系統,也稱為“GObject”,直到最近它還是 GTK+ 的一部分。但是在研究 Glib 2.0 中的這個新對象系統之前,我們需要解決一個更為基本的問題 - “對象系統”到底是什麼以及它為何存在?畢竟,C 是一種非面向對象的語言。是有可能用 C 編寫面向對象的程序,還是必須使用 C++ 編寫面向對象的程序? 答案是有可能用 C 編寫面向對象的程序。但是,由於對象的概念不屬於 C 語言規范,因此需要用外部庫來提供這方面的支持。在本文中,我們使用術語“對象系統”來描述一個提供 OOP 編程所需基礎的庫,而 Glib 便是這種庫的一個示例。Glib 提供了類、繼承、引用計數、信號、接口和對象特性的 C 實現。通過使用 Glib,C 程序員就可以輕松地編寫面向對象的程序。 因此,有可能用 C 編寫面向對象的程序。但是,您可能會感到疑惑:為何 GTK+ 開發人員不直接使用 C++。這裡我們不討論每種可能的解釋,而只解釋為什麼擁有一個用於 C 的對象系統是有意義的。其一,比起 C++,有許多開發人員更喜歡用 C。另外,由於項目或平台的限制,可能不會選擇使用 C++ 編譯器。無論是什麼原因,擁有了用於 C 的對象系統,可以使更多的潛在開發人員也進行 OOP 編程(尤其是 GNOME 編程),我們對此表示感謝。
C++ 包裝器 可以這麼說,所有那些 C++ 的支持者也不必擔心 - 您也可以用 C++ 編寫 GNOME 程序。由於 C++ 是 C 語言的超集,因此您可以方便地將 C 樣式的 Glib/GTK+ 代碼和現有的 C++ 項目結合在一起。另外,您也可以使用 Glib/GTK+ C++ 包裝器。Glib/GTK+ C++ 包裝器將允許您使用本機的 C++ 類和對象與 Glib 對象交互。 研究 GObject 好了,現在您知道了對象系統的意義和用途。現在,我們將深入研究 Glib 對象系統並探究一些基本的 GObject 編程概念。直到最近,GObject 系統還是在 GTK+ 1.2.x 庫中實現的。現在,由於即將發行 GTK+ 2,該對象系統已向下移到了新 Glib 2 庫這一層。由於這樣的移動,Glib 對象系統現在就與 GUI 相關的問題完全無關了。對於我們這些喜歡在基於非 GTK+ 的程序(比如基於控制台的應用程序和工具)中使用類和對象的人而言,是個非常好的消息。 現在,讓我們把 Glib 對象系統(也稱為“GObject”)與 C++ 和 Java 語言的對象系統作一下比較。首先,讓我們進行語法比較。使用 C++,您可以通過對象指針調用方法,如下所示: object->function(arg_a, arg_b); 通過使用 C++ 對象引用,您可以輸入: object.function (arg_a, arg_b); Java 語言只有引用,沒有指針。Java 方法調用語法與 C++ 引用對象方法調用完全一樣: object.function (arg_a, arg_b); 與此相反,GObject 使用標准 C 函數調用語法。其對象指針作為函數的第一個參數被傳遞。還要注意的是,為了防止名稱空間沖突,函數名用類名作為前綴: classname_function (object, arg_a, arg_b); 您可能會感到疑惑:調用 GObject 方法與調用簡單的 C 函數到底有何區別?嗯,從 C 程序語言本身的角度而言,沒有什麼區別。就 C 編譯器而言,它只是調用一個函數,而該函數的第一個參數正好是指向標准 C 結構的指針。 因此您的 C 編譯器甚至不知道我們正在編寫面向對象的程序。但是,別讓這個事實愚弄了您,讓您誤以為 GObject OOP 編程只是將良好的舊式 C 編程進行了一番改頭換面。GObject 在幕後確實作了許多工作,允許您創建現有類的子類、創建類的接口(我們將在本文的後面對此進行討論)等等。不過這個 OOP 的所有功能都旨在與標准的 C 編程構造完全兼容。 關於 GObject 方法調用,這裡還要說明另一件重要的事。當(您調用方法時)將某個對象傳遞給類函數時,需要對該對象進行數據類型轉換以便與您正在調用的函數的預期類型相匹配。例如,如果您想將 GtkButton 對象傳遞給帶有 GtkWidget 參數的函數,您就要編寫: gtk_widget_show (GTK_WIDGET (button)); GObject 接口 除了對 GObject 進行的許多改進之外,Glib 2 還引入了稱作“接口(interface)”的 OOP 概念。為了理解接口是什麼,讓我們研究一個示例。 假設我們想要為應用程序創建一個 Pegasus 對象。由於飛馬(pegasus)是神話中有羽翼的馬(horse),OOP 編程的習慣做法就是創建一個新 Pegasus 類,它使用 Horse 類作為其父類。然後,我們將給新類添加必要的代碼以支持它擁有“羽翼”這一特性。在本例中,創建 Pegasus 作為 Horse 的子類很有意義,因為它表示了這兩個類之間的關系。 但是,假如我們擁有多種不相關的類,比如 Horse、Car 和 House,並且我們希望使它們都可能進行通信。這個新能力與它們彼此的關系無關 - 事實上,這三個類對於我們而言根本沒什麼關聯。但我們希望它們都支持與通信相關的新功能。我們該怎麼辦呢? 接口給這個問題提供了解決方案,它允許我們給全異類添加公共功能。因此回到上面的示例,我們只要為 Horse、Car 和 House 類編寫“Talk”接口。突然之間這三個不相關的類都“能夠通信”了,並且能夠使用我們所創建的與通信相關的新函數。並且這些與通信有關的新函數使用“Talk”接口本身與我們的對象“愉快”地進行交互。因此,由於接口,這三個不相關的類現在“講同一種語言了”。 在 Glib 中,您可以為一個類創建任意數量的接口。因此,如果我們為 House 類創建了一個 Talk 接口,我們可以如下定義 say() 函數: void say (Talk *mytalk, const char *myphrase); 然後,我們可以調用 say() 函數,將 myhouse 變量的數據類型轉換成“Talk”接口。 say (TALK(myhouse), "hello there!"); 可以在 GTK+ 2 的 GtkEditable 接口中找到更加切實可信的接口示例(請參閱參考資料以獲取鏈接)。文本窗口小部件和條目窗口小部件都實現了這個接口。
GObject 信號 一般而言,由事件驅動的 GUI 程序包含一個主循環。在該循環中,程序一直等待從 X 服務器發出的新消息。這些消息(稱為事件)由程序進行解釋,並允許程序對用戶選擇菜單項、單擊按鈕等操作作出反應。 信號除了將對象相互連接之外,與事件非常相似。它們允許對象在不需要顯式的事件循環的情況下自動對另一個對象狀態的變化作出“反應”。只需要將一個對象的信號連接到另一個對象的方法。然後,當第一個對象“發出”信號(由於狀態的內部變化)時,第二個對象“捕獲”該變化並作出適當的反應。之所以不需要事件循環,是由於信號是用回調實現的 - 發出信號的結果只是調用一個 C 函數。信號是一種有效和靈活的“粘合劑”,它們把程序中的對象“粘合”在一起。 事實上,如果您閱讀過以前的 GNOMEnclature 專欄文章,您將看到我們擁有相當多類似於下面這樣的示例代碼行: g_signal_connect (G_OBJECT (window), "destroy", gtk_main_quit, NULL); 在上面的代碼段中,我們將主窗口對象的 "destroy" 信號連接到 gtk_main_quit() 函數。由於 g_signal_connect(),當我們的窗口發出 "destroy" 信號時(當該窗口關閉時),我們的程序將自動退出。如果您過去曾經編寫過事件驅動的程序,那麼我認為您會發現,對於設置顯式事件處理循環這一常見做法,使用信號是一個令人耳目一新的變化。 這樣,我們了解了如何將信號連接到標准的 C 函數。但是我們該如何將這同一個信號連接到某個特定對象的方法呢?很簡單 - 只要使用下面這個模板: g_signal_connect (G_OBJECT (window), "destroy", classname_function, object); 只要用要調用的方法替換 classname_function,並用方法應該操作的對象指針替換 object。要了解有關信號的更多信息,請查閱本文的參考資料一節以獲取一些不錯的鏈接。 但是,請注意,Glib 和 GTK+ 2.0 對信號方面作了一些更改。盡管基本概念並沒有更改,但信號系統已從 GTK+ 2.0 遷移到 Glib。這產生了一個便利的副作用:可以在非 GUI 應用程序中使用信號。如果您正在編寫新 GNOME 2.0 代碼,則應當使用 Glib 新的 GSignal 類;GTK+ 2.0 信號仍然存在,但只是包含了幕後使用 Glib 的 GSignal 的“包裝器”。 盡管存在所有這些變化,但 Glib 信號(用 g_signal_ 作為前綴)總體上還是非常類似於 GTK+ 中對應的信號(用 gtk_signal_ 作為前綴)。但是,如果您已經創建了或正打算創建自己的定制信號,則應當閱讀 GObject GSignal 參考文檔(請參閱參考資料)以了解可能適用於您的某些變化。
結束語 既然我們研究了 GObject 基礎知識,您應當已經掌握必要的基本概念,可以開始您的 GNOME 編程生涯了。咱們在下一篇“GNOMEnclature”專欄文章中再見,繼續為 GNOME 2 平台作准備。到時候再見!