1. 程式人生 > >C語言面向物件程式設計(一):封裝與繼承

C語言面向物件程式設計(一):封裝與繼承

最近在用 C 做專案,之前用慣了 C++ ,轉回頭來用C 還真有點不適應。 C++ 語言中自帶面向物件支援,如封裝、繼承、多型等面向物件的基本特徵。 C 原本是面向過程的語言,自身沒有內建這些特性,但我們還是可以利用 C 語言本身已有的特性來實現面向物件的一些基本特徵。接下來我們就一一來細說封裝、繼承、多型、純虛類等面向物件特性在 C 語言中如何實現,並且給出例項。

    這篇文章中我們先說封裝和繼承。

    先來看封裝。

    所謂封裝,通俗地說,就是一個姑娘化了妝,只給你看她想讓你看的那一面,至於裡面是否颳了骨、墊了東西,不給你看。說到封裝就得說隱藏,這是對兄弟概念;其實我理解隱藏是更深的封裝,完全不給你看見,而封裝可能是猶抱琵琶半遮面。封裝在 C++ 語言中有 protected 、 private 關鍵字在語言層面上支援,而 C 語言中沒有這些。 C 有結構體( struct ),其實可以實現封裝和隱藏。

    在 QT 中,為了更好的隱藏一個類的具體實現,一般是一個公開標頭檔案、一個私有標頭檔案,私有標頭檔案中定義實現的內部細節,公開標頭檔案中定義開放給客戶程式設計師的介面和公共資料。看看 QObject (qobject.h ),對應有一個 QObjectPrivate (qobject_p.h ) ,其他的也類似。而程式碼框架如下:

  1. QObject{

  2. public:

  3. xxx

  4. xxx

  5. private:

  6. QObjectPrivate * priv;

  7. };

    我們在 C 語言中完全可以用同樣的方法來實現封裝和隱藏,只不過是放在結構體中而已。程式碼框架如下:

  1. struct st_abc_private;

  2. struct st_abc {

  3. int a;

  4. xxx;

  5. void (*xyz_func)(struct st_abc*);

  6. struct st_abc_private * priv;

  7. };

    上面的程式碼,我們只前向宣告結構體 struct st_abc_private ,沒人知道它裡面具體是什麼東西。假如 struct st_abc 對應的標頭檔案是 abc.h ,那麼把 st_abc_private 的宣告放在 abc_p.h 中,abc.c 檔案包含 abc_p.h ,那麼在實現 struct st_abc 的函式指標 xyz_func 時如何使用 struct st_abc_private ,客戶程式設計師根本無須知道。

    這樣做的好處是顯而易見的,除了預定義好的介面,客戶程式設計師完全不需要知道實現細節,即便實現經過重構完全重來,客戶程式設計師也不需要關注,甚至相應的模組連重新編譯都不要——因為 abc.h 自始至終都沒變過。

    上面程式碼有個問題,客戶程式設計師如何得到 struct st_abc 的一個例項,他不知道 struct st_abc_private 如何實現的呀。 C 中沒有建構函式,只好我們自己提供了:我們可以在 abc.h 中宣告一個類似建構函式的函式來生成 struct st_abc 的例項,名字就叫作 new_abc() ,函式原型如下:

struct st_abc * new_abc();

    至於實現,我們放在 abc.c 中,客戶程式設計師不需要知道。相應的,還有個類似解構函式的函式,原型如下:

void delete_abc(struct st_abc *);

    到現在為止,封裝和隱藏就實現了,而且很徹底。接下來看繼承。

    什麼是繼承?在面向物件層面上不講了,只說語法層面。語法層面上講,繼承就是派生類擁有父類的資料、方法,又添了點自己的東西,所謂子承父業,發揚光大。在 C 語言中可以用結構體的包含來實現繼承關係。程式碼框架如下:

  1. struct st_base{

  2. xxx;

  3. };

  4. struct st_derived{

  5. struct sb_base base;

  6. yyy;

  7. };

    程式碼上就是這麼簡單,不過有一點要注意:第一點就是派生類(結構體)中一定要把父類型別的成員放在第一個。

    繼承在語法層面上看,有資料成員、函式,資料成員通過上面的方法自動就“繼承”了,至於函式,在結構體表示為函式指標,其實也是一個數據成員,是個指標而已,也會自動“繼承”。之所以還要在這裡列出來說明,是因為 C++ 中有一個很重要的概念:過載。要在 C 中完整實現有點兒麻煩。

    過載,我們常說的過載大概有三種含義:

  • 其一,函式過載,指函式名字一樣,引數個數、型別不一樣的函式宣告和實現。由於 C 編譯器的緣故,不支援。不過這個影響不大。
  • 其二,重定義或者說覆蓋,指派生類中定義與基類簽名一樣(名字、返回值、引數完全一樣)的非虛擬函式,這樣派生類的中的函式會覆蓋基類的同簽名函式,通過成員操作符訪問時無法訪問基類的同簽名函式。
  • 其三,虛擬函式重寫,指在派生類中實現基類定義的虛擬函式或純虛擬函式。虛擬函式是實現多型的關鍵,可以在結構體中使用函式指標來表達,但要完全實現,也很麻煩。

    我們平常在交流時通常不明確區分上面三種類型的過載,這裡出於習慣,也不作區分。     好了,第一篇就到這裡,有時間會往下續。