導(dǎo)讀:使用面向?qū)ο蟮恼Z(yǔ)言可以實(shí)現(xiàn)多態(tài),并且在很大程度上降低了代碼的復(fù)雜性。對(duì)于面向過(guò)程的 C 語(yǔ)言同樣可以實(shí)現(xiàn)多態(tài),本文將著重介紹 C 語(yǔ)言是如何實(shí)現(xiàn)多態(tài)的。
前言:關(guān)于多態(tài),關(guān)于 C多態(tài) (polymorphism) 一詞最初
作為讀者的你或許對(duì)于面向?qū)ο缶幊桃延兄畹囊?jiàn)解,或許對(duì)于多態(tài)的方便與神奇你也有了深入的認(rèn)識(shí)。這時(shí)候你訝異的開(kāi)始質(zhì)疑了:“多態(tài),那是面向?qū)ο缶幊滩庞械募夹g(shù),C 語(yǔ)言是面向過(guò)程的啊!”而我想說(shuō)的是,C 語(yǔ)言作為一種編程語(yǔ)言,也許并不是為了面向?qū)ο缶幊潭O(shè)計(jì),但這并不意味著它不能實(shí)現(xiàn)面向?qū)ο缶幊趟軐?shí)現(xiàn)的功能,就比如說(shuō),多態(tài)性。
在本文中我們使用一個(gè)簡(jiǎn)單的單鏈表作為例子,展示 C 語(yǔ)言是如何體現(xiàn)多態(tài)性的。
結(jié)構(gòu)體:不得不說(shuō)的故事
許多從寫(xiě) C 代碼開(kāi)始,逐漸走向 C++ 的程序員都知道,其實(shí) C++ 里面的 class,其前身正是 C 語(yǔ)言中的 structure。很多基于 C 語(yǔ)言背景介紹 C++ 的書(shū)籍,在介紹到 class 這一章的時(shí)候都會(huì)向讀者清晰地展示,一個(gè) C 語(yǔ)言里的 structure 是怎樣逐漸變成一個(gè)典型的 C++ class 的,甚至最后得出結(jié)論:“structure 就是一個(gè)所有成員都公有的類(lèi)”,當(dāng)然了,class 還是 class,不能簡(jiǎn)單的把它看做一個(gè)復(fù)雜化了的 structure 而已。
下面我們來(lái)看看在 C 語(yǔ)言中定義一個(gè)簡(jiǎn)單的存儲(chǔ)整型數(shù)據(jù)的單鏈表節(jié)點(diǎn)是怎么做的,當(dāng)然是用結(jié)構(gòu)體。大部分人會(huì)像我一樣,在 linkList.h 文件里定義:
typedef struct Node* linkList;
struct Node // 鏈表節(jié)點(diǎn)
{
int data; // 存儲(chǔ)的整型數(shù)據(jù)
linkList next; // 指向下一個(gè)鏈表節(jié)點(diǎn)
};
鏈表有了,下面就是你想要實(shí)現(xiàn)的一些鏈表的功能,當(dāng)然是定義成函數(shù)。我們只舉幾個(gè)常用功能:
linkList initialLinklist(); // 初始化鏈表
link newLinkList (int data); // 建立新節(jié)點(diǎn)
void insertFirst(linkList h,int data); // 在已有鏈表的表頭進(jìn)行插入節(jié)點(diǎn)操作
void linkListOutput(linkList h); // 輸出鏈表中數(shù)據(jù)到控制臺(tái)
這些都是再自然不過(guò)的 C 語(yǔ)言的編程過(guò)程,然后我們就可以在 linkList.c 文件中實(shí)現(xiàn)上述兩個(gè)函數(shù),繼而在 main.c 中調(diào)用它們了。
然而上面我們定義的鏈表還只能對(duì)整型數(shù)據(jù)進(jìn)行操作。如果下次你要用到一個(gè)存儲(chǔ)字符串類(lèi)型的鏈表,就只好把上面的過(guò)程重新來(lái)過(guò)。也許你覺(jué)得這個(gè)在原有代碼基礎(chǔ)上做略微修改的過(guò)程并不復(fù)雜,可是也許我們會(huì)不斷的增加對(duì)于鏈表這個(gè)數(shù)據(jù)結(jié)構(gòu)的操作,而需要用鏈表來(lái)存儲(chǔ)的數(shù)據(jù)類(lèi)型也越來(lái)越多,這些都意味著海量的代碼和繁瑣的后期維護(hù)工作。當(dāng)你有了上百個(gè)存儲(chǔ)不同數(shù)據(jù)類(lèi)型的鏈表結(jié)構(gòu),每當(dāng)你要增加一個(gè)操作,或者修改某個(gè)操作的傳入?yún)?shù),工作量會(huì)變大到像一場(chǎng)災(zāi)難。
但是我們可以改造上述代碼,讓它能夠處理你所想要讓它處理的任何數(shù)據(jù)類(lèi)型:實(shí)行,字符型,乃至任何你自己定義的 structure 類(lèi)型。
Void*:萬(wàn)能的指針“掛鉤”
幾乎所有講授 C 語(yǔ)言課程的老師都會(huì)告訴你:“指針是整個(gè) C 語(yǔ)言的精髓所在?!倍阋惨恢本次分羔?,又愛(ài)又恨地使用著它。許多教材都告訴你,int * 叫做指向整型的指針,而 char * 是指向字符型的指針,等等不一而足。然而這里有一個(gè)另類(lèi)的指針家族成員—— void *。不要按照通常的命名方式叫它做指向 void 類(lèi)型的指針,它的正式的名字叫做:可以指向任意類(lèi)型的指針。你一定注意到了“任意類(lèi)型”這四個(gè)字,沒(méi)錯(cuò),實(shí)現(xiàn)多態(tài),我們靠的就是它。
下面來(lái)改造我們的鏈表代碼,在 linkList.h 里,如下:
typedef struct Node* linkList;
struct Node // 鏈表節(jié)點(diǎn)
{
void *data; // 存儲(chǔ)的數(shù)據(jù)指針
linkList next; // 指向下一個(gè)鏈表節(jié)點(diǎn)
};
linkList initialLinklist(); // 初始化鏈表
link newLinkList (void *data); // 建立新節(jié)點(diǎn)
void insertFirst(linkList h, void *data); // 在已有鏈表的表頭進(jìn)行插入節(jié)點(diǎn)操作
void linkListOutput(linkList h); // 輸出鏈表中數(shù)據(jù)到控制臺(tái)
我們來(lái)看看現(xiàn)在這個(gè)鏈表和剛才那個(gè)只能存儲(chǔ)整型數(shù)據(jù)的鏈表的區(qū)別。
當(dāng)你把 Node 結(jié)構(gòu)體里面的成員定義為一個(gè)整型數(shù)據(jù),就好像把這個(gè)鏈表節(jié)點(diǎn)打造成了一個(gè)大小形狀固定的盒子,你定義一個(gè)鏈表節(jié)點(diǎn),程序進(jìn)行編譯的時(shí)候編譯器就為你打造一個(gè)這樣的盒子:裝一個(gè) int 類(lèi)型的數(shù)據(jù),然后裝一個(gè) linkList 類(lèi)型的指針。如果你想強(qiáng)行在這個(gè)盒子里裝別的東西,編譯器會(huì)告訴你,對(duì)不起,盒子的大小形狀并不合適。所以你必須為了裝各種各樣類(lèi)型的數(shù)據(jù)打造出不同的生產(chǎn)盒子的流水線,想要裝哪種類(lèi)型數(shù)據(jù)的盒子,就開(kāi)啟對(duì)應(yīng)的流水線來(lái)生產(chǎn)。
但是當(dāng)你把結(jié)構(gòu)體成員定義為 void *,一切都變得不同了。這時(shí)的鏈表節(jié)點(diǎn)不再像個(gè)大小形狀固定的盒子,而更像一個(gè)掛鉤,它可以掛上一個(gè)任意類(lèi)型的數(shù)據(jù)。不管你需要存儲(chǔ)什么類(lèi)型的數(shù)據(jù),你只要傳遞一個(gè)指針,把它存儲(chǔ)到 Node 節(jié)點(diǎn)中去,就相當(dāng)于把這個(gè)數(shù)據(jù)“掛”了上去,無(wú)論何時(shí)你都可以根據(jù)指針找到它。這時(shí)的鏈表仿佛變成了一排粘貼在墻上的衣帽鉤,你可以掛一排大衣,可以掛一排帽子,可以掛一排圍巾,甚至,你可以并排掛一件大衣一頂帽子一條圍巾在墻上。void * 初露猙獰,多態(tài)離 C 語(yǔ)言并不遙遠(yuǎn)。