談面向?qū)ο笏枷朐贑語(yǔ)言的運(yùn)用
微信公眾號(hào):二進(jìn)制人生
專(zhuān)注于嵌入式linux開(kāi)發(fā)。
目錄
前言1、繼承2、封裝偽構(gòu)造函數(shù)3、多態(tài)
前言
我們都知道C語(yǔ)言是一門(mén)過(guò)程性語(yǔ)言,所謂過(guò)程性就是在解決問(wèn)題時(shí),將問(wèn)題按步驟分解。
例如,做菜的時(shí)候,先點(diǎn)火,再倒油,接著下菜翻炒,最后加鹽和醬油。但有時(shí)候借鑒面向?qū)ο?/a>的思想來(lái)組織代碼,邏輯層次會(huì)更加清晰。
C和C++的最大區(qū)別便是,C++有類(lèi),C沒(méi)有類(lèi)的概念。單單這一個(gè)類(lèi)使得C缺失很多的東西。好在C有結(jié)構(gòu)體,勉強(qiáng)可以當(dāng)0.1個(gè)類(lèi)來(lái)使用。
眾所周知,類(lèi)有三大特性:封裝、繼承、多態(tài)。我們來(lái)看看C語(yǔ)言如何借鑒類(lèi)的三大特性來(lái)更好的組織代碼。
1、繼承
C語(yǔ)言沒(méi)有嚴(yán)格意義上的繼承,可以借助結(jié)構(gòu)體嵌套實(shí)現(xiàn)類(lèi)似于繼承的形式,但始終不盡人意。
struct?parent
{
????int?a;
};
struct?son
{
????struct?parent?p;//兒子繼承父親
????int?b;
};
C++的類(lèi)可以實(shí)現(xiàn)成員的訪問(wèn)控制,例如將變量b聲明成private,那么外部就無(wú)法訪問(wèn)。但C的結(jié)構(gòu)體做不到。
在C++里頭,父親的私有成員,兒子是無(wú)法訪問(wèn)的。結(jié)構(gòu)體嵌套也做不到。因?yàn)榻Y(jié)構(gòu)體根本就沒(méi)有訪問(wèn)控制的概念。
對(duì)于C++而言,訪問(wèn)控制實(shí)質(zhì)上是在編譯層做的,我們?nèi)耘f可以通過(guò)指針來(lái)間接訪問(wèn)。
例如:
class?Base{
public:
???int?a;
private:
???int?b;
};
盡管b被聲明成私有,但我們?nèi)耘f有辦法訪問(wèn)它(借助指針繞過(guò)語(yǔ)法檢查):
Base?t;
int?*p?=?&t.a;
cout?<1]?<endl;
2、封裝
封裝就是把數(shù)據(jù)和方法打包到一個(gè)類(lèi)里面。C++的實(shí)現(xiàn)大致如下:
class?類(lèi)名
{
public:
????公有方法1
????公有方法2
????……
????公有數(shù)據(jù)1
????……
private:
????私有方法1
????私有方法2
????……
????私有數(shù)據(jù)1
????……
};
這樣做的好處是顯而易見(jiàn)的。一個(gè)類(lèi)實(shí)現(xiàn)了一個(gè)小模塊,使得代碼結(jié)構(gòu)比較清晰。對(duì)外接口和數(shù)據(jù)定義成public,允許調(diào)用者直接訪問(wèn)。內(nèi)部接口和數(shù)據(jù)定義成private,外部不可見(jiàn)。
在 QT 中,為了更好的隱藏一個(gè)類(lèi)的具體實(shí)現(xiàn),一般是一個(gè)公開(kāi)頭文件、一個(gè)私有頭文件,私有頭文件中定義實(shí)現(xiàn)的內(nèi)部細(xì)節(jié),公開(kāi)頭文件中定義開(kāi)放給客戶程序員的接口和公共數(shù)據(jù)。看看QObject
(qobject.h),對(duì)應(yīng)有一QObjectPrivate
(qobject_p.h ) ,其他的也類(lèi)似。
QObject{
public:
????xxx
????xxx
private:
????QObjectPrivate?*?priv;
};
我們可以借助C語(yǔ)言的指針和結(jié)構(gòu)體來(lái)實(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ù)也是通過(guò)函數(shù)指針的形式來(lái)實(shí)現(xiàn),本質(zhì)上是一致的。我們都知道類(lèi)的成員函數(shù)和類(lèi)的成員變量是分開(kāi)存儲(chǔ)的,同一個(gè)類(lèi)的所有對(duì)象,成員函數(shù)只需要占據(jù)一份地址空間。
在定義結(jié)構(gòu)體之后,函數(shù)指針并沒(méi)有賦值,一般我們會(huì)定義一個(gè)結(jié)構(gòu)體初始化函數(shù)來(lái)初始化結(jié)構(gòu)體成員,這有點(diǎn)類(lèi)似于類(lèi)的構(gòu)造函數(shù),但類(lèi)的構(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è)贑語(yǔ)言里頭有一個(gè)去初始化的函數(shù)來(lái)完成模塊的去初始化,這種思想不就是一樣的嗎?
static?int?operation1(struct?manager?*manager_ptr)
{
????……
}
static?int?operation2(struct?manager?*manager_ptr)
{
????……
}
偽構(gòu)造函數(shù)
注意,我們把兩個(gè)operation函數(shù)定義成了static,這樣子文件之外的函數(shù)就不能調(diào)用它,只能通過(guò)manager結(jié)構(gòu)體來(lái)調(diào)用。是不是感覺(jué)有點(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ù)我就不寫(xiě)了。
如果operation函數(shù)在外面的文件定義,則可以作為init_manager函數(shù)的參數(shù)傳入,這種場(chǎng)景也非常常見(jiàn)。我實(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ù)注冊(cè)下,我會(huì)在operation1函數(shù)處理完數(shù)據(jù)后,調(diào)用你們的處理函數(shù),給你們一個(gè)滿意的結(jié)果。
為了達(dá)到上面的目的,簡(jiǎn)單修改下,我們把函數(shù)operation2定義成一種類(lèi)型,
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;
}
通過(guò)上面的操作,我們用結(jié)構(gòu)體和函數(shù)指針完成了模塊化封裝。
我看了網(wǎng)上的博客,有些人為了特意模仿類(lèi),還用以下方式實(shí)現(xiàn)了類(lèi)似于類(lèi)的構(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;
}
以及類(lèi)似于類(lèi)的析構(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è)人不是很喜歡這種做法,萬(wàn)一忘記調(diào)用manager_delete還有內(nèi)存泄露的風(fēng)險(xiǎn)。
結(jié)構(gòu)體歸根到底還是結(jié)構(gòu)體,不能實(shí)現(xiàn)成員對(duì)外不可見(jiàn)。而C++中將成員聲明成private之后,外部就無(wú)法訪問(wèn)了。C語(yǔ)言里想這么做,只能將該成員移出結(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語(yǔ)言實(shí)現(xiàn)的多態(tài)并非是嚴(yán)格意義上的多態(tài),但是這種思想的應(yīng)用很廣泛,我們姑且叫它多態(tài)吧。你不解C++的多態(tài)也沒(méi)關(guān)系,絲毫不影響你理解下文。
linux的VFS便借鑒了這種思想。VFS(Virtual File System)是內(nèi)核提供的文件系統(tǒng)抽象層,其提供了文件系統(tǒng)的操作接口,可以隱藏底層不同文件系統(tǒng)的實(shí)現(xiàn)。
一個(gè)文件系統(tǒng)無(wú)非就是實(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)讀寫(xiě)等操作的方式都不一樣,所以將這些方法封裝成函數(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)。
再寫(xiě)一個(gè)實(shí)際的例子。
定義一個(gè)人的標(biāo)準(zhǔn)接口和數(shù)據(jù)如下:
strcut?man{
????int?head;
????int?body;
????……
????void?(*say_hello)(void);//見(jiàn)面問(wèn)候的方式
};
中國(guó)人見(jiàn)面時(shí),說(shuō)你好:
void?china_say_hello(void)
{
????printf(“你好”);
}
英國(guó)人見(jiàn)面時(shí),說(shuō)hello:
void?english_say_hello(void)
{
????printf(“hello”);
}
現(xiàn)在來(lái)初始化它們各自的問(wèn)候方式:
struct?man?china{
????.say_hello?=?china_say_hello;
}
struct?man?english{
????.say_hello?=?English_say_hello;
}
英國(guó)人和中國(guó)人對(duì)外呈現(xiàn)都是struct man,其見(jiàn)面問(wèn)候的接口都是man.say_hello,但其底層實(shí)現(xiàn)卻可以不一樣。
并且我們可以在程序運(yùn)行時(shí),隨意的更改中國(guó)人的問(wèn)候方式。比如嬰兒時(shí)期,只會(huì)“哇哇”叫,長(zhǎng)大了才會(huì)說(shuō)“你好”,我們可以改變成員say_hello的值,讓其在不同時(shí)期指向不同的函數(shù),從而達(dá)到運(yùn)行時(shí)多態(tài)的目的。
其實(shí)呢,C++的多態(tài),也是通過(guò)函數(shù)指針來(lái)實(shí)現(xiàn)的,學(xué)習(xí)過(guò)C++的同學(xué)就會(huì)知道,含有虛函數(shù)的類(lèi),會(huì)維護(hù)一個(gè)虛函數(shù)表,里面存放了虛函數(shù)的地址。所以說(shuō)啊,C語(yǔ)言是C++的母語(yǔ),萬(wàn)變不離指針,指針是C語(yǔ)言的一大法寶。
-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)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!