展示了單鏈表的基本結構:
head指標是鏈表的頭指標,指向第一個節點,每個節點的next指標域指向下一個節點,最後一個節點的next指標域為NULL,在圖中用0表示。
下面先來看程式(棧的鏈式儲存實現,另外一個實現點這裡)和對應的輸出(注意輸出前進行了鏈表反轉(見《單鏈表反轉》,否則程式後面的while迴圈輸出的順序是250,200,100),接著來剖析器:
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
|
/* linkedlist.h */ #ifndef LINKEDLIST_H #define LINKEDLIST_H typedef struct node *link; struct node { unsigned char item; link next; }; link make_node(unsigned char item); void free_node(link p); link search(unsigned char key); void insert(link p); void deletep(link p); void traverse(void (*visit)(link)); void reverse(void); void destroy(void); void push(link p); link pop(void); #endif |
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
|
|
/* linkedlist.c */ #include <stdlib.h> #include <stdio.h> #include "linkedlist.h"static link head = NULL; link make_node(unsigned char item) { link p = malloc(sizeof(*p)); p->item = item; p->next = NULL; printf("make node from Item %d\n", item); return p; } void free_node(link p) { printf("free node ...\n"); free(p); } link search(unsigned char key) { link p; printf("search by key %d\n", key); for (p = head; p; p = p->next) if (p->item == key) return p; return NULL; } void insert(link p) { printf("insert node from head ...\n"); p->next = head; head = p; } /* void delete(link p) { link pre; printf("delete node from ptr ...\n"); if (p == head) { head = p->next; return; } for (pre = head; pre; pre = pre->next) if (pre->next == p) { pre->next = p->next; return; } } */ void deletep(link p) { link *pnext; printf("delete node from ptr ...\n"); for (pnext = &head; *pnext; pnext = &(*pnext)->next) if (*pnext == p) { *pnext = p->next; return; } } void traverse(void (*visit) (link)) { link p; printf("linkedlist traverse ...\n"); for (p = head; p; p = p->next) visit(p); } void reverse(void) { link pnode = head; link pprev = NULL; printf("reverse linkedlist ...\n"); while (pnode != NULL) { // get the next node, and save it at pNext link pnext = pnode->next; // reverse the linkage between nodes pnode->next = pprev; // move forward on the the list pprev = pnode; pnode = pnext; } head = pprev; } void destroy(void) { link q, p = head; printf("destory linkedlist ...\n"); head = NULL; while (p) { q = p; p = p->next; free_node(q); } } void push(link p) { printf("push item from head ...\n"); insert(p); } link pop(void) { if (head == NULL) return NULL; else { link p = head; printf("pop item from head ...\n"); head = head->next; return p; } } |
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
|
|
/************************************************************************* > File Name: main.c > Author: Simba > Mail: dameng34@163.com > Created Time: Fri 28 Dec 2012 01:22:24 PM CST ************************************************************************/#include<stdio.h> #include "linkedlist.h" void print_item(link p) { printf("print item %d \n", p->item); } int main(void) { link p = make_node(10); insert(p); p = make_node(5); insert(p); p = make_node(90); insert(p); p = search(5); deletep(p); free_node(p); traverse(print_item); destroy(); printf("..................\n"); p = make_node(100); push(p); p = make_node(200); push(p); p = make_node(250); push(p); reverse();//鏈表反轉
while ((p = pop())) { print_item(p); free_node(p); } return 0; } |
輸出為:
分析:
在初始化時把頭指標head初始化為NULL,表示空鏈表(不帶頭結點)。然後main函數調用make_node建立幾個節點,分別調用insert插入到鏈表中。
鏈表的插入操作如:
正如所示,insert函數雖然簡單,其中也隱含了一種特殊情況(Special Case)的處理,當head為NULL時,執行insert操作插入第一個節點之後,head指向第一個節點,而第一個節點的next指標域成為NULL,這很合理,因為它也是最後一個節點。所以空鏈表雖然是一種特殊情況,卻不需要特殊的代碼來處理,和一般情況用同樣的代碼處理即可,這樣寫出來的代碼更簡潔,但是在讀代碼時要想到可能存在的特殊情況。當然,insert函數傳進來的參數p也可能有特殊情況,傳進來的p可能是NULL,甚至是野指標,本章的函數代碼都假定調用者的傳進來的參數是合法的,不對參數做特別檢查。事實上,對指標參數做檢查是不現實的,如果傳進來的是NULL還可以檢查一下,如果傳進來的是野指標,根本無法檢查它指向的記憶體單元是不是合法的,C標準庫的函數通常也不做這種檢查,例如strcpy(p,
NULL)就會引起段錯誤。
接下來main函數調用search在鏈表中尋找某個節點,如果找到就返回指向該節點的指標,找不到就返回NULL。
search函數其實也隱含了對於空鏈表這種特殊情況的處理,如果是空鏈表則迴圈體一次都不執行,直接返回NULL。
然後main函數調用delete從鏈表中摘除用search找到的節點,最後調用free_node釋放它的儲存空間。
鏈表的刪除操作如:
從可以看出,要摘除一個節點需要首先找到它的前趨然後才能做摘除操作,而在單鏈表中通過某個節點只能找到它的後繼而不能找到它的前趨,所以刪除操作要麻煩一些,需要從第一個節點開始依次尋找要摘除的節點的前趨。delete操作也要處理一種特殊情況,如果要摘除的節點是鏈表的第一個節點,它是沒有前趨的,這種情況要用特殊的代碼處理,而不能和一般情況用同樣的代碼處理。這樣很不爽,能不能把這種特殊情況轉化為一般情況呢?可以把delete函數改成上述程式那樣:
消除特殊情況的鏈表刪除操作如:
定義一個指向指標的指標pnext,在for迴圈中pnext遍曆的是指向鏈表中各節點的指標域,這樣就把head指標和各節點的next指標統一起來了,可以在一個迴圈中處理。(其實增加一個前端節點也可以消除delete的特殊情況《線性表的鏈式儲存結構》)
然後main函數調用traverse函數遍曆整個鏈表,調用destroy函數銷毀整個鏈表。
參考:《linux c 編程一站式學習》