關於多態,關於 C

來源:互聯網
上載者:User

前言:關於多態,關於 C

多態 (polymorphism) 一詞最初來源於希臘語 polumorphos,含義是具有多種形式或形態的情形。在程式設計領域,一個廣泛認可的定義是“一種將不同的特殊行為和單個泛化記號相關聯的能力”。然而在人們的直觀感覺中,多態的含義大約等同於“同一個方法對於不同類型的輸入參數均能做出正確的處理過程,並給出人們所期望獲得的結果”,也許這正體現了人們對於多態性所能達到的效果所寄予的期望:使程式能夠做到越來越智能化,越來越便於使用,越來越能夠使設計者透過形形色色的表象看到代碼所要觸及到的問題本質。

作為讀者的你或許對於物件導向編程已有著精深的見解,或許對於多態的方便與神奇你也有了深入的認識。這時候你訝異的開始質疑了:“多態,那是物件導向編程才有的技術,C 語言是面向過程的啊!”而我想說的是,C 語言作為一種程式設計語言,也許並不是為了物件導向編程而設計,但這並不意味著它不能實現物件導向編程所能實現的功能,就比如說,多態性。

在本文中我們使用一個簡單的單鏈表作為例子,展示 C 語言是如何體現多態性的。

回頁首

結構體:不得不說的故事

許多從寫 C 代碼開始,逐漸走向 C++ 的程式員都知道,其實 C++ 裡面的 class,其前身正是 C 語言中的 structure。很多基於 C 語言背景介紹 C++ 的書籍,在介紹到 class 這一章的時候都會向讀者清晰地展示,一個 C 語言裡的 structure 是怎樣逐漸層成一個典型的 C++ class 的,甚至最後得出結論:“structure 就是一個所有成員都公有的類”,當然了,class 還是 class,不能簡單的把它看做一個複雜化了的 structure 而已。

下面我們來看看在 C 語言中定義一個簡單的儲存整型資料的單鏈表節點是怎麼做的,當然是用結構體。大部分人會像我一樣,在 linkList.h 檔案裡定義:

 typedef struct Node* linkList;  struct Node                                       // 鏈表節點 {  int data;                                 // 儲存的整型資料 linkList next;                            // 指向下一個鏈表節點 }; 

 

鏈表有了,下面就是你想要實現的一些鏈表的功能,當然是定義成函數。我們只舉幾個常用功能:

 linkList initialLinklist();                               // 初始化鏈表 link newLinkList (int data);                        // 建立新節點 void insertFirst(linkList h,int data);              // 在已有鏈表的表頭進行插入節點操作 void linkListOutput(linkList h);                           // 輸出鏈表中資料到控制台

 

這些都是再自然不過的 C 語言的編程過程,然後我們就可以在 linkList.c 檔案中實現上述兩個函數,繼而在 main.c 中調用它們了。

然而上面我們定義的鏈表還只能對整型資料進行操作。如果下次你要用到一個儲存字串類型的鏈表,就只好把上面的過程重新來過。也許你覺得這個在原有代碼基礎上做略微修改的過程並不複雜,可是也許我們會不斷的增加對於鏈表這個資料結構的操作,而需要用鏈表來儲存的資料類型也越來越多,這些都意味著海量的代碼和繁瑣的後期維護工作。當你有了上百個儲存不同資料類型的鏈表結構,每當你要增加一個操作,或者修改某個操作的傳入參數,工作量會變大到像一場災難。

但是我們可以改造上述代碼,讓它能夠處理你所想要讓它處理的任何資料類型:實行,字元型,乃至任何你自己定義的 structure 類型。

回頁首

Void*:萬能的指標“掛鈎”

幾乎所有講授 C 語言課程的老師都會告訴你:“指標是整個 C 語言的精髓所在。”而你也一直敬畏著指標,又愛又恨地使用著它。許多教材都告訴你,int * 叫做指向整型的指標,而 char * 是指向字元型的指標,等等不一而足。然而這裡有一個另類的指標家族成員—— void *。不要按照通常的命名方式叫它做指向 void 類型的指標,它的正式的名字叫做:可以指向任意類型的指標。你一定注意到了“任意類型”這四個字,沒錯,實現多態,我們靠的就是它。

下面來改造我們的鏈表代碼,在 linkList.h 裡,如下:

 typedef struct Node* linkList;  struct Node                                       // 鏈表節點 {  void *data;                               // 儲存的資料指標 linkList next;                            // 指向下一個鏈表節點 };  linkList initialLinklist();                             // 初始化鏈表 link newLinkList (void *data);                    // 建立新節點 void insertFirst(linkList h, void *data);         // 在已有鏈表的表頭進行插入節點操作 void linkListOutput(linkList h);                         // 輸出鏈表中資料到控制台

 

我們來看看現在這個鏈表和剛才那個只能儲存整型資料的鏈表的區別。

當你把 Node 結構體裡面的成員定義為一個整型資料,就好像把這個鏈表節點打造成了一個大小形狀固定的盒子,你定義一個鏈表節點,程式進行編譯的時候編譯器就為你打造一個這樣的盒子:裝一個 int 類型的資料,然後裝一個 linkList 類型的指標。如果你想強行在這個盒子裡裝別的東西,編譯器會告訴你,對不起,盒子的大小形狀並不合適。所以你必須為了裝各種各樣類型的資料打造出不同的生產盒子的流水線,想要裝哪種類型資料的盒子,就開啟對應的流水線來生產。

但是當你把結構體成員定義為 void *,一切都變得不同了。這時的鏈表節點不再像個大小形狀固定的盒子,而更像一個掛鈎,它可以掛上一個任意類型的資料。不管你需要儲存什麼類型的資料,你只要傳遞一個指標,把它儲存到 Node 節點中去,就相當於把這個資料“掛”了上去,無論何時你都可以根據指標找到它。這時的鏈表彷彿變成了一排粘貼在牆上的衣帽鉤,你可以掛一排大衣,可以掛一排帽子,可以掛一排圍巾,甚至,你可以並排掛一件大衣一頂帽子一條圍巾在牆上。void * 初露猙獰,多態離 C 語言並不遙遠。

回頁首

實現:你的多態你做主

當你真正開始著手做這個工作的時候,你會發現把資料放入鏈表中的操作和普通的存放 int 類型的鏈表的實現並沒有什麼大的區別,很方便。但是當你要把已經存進去的資料讀取出來的時候,就有一點麻煩了。對於 void * 類型的指標,編譯器只知道它裡面儲存了一個地址,但是關於這個地址裡的資料類型,編譯器是沒有任何概念的。畢竟我們不能指望編譯器什麼都知道,什麼都能替你做好,所以存進去的資料的類型,作為程式員的我們必須清楚的知道,並且在取出這個資料的時候,用這一類型的指標來對 void * 做強制類型轉換。

為了方便的做到這一點,我採取的方法是在 Node 結構體中增加一個標識資料類型的域,並用一個枚舉類型來存放這些資料類型。這時的 linkList.h 如下所示:

 #ifndef LINKLIST_H  #define LINKLIST_H  typedef struct Node* linkList;  enum dataType  {     INT,     DOUBLE,     CHAR,     STRING  };  struct Node                                               // 鏈表節點 {     void *data;                                       // 儲存的資料指標    int dataType;                                     // 儲存資料類型    linkList next;                                    // 指向下一個鏈表節點 };  linkList initialLinklist();                               // 初始化鏈表 linkList newLinkList (void *data, int dataType);          // 建立新節點 void insertFirst(linkList h, void *data, int dataType);   // 在已有鏈表的表頭進行插入節點操作 void linkListOutput(linkList h);                          // 輸出鏈表中資料到控制台 #endif 

 

初始化鏈表,代碼如下:

 linkList initialLinklist()  {  linkList link = (linkList*)malloc(sizeof(*link));     link->data = NULL;     link->dataType = -1;     link->next = NULL;     return link;  } 

 

建立新節點,代碼如下:

 linkList newLinkList (void *data, int dataType)  {     linkList link = (linkList*)malloc(sizeof(*link));     link->data = data;     link->dataType = dataType;     link->next = NULL;     return link;  } 

 

在已有鏈表的表頭進行插入節點操作,代碼如下:

 void insertFirst(linkList h, void *data, int dataType)  {     linkList l = newLinkList(data, dataType);     l->next = h->next;     h->next = l;  } 

 

輸出鏈表中資料到控制台,代碼如下:

 void linkListOutput(linkList h)  {     linkList p = h;     p = p->next;     while(p != NULL)     {       switch(p->dataType)     {        case 0:        {          printf("%4d", *(int*)(p->data));          break;        }        case 1:        {          printf("%4f", *(double*)(p->data));          break;        }        case 2:        {           printf("%4c", *(char*)(p->data));           break;        }        case 3:        {           printf("%s ", (char*)(p->data));           break;        }     }     p = p->next;   }     printf("/n");  } 

 

回頁首

小結

通過上面這個鏈表的小例子,大家可能已經看到了 C 語言的靈活性。這段代碼雖然短並且功能簡單,但是已經實現了多態性。這篇文章的本意並不在於想要告訴大家用 C 實現多態的方法,而多態的含義也無疑是更加廣泛的。這篇文章的初衷其實是基於這樣一點認識:物件導向是一種程式設計思想,而 C 語言則是一種程式設計語言。也許它並不是專門為了物件導向編程而設計,但是這絕不意味著它不能實現物件導向的程式設計。當然以上所展示的這幾個操作,如果是用別的程式設計語言,可能只要寥寥幾行就可以完成,但是 C 語言想告訴我們的是:也許我不擅長,但是並不意味著我做不到。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.