聲明: 如下內容可能會引發某些讀者不適, 請當心閱讀. 有些內容並無詳細介紹, 可能簡單理解也是錯誤的, 可是這都是爲了儘可能簡單。node
前言: 我本身認爲, 對想要學習編程的人而言, C語言是一門必需要學習的語言, 可是其實就如今這個時代的話, 你沒有學習C語言的絕對必要。使用別的高級語言(好比python)站在巨人的肩膀上很好, 也很爽。python
推薦書目:linux
鏈表是一種頗有意思的數據結構,他是經過動態的申請內存和釋放內存來獲得的,經過鏈表的學習,我以爲能夠強化咱們對指針的理解,若是指針學的並不透徹的話。
git
單鏈表的每一個節點裏面有一個數據段和指針段。對比的看,int類型的變量只能存儲int,而鏈表節點是結構體(通常狀況下是), 是一種複合結構。 看下圖:
程序員
鏈表節點的結構定義:github
1
2
3
4
5
|
struct link_node
{
void *data;
struct link_node *next;
}
|
在這個鏈表節點的結構定義中,有一個空指針, 也有一個指向鏈表節點類型結構體的指針。編程
1
2
3
4
5
6
|
struct link
{
struct link_node *head;
int len;
struct link_node *tail;
};
|
在這個鏈表的定義中有頭尾指針, 還有鏈表長度。數據結構
1
2
3
4
5
|
void link_init(struct link *l)
{
l->head = l->tail = NULL;
l->len = 0;
}
|
咱們在這裏接受鏈表結構體指針。經過->訪問符, 訪問到指針所指向的結構體對象的各個段。包括頭指針段,尾指針段,長度段,對它們的值進行了初始化。函數
增長鏈表節點, 這裏是在鏈表後面增長節點的方法:學習
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
void link_addnode(struct link *l, struct link_node *node)
{
if (l->head == NULL)
{
l->head = node;
l->tail = node;
l->len = 1;
}
else
{
node->next = l -> tail->next;
l->tail->next = node;
l->tail = node;
}
}
|
刪除鏈表中的某個節點:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
void link_rmnode(struct link *l, struct link_node *node,
void (*link_node_free)(struct link_node *node))
{
assert(l != NULL && node != NULL);
if (node == l->head) // 若是要刪除第一個節點
{
l->head = l->head->next; //將頭結點設置爲未刪除時的第二個節點
link_node_free(node); // 釋放第一個節點佔用的內存
}
else // 不然的話
{
struct link_node *tmp = l->head; // 設置一個臨時指針指向鏈表頭結點
while (tmp->next != NULL && tmp->next != node) { tmp = tmp->next; } // 當臨時指針的下一個節點不是node節點的話, 就向後移動指針。
if (tmp == l->tail) { return; } // 若是tmp到告終尾,說明node並不在l中
tmp->next = node->next; // 從鏈表中刪除node
link_node_free(node); // 釋放node節點佔用
}
}
|
咱們在這個函數的參數中加入了一個函數指針, 這個指針負責節點的內存釋放, 由於咱們每一個結構體的內部還有一個data指針, 因此咱們不能簡單的把結構體指針直接free掉,而是應該使用本身寫的對應的link_node_free函數。
查找鏈表中的某個節點:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
struct link_node* link_findnode(struct link *l, void *value, int value_size,
int(*find)(struct link_node *l1, void *value, int value_size))
{
struct link_node* p = l->head;
int found = 0;
while (p != NULL)
{
if (find(p, value, value_size))
{
found = 1;
break;
}
else
{
p = p->next;
}
}
return (found) ? p : NULL;
}
|
這個函數的參數須要一個被查找的鏈表,須要一個查找憑據的指針,須要知道這個憑據的長度,還須要本身設置一個查找函數並傳入。
第四行,咱們設置了一個鏈表節點指針p使他指向鏈表頭結點。
第五行,咱們設置了一個標誌值,表示有沒有查找到節點。
而後咱們用一個循環來在每一個節點中查找,若是找到就設置標誌值而且退出循環。
最後咱們返回查找結果。
單鏈表只能向一個方向進行移動,而雙向鏈表能夠向兩個方向移動。
這裏是雙向鏈表節點的定義:
1
2
3
4
5
|
struct link_node
{
void *data;
struct link_node *next, *prev;
}
|
這裏是雙向鏈表的結構定義:
1
2
3
4
5
|
struct link
{
struct link_node* head, *tail;
int len;
};
|
雙向鏈表的初始化:
1
2
3
4
5
|
void link_init(struct link *l)
{
l->head = l->tail = NULL;
l->len = 0;
}
|
鏈表是一種特別的鏈式數據結構,他經過C語言提供的指針功能來實現數據之間的連接。由於指針能夠達到間接訪問的目的,因此當使用malloc獲得一塊內存,而且進行合理類型轉換以後,咱們就能夠用指針來對這塊內存進行訪問。固然,咱們要承擔對相應內存的釋放工做。程序員至少要知道本身在作什麼,這是C語言要求的。
注意:
指針指向一塊內存(指針變量的值存儲那塊內存的首地址),而後你就能夠經過\r解引用符號對那塊內存進行操做(像這樣: (pointer)->member_var),你也能夠經過這樣的方法 pointer->member_var 來訪問pointer所指向的內存空間裏面 member_var 所對應的內存區域。指針變量p存儲了a的地址,那麼*p就能夠被看成是a使用,同理,若是p存儲的是結構體變量的地址,那麼*p就能夠當作是哪一個結構體變量來使用。
而malloc這個函數能夠返回一片你指定大小的內存的首地址,而若是沒有供你使用的大小的內存存在的時候若是你是很是新的小白,你可能還不懂這究竟是怎麼回事。但這並非我所關心的。