我們都知道C語言是一門過程性語言,所謂過程性就是在解決問題時,將問題按步驟分解。
例如,做菜的時候,先點火,再倒油,接著下菜翻炒,最後加鹽和醬油。但有時候借鑑面向對象的思想來組織代碼,邏輯層次會更加清晰。
C和C++的最大區別便是,C++有類,C沒有類的概念。單單這一個類使得C缺失很多的東西。好在C有結構體,勉強可以當0.1個類來使用。
眾所周知,類有三大特性:封裝、繼承、多態。我們來看看C語言如何借鑑類的三大特性來更好的組織代碼。
1、繼承
C語言沒有嚴格意義上的繼承,可以藉助結構體嵌套實現類似於繼承的形式,但始終不盡人意。
C++的類可以實現成員的訪問控制,例如將變量b聲明成private,那麼外部就無法訪問。但C的結構體做不到。
在C++裡頭,父親的私有成員,兒子是無法訪問的。結構體嵌套也做不到。因為結構體根本就沒有訪問控制的概念。
對於C++而言,訪問控制實質上是在編譯層做的,我們仍舊可以通過指針來間接訪問。
例如:
儘管b被聲明成私有,但我們仍舊有辦法訪問它(藉助指針繞過語法檢查):
2、封裝
封裝就是把數據和方法打包到一個類裡面。C++的實現大致如下:
這樣做的好處是顯而易見的。
一個類實現了一個小模塊,使得代碼結構比較清晰。對外接口和數據定義成public,允許調用者直接訪問。
內部接口和數據定義成private,外部不可見。
在 QT 中,為了更好的隱藏一個類的具體實現,一般是一個公開頭文件、一個私有頭文件,私有頭文件中定義實現的內部細節,公開頭文件中定義開放給客戶程式設計師的接口和公共數據。看看QObject (qobject.h),對應有一QObjectPrivate(qobject_p.h ) ,其他的也類似。
我們可以藉助C語言的指針和結構體來實現方法和數據的封裝。基本框架如下:
在結構體裡定義成員變量很容易,直接int a;
在結構體裡定義成員函數要使用函數指針,比如:
所以,我們把上面的框架具體化就是:
實際上,C++的成員函數也是通過函數指針的形式來實現,本質上是一致的。
我們都知道類的成員函數和類的成員變量是分開存儲的,同一個類的所有對象,成員函數只需要佔據一份地址空間。
在定義結構體之後,函數指針並沒有賦值,一般我們會定義一個結構體初始化函數來初始化結構體成員,這有點類似於類的構造函數,但類的構造函數在創建對象時自動調用,而我們這個結構體初始化函數只能自己手動調用了。
同樣的,對標C++的析構函數,我們在C語言裡頭有一個去初始化的函數來完成模塊的去初始化,這種思想不就是一樣的嗎?
偽構造函數
注意,我們把兩個operation函數定義成了static,這樣子文件之外的函數就不能調用它,只能通過manager結構體來調用。是不是感覺有點封裝的意味。
去初始化函數我就不寫了。
為了達到上面的目的,簡單修改下,我們把函數operation2定義成一種類型,
結構體定義稍作修改:
結構體初始化函數也要做相應的修改,增加了一個函數指針形參:
通過上面的操作,我們用結構體和函數指針完成了模塊化封裝。
我看了網上的博客,有些人為了特意模仿類,還用以下方式實現了類似於類的構造函數:
以及類似於類的析構函數:
使用示例:
個人不是很喜歡這種做法,萬一忘記調用manager_delete還有內存洩露的風險。
結構體歸根到底還是結構體,不能實現成員對外不可見。而C++中將成員聲明成private之後,外部就無法訪問了。
C語言裡想這麼做,只能將該成員移出結構體,定義為static形式。因為C不支持在結構體內部定義static變量(不信,你可以自己去試下)。
為何不能在結構體內定義static變量,想想就知道了,static變量的地址在編譯連結之後是唯一且確定的,而結構體只有在實例化時才能確定其地址,並且每個結構體實例都有自己的地址空間。
3、多態
多態在上面的例子也有體現。C語言實現的多態並非是嚴格意義上的多態,但是這種思想的應用很廣泛,我們姑且叫它多態吧。你不解C++的多態也沒關係,絲毫不影響你理解下文。
linux的VFS便借鑑了這種思想。VFS(Virtual File System)是內核提供的文件系統抽象層,其提供了文件系統的操作接口,可以隱藏底層不同文件系統的實現。
一個文件系統無非就是實現對文件、目錄的管理。針對文件VFS定義了統一的結構體:
strcut file代表一個文件,每種文件系統(比如ext3,vfat)實現讀寫等操作的方式都不一樣,所以將這些方法封裝成函數指針,統一定義在結構體struct file_operations內。
每個文件系統各自完成自己的實現。
再寫一個實際的例子。
定義一個人的標準接口和數據如下:
中國人見面時,說你好:
英國人見面時,說hello:
現在來初始化它們各自的問候方式:
英國人和中國人對外呈現都是struct man,其見面問候的接口都是man.say_hello,但其底層實現卻可以不一樣。
並且我們可以在程序運行時,隨意的更改中國人的問候方式。比如嬰兒時期,只會「哇哇」叫,長大了才會說「你好」,我們可以改變成員say_hello的值,讓其在不同時期指向不同的函數,從而達到運行時多態的目的。
其實呢,C++的多態,也是通過函數指針來實現的,學習過C++的同學就會知道,含有虛函數的類,會維護一個虛函數表,裡面存放了虛函數的地址。
所以說啊,C語言是C++的母語,萬變不離指針,指針是C語言的一大法寶。