談面向?qū)ο笏枷朐贑語言的運(yùn)用
微信公眾號(hào):二進(jìn)制人生
專注于嵌入式linux開發(fā)。
目錄
前言1、繼承2、封裝偽構(gòu)造函數(shù)3、多態(tài)
前言
我們都知道C語言是一門過程性語言,所謂過程性就是在解決問題時(shí),將問題按步驟分解。
例如,做菜的時(shí)候,先點(diǎn)火,再倒油,接著下菜翻炒,最后加鹽和醬油。但有時(shí)候借鑒面向?qū)ο?/a>的思想來組織代碼,邏輯層次會(huì)更加清晰。
C和C++的最大區(qū)別便是,C++有類,C沒有類的概念。單單這一個(gè)類使得C缺失很多的東西。好在C有結(jié)構(gòu)體,勉強(qiáng)可以當(dāng)0.1個(gè)類來使用。
眾所周知,類有三大特性:封裝、繼承、多態(tài)。我們來看看C語言如何借鑒類的三大特性來更好的組織代碼。
1、繼承
C語言沒有嚴(yán)格意義上的繼承,可以借助結(jié)構(gòu)體嵌套實(shí)現(xiàn)類似于繼承的形式,但始終不盡人意。
struct?parent
{
????int?a;
};
struct?son
{
????struct?parent?p;//兒子繼承父親
????int?b;
};
C++的類可以實(shí)現(xiàn)成員的訪問控制,例如將變量b聲明成private,那么外部就無法訪問。但C的結(jié)構(gòu)體做不到。
在C++里頭,父親的私有成員,兒子是無法訪問的。結(jié)構(gòu)體嵌套也做不到。因?yàn)榻Y(jié)構(gòu)體根本就沒有訪問控制的概念。
對(duì)于C++而言,訪問控制實(shí)質(zhì)上是在編譯層做的,我們?nèi)耘f可以通過指針來間接訪問。
例如:
class?Base{
public:
???int?a;
private:
???int?b;
};
盡管b被聲明成私有,但我們?nèi)耘f有辦法訪問它(借助指針繞過語法檢查):
Base?t;
int?*p?=?&t.a;
cout?<1]?<endl;
2、封裝
封裝就是把數(shù)據(jù)和方法打包到一個(gè)類里面。C++的實(shí)現(xiàn)大致如下:
class?類名
{
public:
????公有方法1
????公有方法2
????……
????公有數(shù)據(jù)1
????……
private:
????私有方法1
????私有方法2
????……
????私有數(shù)據(jù)1
????……
};
這樣做的好處是顯而易見的。一個(gè)類實(shí)現(xiàn)了一個(gè)小模塊,使得代碼結(jié)構(gòu)比較清晰。對(duì)外接口和數(shù)據(jù)定義成public,允許調(diào)用者直接訪問。內(nèi)部接口和數(shù)據(jù)定義成private,外部不可見。
在 QT 中,為了更好的隱藏一個(gè)類的具體實(shí)現(xiàn),一般是一個(gè)公開頭文件、一個(gè)私有頭文件,私有頭文件中定義實(shí)現(xiàn)的內(nèi)部細(xì)節(jié),公開頭文件中定義開放給客戶程序員的接口和公共數(shù)據(jù)??纯?code style="font-size: inherit;line-height: inherit;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(233, 105, 0);background: rgb(248, 248, 248);">QObject (qobject.h),對(duì)應(yīng)有一QObjectPrivate
(qobject_p.h ) ,其他的也類似。
QObject{
public:
????xxx
????xxx
private:
????QObjectPrivate?*?priv;
};
我們可以借助C語言的指針和結(jié)構(gòu)體來實(shí)現(xiàn)方法和數(shù)據(jù)的封裝?;究蚣苋缦拢?/p>
struct?結(jié)構(gòu)體名{
????數(shù)據(jù)1;
????數(shù)據(jù)2;
????……
????方法1:
????方法2;
????……
}
在結(jié)構(gòu)體里定義成員變量很容易,直接int a;
在結(jié)構(gòu)體里定義成員函數(shù)要使用函數(shù)指針,比如:
int?(*func)(void?*)
所以,我們把上面的框架具體化就是:
struct?manager{
????int?data1;
????int?data2;
????int?(*operation1)(struct?manager*);
????int?(*operation2)(struct?manager*);
};
實(shí)際上,C++的成員函數(shù)也是通過函數(shù)指針的形式來實(shí)現(xiàn),本質(zhì)上是一致的。我們都知道類的成員函數(shù)和類的成員變量是分開存儲(chǔ)的,同一個(gè)類的所有對(duì)象,成員函數(shù)只需要占據(jù)一份地址空間。
在定義結(jié)構(gòu)體之后,函數(shù)指針并沒有賦值,一般我們會(huì)定義一個(gè)結(jié)構(gòu)體初始化函數(shù)來初始化結(jié)構(gòu)體成員,這有點(diǎn)類似于類的構(gòu)造函數(shù),但類的構(gòu)造函數(shù)在創(chuàng)建對(duì)象時(shí)自動(dòng)調(diào)用,而我們這個(gè)結(jié)構(gòu)體初始化函數(shù)只能自己手動(dòng)調(diào)用了。
同樣的,對(duì)標(biāo)C++的析構(gòu)函數(shù),我們在C語言里頭有一個(gè)去初始化的函數(shù)來完成模塊的去初始化,這種思想不就是一樣的嗎?
static?int?operation1(struct?manager?*manager_ptr)
{
????……
}
static?int?operation2(struct?manager?*manager_ptr)
{
????……
}
偽構(gòu)造函數(shù)
注意,我們把兩個(gè)operation函數(shù)定義成了static,這樣子文件之外的函數(shù)就不能調(diào)用它,只能通過manager結(jié)構(gòu)體來調(diào)用。是不是感覺有點(diǎn)封裝的意味。
void?init_manager(struct?manager*?manager_ptr)
{
????manager_ptr->operation1?=?operation1;
????manager_ptr->operation2?=?operation2;
????manager_ptr->data1?=?0;
????manager_ptr->data2?=?0;
}
去初始化函數(shù)我就不寫了。
如果operation函數(shù)在外面的文件定義,則可以作為init_manager函數(shù)的參數(shù)傳入,這種場景也非常常見。我實(shí)現(xiàn)了模塊A,該模塊的operation1函數(shù)處理數(shù)據(jù)并輸出一些結(jié)果。但是我并不知道使用該模塊的人想要什么格式的結(jié)果,比如有一些人想要json格式的結(jié)果,有些人想要xml格式的結(jié)果。我不能幫他們一一實(shí)現(xiàn)一個(gè)方法,我干脆叫你們統(tǒng)一按照我指定的函數(shù)模板,實(shí)現(xiàn)一個(gè)處理函數(shù),完了你們調(diào)用結(jié)構(gòu)體初始化函數(shù)注冊下,我會(huì)在operation1函數(shù)處理完數(shù)據(jù)后,調(diào)用你們的處理函數(shù),給你們一個(gè)滿意的結(jié)果。
為了達(dá)到上面的目的,簡單修改下,我們把函數(shù)operation2定義成一種類型,
typedef?int??(*FUN_CBK)(struct?manager *manager_ptr);
結(jié)構(gòu)體定義稍作修改:
struct?manager{
????int?data1;
????int?data2;
????int?(*operation1)(struct?manager*);
????FUN_CBK?call_back;
};
結(jié)構(gòu)體初始化函數(shù)也要做相應(yīng)的修改,增加了一個(gè)函數(shù)指針形參:
void?init_manager(struct?manager*?manager_ptr,?\
FUN_CBK?fun)
{
????manager_ptr->operation1?=?operation1;
????manager_ptr->call_back?=?fun;//用外部傳入的回調(diào)函數(shù)進(jìn)行初始化
????manager_ptr->data1?=?0;
????manager_ptr->data2?=?0;
}
通過上面的操作,我們用結(jié)構(gòu)體和函數(shù)指針完成了模塊化封裝。
我看了網(wǎng)上的博客,有些人為了特意模仿類,還用以下方式實(shí)現(xiàn)了類似于類的構(gòu)造函數(shù):
struct?manager?*manager_create(int?m,?int?n)
{
????struct?manager?*m?=?(struct?manager?*)\
????malloc(sizeof(struct?manager?*));
????m->data1?=?m;
????m->data2?=?n;
????m->operation1?=?operation1;
????m->operation2?=?operation2;
}
以及類似于類的析構(gòu)函數(shù):
void?manager_delete(struct?manager?*m_ptr)
{
????free(m_ptr);
????m_ptr?=?NULL;
}
使用示例:
struct?manager?*m_ptr?=?manager_create?(1,2);
manager_delete(m_ptr);
個(gè)人不是很喜歡這種做法,萬一忘記調(diào)用manager_delete還有內(nèi)存泄露的風(fēng)險(xiǎn)。
結(jié)構(gòu)體歸根到底還是結(jié)構(gòu)體,不能實(shí)現(xiàn)成員對(duì)外不可見。而C++中將成員聲明成private之后,外部就無法訪問了。C語言里想這么做,只能將該成員移出結(jié)構(gòu)體,定義為static形式。因?yàn)镃不支持在結(jié)構(gòu)體內(nèi)部定義static變量(不信,你可以自己去試下)。
為何不能在結(jié)構(gòu)體內(nèi)定義static變量,想想就知道了,static變量的地址在編譯鏈接之后是唯一且確定的,而結(jié)構(gòu)體只有在實(shí)例化時(shí)才能確定其地址,并且每個(gè)結(jié)構(gòu)體實(shí)例都有自己的地址空間。
3、多態(tài)
多態(tài)在上面的例子也有體現(xiàn)。C語言實(shí)現(xiàn)的多態(tài)并非是嚴(yán)格意義上的多態(tài),但是這種思想的應(yīng)用很廣泛,我們姑且叫它多態(tài)吧。你不解C++的多態(tài)也沒關(guān)系,絲毫不影響你理解下文。
linux的VFS便借鑒了這種思想。VFS(Virtual File System)是內(nèi)核提供的文件系統(tǒng)抽象層,其提供了文件系統(tǒng)的操作接口,可以隱藏底層不同文件系統(tǒng)的實(shí)現(xiàn)。
一個(gè)文件系統(tǒng)無非就是實(shí)現(xiàn)對(duì)文件、目錄的管理。針對(duì)文件VFS定義了統(tǒng)一的結(jié)構(gòu)體:
struct?file?{
????union?{
????????struct?llist_nodefu_llist;
????????struct?rcu_head?fu_rcuhead;
????}?f_u;
????struct?pathf_path;
????struct?inode*f_inode;/*?cached?value?*/
????const?struct?file_operations*f_op;
????……
};?
strcut file代表一個(gè)文件,每種文件系統(tǒng)(比如ext3,vfat)實(shí)現(xiàn)讀寫等操作的方式都不一樣,所以將這些方法封裝成函數(shù)指針,統(tǒng)一定義在結(jié)構(gòu)體struct file_operations
內(nèi)。
struct?file_operations?{
????struct?module?*owner;
????loff_t?(*llseek)?(struct?file?*,?loff_t,?int);
????ssize_t?(*read)?(struct?file?*,?char?__user?*,?size_t,?loff_t?*);
????ssize_t?(*write)?(struct?file?*,?const?char?__user?*,?size_t,?loff_t?*);
????……
};
每個(gè)文件系統(tǒng)各自完成自己的實(shí)現(xiàn)。
再寫一個(gè)實(shí)際的例子。
定義一個(gè)人的標(biāo)準(zhǔn)接口和數(shù)據(jù)如下:
strcut?man{
????int?head;
????int?body;
????……
????void?(*say_hello)(void);//見面問候的方式
};
中國人見面時(shí),說你好:
void?china_say_hello(void)
{
????printf(“你好”);
}
英國人見面時(shí),說hello:
void?english_say_hello(void)
{
????printf(“hello”);
}
現(xiàn)在來初始化它們各自的問候方式:
struct?man?china{
????.say_hello?=?china_say_hello;
}
struct?man?english{
????.say_hello?=?English_say_hello;
}
英國人和中國人對(duì)外呈現(xiàn)都是struct man,其見面問候的接口都是man.say_hello,但其底層實(shí)現(xiàn)卻可以不一樣。
并且我們可以在程序運(yùn)行時(shí),隨意的更改中國人的問候方式。比如嬰兒時(shí)期,只會(huì)“哇哇”叫,長大了才會(huì)說“你好”,我們可以改變成員say_hello的值,讓其在不同時(shí)期指向不同的函數(shù),從而達(dá)到運(yùn)行時(shí)多態(tài)的目的。
其實(shí)呢,C++的多態(tài),也是通過函數(shù)指針來實(shí)現(xiàn)的,學(xué)習(xí)過C++的同學(xué)就會(huì)知道,含有虛函數(shù)的類,會(huì)維護(hù)一個(gè)虛函數(shù)表,里面存放了虛函數(shù)的地址。所以說啊,C語言是C++的母語,萬變不離指針,指針是C語言的一大法寶。
-END-
本文授權(quán)轉(zhuǎn)載自二進(jìn)制人生,作者:二進(jìn)制人生
推薦閱讀
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場,如有問題,請(qǐng)聯(lián)系我們,謝謝!