C語言實現面向物件三大特性:封裝、繼承、多型

不想錯過我的推送,記得右上角-檢視公眾號-

設為星標

,送你一顆小星星

不知道有多少人去了解過語言的發展史,早期C語言的語法功能其實比較簡單。隨著應用需求和場景的變化,C語言的語法功能在不斷升級變化。

雖然我們的教材有這麼一個結論:C語言是

面向過程

的語言,C++是

面向物件

的程式語言,但面向物件的概念是在C語言階段就有了,而且應用到了很多地方,比如某些作業系統核心、通訊協議等。

面向物件程式設計,也就是大家說的OOP(Object Oriented Programming)並不是一種特定的語言或者工具,

它只是一種設計方法、設計思想

,它表現出來的三個最基本的特性就是

封裝、繼承與多型

1、為什麼用C實現OOP

閱讀文字之前肯定有讀者會問這樣的問題:我們有C++面向物件的語言,為什麼還要用C語言實現面向物件呢?

C語言這種非面向物件的語言,同樣也可以使用面向物件的思路來編寫程式的。

只是用面向物件的C++語言來實現面向物件程式設計會更簡單一些,但是C語言的高效性是其他面向物件程式語言無法比擬的。

當然使用C語言來實現面向物件的開發

相對不容易理解

,這就是為什麼大多數人學過C語言卻看不懂Linux核心原始碼。

所以這個問題其實很好理解,只要有一定C語言程式設計經驗的讀者都應該能明白:

面向過程的C語言和麵向物件的C++語言相比,程式碼執行效率、程式碼量都有很大差異

。在效能不是很好、資源不是很多的MCU中使用C語言面向物件程式設計就顯得尤為重要。

2、所具備的條件

要想使用C語言實現面向物件,首先需要具備一些基礎知識。比如:(C語言中的)結構體、函式、指標,以及函式指標等,(C++中的)基類、派生、多型、繼承等。

首先,不僅僅是瞭解這些基礎知識,而是有一定的程式設計經驗,因為上面說了“面向物件是一種設計方法、設計思想”,如果只是停留在字面意思的理解,沒有這種設計思想肯定不行。

因此,不建議初學者使用C語言實現面向物件,特別是在真正專案中。建議把基本功練好,再使用。

利用C語言實現面向物件的方法很多,下面就來描述最基本的封裝、繼承和多型。

3、封裝

封裝就是把資料和函式打包到一個類裡面,

其實大部分C語言程式設計者都已近接觸過了。

C 標準庫中的 fopen(), fclose(), fread(), fwrite()等函式的操作物件就是 FILE。資料內容就是 FILE,資料的讀寫操作就是 fread()、fwrite(),fopen() 類比於建構函式,fclose() 就是解構函式。

這個看起來似乎很好理解,那下面我們實現一下基本的封裝特性。

這是 Shape 類的宣告,非常簡單,很好理解。一般會把宣告放到標頭檔案裡面 “Shape。h”。來看下 Shape 類相關的定義,當然是在 “Shape。c” 裡面。

再看下 main。c

編譯之後,看看執行結果:

整個例子,非常簡單,非常好理解。以後寫程式碼時候,要多去想想標準庫的檔案IO操作,這樣也有意識的去培養面向物件程式設計的思維。

4、繼承

繼承就是基於現有的一個類去定義一個新類

,這樣有助於重用程式碼,更好的組織程式碼。在 C 語言裡面,去實現單繼承也非常簡單,只要把基類放到繼承類的第一個資料成員的位置就行了。

例如,我們現在要建立一個 Rectangle 類,我們只要繼承 Shape 類已經存在的屬性和操作,再新增不同於 Shape 的屬性和操作到 Rectangle 中。

下面是 Rectangle 的宣告與定義:

我們來看一下 Rectangle 的繼承關係和記憶體佈局:

C語言實現面向物件三大特性:封裝、繼承、多型

因為有這樣的記憶體佈局,所以你可以很安全的傳一個指向 Rectangle 物件的指標到一個期望傳入 Shape 物件的指標的函式中,就是一個函式的引數是 “Shape *”,你可以傳入 “Rectangle *”,並且這是非常安全的。這樣的話,基類的所有屬性和方法都可以被繼承類繼承!

輸出結果:

5、多型

C++ 語言實現多型就是使用虛擬函式。在 C 語言裡面,也可以實現多型。

現在,我們又要增加一個圓形,並且在 Shape 要擴充套件功能,我們要增加 area() 和 draw() 函式。但是 Shape 相當於抽象類,不知道怎麼去計算自己的面積,更不知道怎麼去畫出來自己。而且,矩形和圓形的面積計算方式和幾何影象也是不一樣的。

下面讓我們重新宣告一下 Shape 類:

看下加上虛擬函式之後的類關係圖:

C語言實現面向物件三大特性:封裝、繼承、多型

5.1 虛表和虛指標

虛表(Virtual Table)是這個類所有虛擬函式的函式指標的集合。

虛指標(Virtual Pointer)是一個指向虛表的指標。這個虛指標必須存在於每個物件例項中,會被所有子類繼承。

在《Inside The C++ Object Model》的第一章內容中,有這些介紹。

5.2 在建構函式中設定vptr

在每一個物件例項中,vptr 必須被初始化指向其 vtbl。最好的初始化位置就是在類的建構函式中。事實上,在建構函式中,C++ 編譯器隱式的建立了一個初始化的vptr。在 C 語言裡面, 我們必須顯示的初始化vptr。

下面就展示一下,在 Shape 的構造函數里面,如何去初始化這個 vptr。

5.3 繼承 vtbl 和 過載 vptr

上面已經提到過,基類包含 vptr,子類會自動繼承。但是,vptr 需要被子類的虛表重新賦值。並且,這也必須發生在子類的建構函式中。下面是 Rectangle 的建構函式。

5.4 虛擬函式呼叫

有了前面虛表(Virtual Tables)和虛指標(Virtual Pointers)的基礎實現,虛擬呼叫(後期繫結)就可以用下面程式碼實現了。

這個函式可以放到。c檔案裡面,但是會帶來一個缺點就是每個虛擬呼叫都有額外的呼叫開銷。為了避免這個缺點,如果編譯器支援行內函數(C99)。我們可以把定義放到標頭檔案裡面,類似下面:

如果是老一點的編譯器(C89),我們可以用宏函式來實現,類似下面這樣:

看一下例子中的呼叫機制:

C語言實現面向物件三大特性:封裝、繼承、多型

5.5 main.c

輸出結果:

6、總結

還是那句話,

面向物件程式設計是一種方法,並不侷限於某一種程式語言。

用 C 語言實現封裝、單繼承,理解和實現起來比較簡單,多型反而會稍微複雜一點,如果打算廣泛的使用多型,還是推薦轉到 C++ 語言上,畢竟這層複雜性被這個語言給封裝了,你只需要簡單的使用就行了。但並不代表,C 語言實現不了多型這個特性。

文章來源:嵌入式情報局

TAG: C語言shape面向物件多型C++