如何用c語言實現多態【轉】

轉自:https://blog.csdn.net/wwh578867817/article/details/45101033

前幾天在小組無意見聽到學姐說到c語言實現多態這個詞,比較感興趣,歡迎一起討論哈。

提前說一下,c實現多態算是一個奇怪的用法吧,而且不是完全的多態,並不通用,也不推薦用。感興趣的可以瞭解下

 

我們都知道多態性是針對OOP(面嚮對象語言)說的,OOP語言的三大特性:

封裝,繼承,多態

 

相對於OOP語言,c語言就比較“麻煩”且不靈活(可以查詢面向對象和麪向過程語言的區別),它是面向過程的。

但是可以用c來實現OOP的多態性是不是感覺很”高大上“。

 

多態性的表現,這裏用c++來簡述,其實OOP應該都差不多。

c++的多態分爲兩種:

1.編譯時多態:重載

2.運行時多態:重寫(也稱爲覆蓋override)

 

重載:函數名稱相同,但參數類型或參數個數不同的一組函數。在編譯期就決好的。

重寫:也稱爲覆蓋,牽扯到虛函數,簡單來說就是虛函數(impure virtual)爲我們實現一份默認的操作,我們可以使用這個也可以自己重寫(覆蓋)虛函數。

虛函數可參見陳皓大牛的這篇博客http://blog.csdn.net/haoel/article/details/1948051/ 介紹的很詳細。

 

一.重載

先說一個c中的宏,__V_ARGS__,是c99引入進來的可變參宏,一般是用來輸出debug信息。

舉個例子:


#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#define Check(...) printf(__VA_ARGS__);
#define Che(x) printf(x);

int main(int argc, char *argv[])
{
int i = 1, j = 2;
char *Error = "error!!";
Check("i = %d\n", j);
Check("i = %d, j = %d\n", i, j);
Check("i = %d, j = %d, Error:%s\n", i, j, Error);

return EXIT_SUCCESS;
}

 


有了這個宏__VA_ARGS__我們就可以實現簡單的多態了。不太理解這個宏可以查下

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#define func(...) myfunc((struct mystru) { __VA_ARGS__})

struct mystru
{
const char *name;
double d;
int number;
};

void myfunc(struct mystru ms)
{
printf("%s, %lf, %d\n", ms.name, ms.d, ms.number);
}

int main(int argc, char *argv[])
{
func();
func("hello", 1.1);
func("hello");
func("hello", 1.1, 100);
func(.name = "hello");
func(.d = 1.1);
func(.number = 100);
func(.d = 1.1, .name = "hello");

return EXIT_SUCCESS;
}


給func傳遞了個數不同且類型不同的參數,func都可以執行,是不是感覺很神奇。

還有需要注意下在沒有指定參數是結構體的哪個類型時我們必須按順序傳遞參數

func("hello", 1.1);
func("hello");
func("hello", 1.1, 100);
指定結構體中的類型了就可以任意傳遞參數,不用在乎順序
func(.name = "hello");
func(.d = 1.1);
func(.number = 100);
func(.d = 1.1, .name = "hello");
這個比較侷限,要想實現不同的功能必須在函數裏面判斷傳遞了幾個參數。然後選擇不同的功能。

順便補充下,我們都見過printf的函數原型,extern void printf(const char *format,...);

注意到"..."這個可變參東西了吧,printf的“...”內部實際上是通過三個函數來實現的,感興趣戳這裏http://zh.wikipedia.org/wiki/%E5%8F%AF%E8%AE%8A%E5%8F%83%E6%95%B8%E5%87%BD%E6%95%B8

現在來看看代碼中的關鍵句

#define func(...) myfunc((struct mystru) { __VA_ARGS__})</span>

解釋下:一般我們在使用結構體的時候都是先賦值在傳參數,這裏實際上是用"..."可變參數替換爲了__VA_ARGS__這個宏,然後轉化爲(struct mystru){ __VA_ARGS__ },其實就是
(struct mystru){ ... } 。 通過(struct mystru)把參數轉化結構體,這也是爲什麼如果我們不指定.name或者是.number時,必須按順序傳遞參數,否則報錯,因爲在內存中結構體佈局是確定的。不明確指定哪個參數只能按順序傳遞。

 

 

二.重寫(覆蓋)

c++中的(impure virtual)虛函數我們可以使用默認提供的功能,也可以自己實現一份。

瞭解虛函數的都知道,c++中只要有虛函數存在,內存中就會爲它維護一個虛函數表,我們在運行過程中定義父類(基類)的指針,運行時可以使用子類實際的成員函數,

也就是父類具有了多種“形態”,所謂多態。

詳情可參見上面推薦的文章。

如果我們自己實現的話,一定會用到指針->函數指針,因爲指針是c的精髓,函數指針是個非常強大的東西,許多功能都可以用函數指針來實現。

我以前的文章說過c++11中的std::function,產生一個函數對象,可以把任何形似函數指針的傳遞給它,比如函數對象,lambda表達式等,我猜想這些內部應該使用函數指針實現的。

還有一點重要的c++和c的不同,c++中struct和class本質其實沒有區別,區別僅僅是默認的“權限不同”,class是private,struct是public,sizeof(class)或者sizeof(struct)在c++中結果是1(用了一個char爲了保證空類和空類之間在內存中不會有相同的地址),在c中sizeof(struct)是0,從內存上來看,c++的class和struct不僅僅有數據還有成員函數,這些成員函數如果是non-inline的,那麼只會在內存中產生一份實例供所有對象使用,如果是inline的,會爲每一個使用這產生一個實例。而c的struct就簡單多了,它不佔用內存空間,每一個實例按照struct分配一份就好,但這也是問題所在,它在內存中沒實例啊。

那麼用c的struct實現時,我們就要想辦法讓它在內存中存在一份,大家都能找到它。

 

下面簡單談自己的想法,僅僅是想法,不嚴謹,不一定對,有想法會在補充的,就天馬行空一回^_^。

自己實現虛函數多態的話,肯定要有虛函數表,這個在c裏面實現就是結構體+函數指針

一般繼承,無虛函數覆蓋。


如上圖建立一個結構體,裏面保存着一個指針,是指向第一個virtual成員函數,然後連接第二個,第三個直到到void,然後實例該結構體,目的是讓它在內存

中存在一份實例,其他成員可以訪問它,從而找到所需函數的位置。

 

一般繼承,有虛函數覆蓋。

 

如上圖,再在內存中創建一份實例,然後修改指針域,是指向被覆蓋後的新的函數的指針。

多重繼承與一般繼承類似。

 

重要的問題:如何讓父類指針在運行時來判斷是父類還是子類從而在虛函數表中調用對應的函數呢?c++可以判斷類型,而c不行,需要好好考慮。
————————————————
版權聲明:本文爲CSDN博主「I_myours」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/wwh578867817/article/details/45101033

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章