用 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 對象交互。
現在,讓我們把 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 編程構造完全兼容。
接口給這個問題提供了解決方案,它答應我們給全異類添加公共功能。因此回到上面的示例,我們只要為 Horse、Car 和 House 類編寫“Talk”接口。忽然之間這三個不相關的類都“能夠通信”了,并且能夠使用我們所創建的與通信相關的新函數。并且這些與通信有關的新函數使用“Talk”接口本身與我們的對象“愉快”地進行交互。因此,由于接口,這三個不相關的類現在“講同一種語言了”。
在 Glib 中,您可以為一個類創建任意數量的接口。因此,假如我們為 House 類創建了一個 Talk 接口,我們可以如下定義 say() 函數:
say (TALK(myhouse), "hello there!"); 可以在 GTK+ 2 的 GtkEditable 接口中找到更加切實可信的接口示例(請參閱參考資料以獲取鏈接)。文本窗口小部件和條目窗口小部件都實現了這個接口。
GObject 信號 一般而言,由事件驅動的 GUI 程序包含一個主循環。在該循環中,程序一直等待從 X 服務器發出的新消息。這些消息(稱為事件)由程序進行解釋,并答應程序對用戶選擇菜單項、單擊按鈕等操作作出反應。
信號除了將對象相互連接之外,與事件非常相似。它們答應對象在不需要顯式的事件循環的情況下自動對另一個對象狀態的變化作出“反應”。只需要將一個對象的信號連接到另一個對象的方法。然后,當第一個對象“發出”信號(由于狀態的內部變化)時,第二個對象“捕捉”該變化并作出適當的反應。之所以不需要事件循環,是由于信號是用回調實現的 - 發出信號的結果只是調用一個 C 函數。信號是一種有效和靈活的“粘合劑”,它們把程序中的對象“粘合”在一起。