C語言寫單鏈表的創建、釋放、追加(即總是在最後的位置增加節點)
昨天周末給學妹講了一些指針的知識,本來我對指針就是似懂非懂的狀態,經過昨天一講,我對指針的學習就更深刻了果然給別人講課也是學習的一個方法。加上最近復習數據結構,發現我的博客裏沒有鏈表的博文,所以趁這時候加上一篇。
在此之前,我們先談一下我要說的一些基本知識:
①函數參數為什麽是雙指針?
我們先寫一下這麽一個程序:
# include<stdio.h>
void Gai(int m)
{
m=5;
}
int main(void)
{
int a=1;
Gai(a);
printf("%d\n",a);
return 0;
}
那麽我們可以得知,輸出a的值是1,為什麽我調用了函數,把a傳進去,並沒有變成5呢?這就是關鍵所在
接下來我們把程序修改一下:
# include<stdio.h>
void Gai(int * m)
{
*m=5;
}
int main(void)
{
int a=1;
Gai(&a);
printf("%d\n",a);
return 0;
}
通過運行後我們可以看到a的值此時變成了“5”。所以,我們可以總結:
若一個變量想通過函數來改變本身的值,將本身作為參數傳遞是不成功的,只有傳遞本身的指針(地址)才能達到這樣的效果。
所以後面我們創建鏈表時,傳遞的是雙指針,這就是為什麽參數是雙指針的原因。
因為我之前也一直不明白,直到我昨天給學弟學妹們講課的時候,我才恍然大悟,所以我也算很笨了,所以在這裏給大家總結一下,因為我在別的博客裏,看到也有挺多人不理解為什麽是雙指針,現在希望讀者們可以理解。
②每一個變量的內存單元中都有自己的地址
為了好理解,我畫圖把它綁在一塊,雖然可能物理結構上不是長這樣,但邏輯上是長這樣的,比如
int a=2;
int * p = &a;
所以說,只要是變量它都會有自己的地址(指針),即使是指針變量。
然後,指針它就是用來存地址的,只有兩部分,一部分是附帶自己的地址,一部分是存別人的地址
③指針就是地址,地址就是指針,指針類型的變量它的值只用來裝指針。
為什麽我會說這句話呢。因為之前,在昨天為止,我那麽久,居然一直都理解錯了,也怪我太笨了哈哈。比如說定義了節點類型
typedef struct n
{
int data; //數據域
struct n * next; //指針域
} Node;
然後 Node * L; 我一直以為L是長這樣子的
原來不是!!! 它不是!! 害死我了,以前我可糾結了好久了!!!太蠢了哈哈!!原來我一直以為什麽類型的指針就長什麽樣!!
不是的,其實它是什麽類型的指針它就存什麽樣的地址。而不是長成那樣,所以L其實是長這樣的:
總之這個坑,如果你們已經會的,可以笑一下我,如果也一樣像我一樣掉坑的,希望看到這裏後能及時填坑。這個大大大大大的坑,嗨呀,氣死了。都怪以前沒認真學指針。
以上就是今天的預備知識,接下來就開始學習單鏈表的簡單操作了。我會用圖來結合,因為我一直強調圖和代碼結合,這樣才能學好數據
結構,這樣才能對數據結構有形象的想法,當然大神都是直接理解的,我高攀不起。我比較菜,就挖掘了自己的學習方法,嘿嘿。
單鏈表我采用了頭指針和頭結點的結構。
這次單鏈表的操作可能有些不一樣,但原理都是一樣的,或者說,把圖理解了,代碼也就理解了;
/* 參數:頭指針的指針(雙指針) 作用:初始化鏈表,使頭指針指向一個新結點, 這個新節點就是頭結點 */ void InitHead(Node * *pHead) /* 參數:頭指針,其實也是頭指針的拷貝 作用:釋放整個鏈表 */ void Free_list(Node * pHead) /* 參數:頭指針,一個值 作用:往鏈表的末段追加val */ void append(Node * pHead,int val) /* 參數:頭指針 作用:遍歷輸出鏈表(跳過頭結點) */ void Showlist(Node * pHead)
(一)初始化鏈表
void InitHead(Node * *pHead) //為鏈表生成頭結點 使頭指針指向頭結點
{
*pHead = (Node *)malloc(sizeof(Node));
if(*pHead == NULL)
{
printf("頭結點分配失敗,程序終止! \n");
exit(-1);
}
(*pHead)->next=NULL;
}
在main函數裏面定義: Node * L = NULL; //定義一個指針,指向Node類型,其實也就是整個鏈表的頭指針
然後調用 InitHead(&L);
圖解如下:
*pHead = (Node *)malloc(sizeof(Node));
//其實*PHead就是頭指針L的值了,加*號就代表指針的值,malloc會申請一個結點,然後返回結點的首地址,其實這個新生成的結點是沒有名字的,為了方便
//理解,我們管它叫x 圖解如下:
至於PHead?哈哈,等這個函數結束後,它就被會幹掉了,所以到頭來,它只為他人作了嫁衣,不過這也正是它存在的意義。
如果傳遞的是單指針的話,pHead作為盜版的頭指針指向了那個新生成的結點,然後函數結束後,它們的狀態分別是:L 依然存在,什麽也沒變化。 pHead,被幹掉,徹徹底底的沒了,至於新生成的結點,則是孤零零的在內存區裏瑟瑟發抖,等待有緣人來指向它,所以這就是為什麽要用雙指針的理由。用了雙指針,L指向了新生成的結點,PHead被幹掉,皆大歡喜。
(二)釋放鏈表
void Free_list(Node * pHead) //釋放鏈表
{
Node * p;
while(pHead != NULL)
{
p = pHead;
pHead = pHead->next;
free(p);
p = NULL;
}
}
因為在鏈表中生成的新節點是用malloc的,所以要用free把它回收,malloc和free是一夫一妻。
在這種小程序或許不free也沒什麽太大的問題,但以後做項目時如果不回收就麻煩大了,所以養成free的習慣。
圖解如下:
之後p就會變成 然後free(p)就是把p指向的那個結點,也就是圖中的頭結點,給幹掉,
而pHead也被函數結束後幹掉,而L只拿著一個head的地址但卻找不到人了;
這裏的圖是只有一個頭結點時的釋放,但即使有多個結點,也是一樣的做法,你們可以自己畫圖模擬一下,加深記憶。
(三)向鏈表末端追加元素
void append(Node * pHead,int val)
{
Node * r=pHead;
Node * pNew = (Node *)malloc(sizeof(Node)); //生成新節點
if(pNew == NULL)
{
printf("新節點分配失敗,程序終止! \n");
exit(-1);
}
pNew->data=val;
pNew->next=NULL;
while(r->next != NULL) //讓尾指針循環直到最後一個節點
{
r=r->next;
}
r->next=pNew;
r=pNew;
}
這個代碼太長,有點難畫圖,我盡量吧,圖示如下:
然後定義一個指針r,把pHead復制過去
為了方便理解,我把所有新生成的結點都叫做x。
然後定義一個PNew指針指向新生成的結點x;然後賦值,並置為NULL
其實pNew-<data就是x.data
然後這一句代碼
while(r->next != NULL) //讓尾指針循環直到最後一個節點
{
r=r->next;
}
這是為了讓r指針指向最後一個結點,
為什麽是r->next != NULL 而不是r!=NULL;這裏是有區別的,因為我這種追加的方法是屬於後插法。總之就是根據圖來寫代碼
r->next是這個
而r是這個
所以判斷的時候,應該判斷的是鏈表中的next;而不是判斷r有沒有指向誰;
接下來就是最後的連起來了。
r->next=pNew;
r=pNew;
r->next=pNew;//為什麽這裏它們明明是指向了PNew,但圖中卻指向x呢?我這麽理解不懂對不對,一個指針指向了一個結點,那麽這個指針就相當於這個結點了
然後最後一個
(四)遍歷輸出鏈表
void Showlist(Node * pHead)
{
pHead=pHead->next; //跳過頭結點輸出
while(pHead!=NULL)
{
printf("%d ",pHead->data);
pHead=pHead->next;
}
}
最後一個就不畫圖了,相信大家也能看懂。
至此,整篇文章應該寫完了。嘖嘖嘖嘖,去吃飯了。
有錯請在下方評論,咱們一起進步。
謝謝~
忘了貼完整代碼了,測試你們自己測試一下,我這裏測試結果無誤
# include<stdio.h> # include<stdlib.h> typedef struct n { int data; //數據域 struct n * next; //指針域 } Node; void InitHead(Node * *pHead) //為鏈表生成頭結點 使頭指針指向頭結點 { *pHead = (Node *)malloc(sizeof(Node)); if(*pHead == NULL) { printf("頭結點分配失敗,程序終止! \n"); exit(-1); } (*pHead)->next=NULL; } void Free_list(Node * pHead) //釋放鏈表 { Node * p; while(pHead != NULL) { p = pHead; pHead = pHead->next; free(p); p = NULL; } } void append(Node * pHead,int val) { Node * r=pHead; Node * pNew = (Node *)malloc(sizeof(Node)); //生成新節點 if(pNew == NULL) { printf("新節點分配失敗,程序終止! \n"); exit(-1); } pNew->data=val; pNew->next=NULL; while(r->next != NULL) //讓尾指針循環直到最後一個節點 { r=r->next; } r->next=pNew; r=pNew; } void Showlist(Node * pHead) { pHead=pHead->next; //跳過頭結點輸出 while(pHead!=NULL) { printf("%d ",pHead->data); pHead=pHead->next; } } int main(void) { Node * L = NULL; InitHead(&L); append(L,1); append(L,4); append(L,7); append(L,9); append(L,332); append(L,6); append(L,235); Showlist(L); Free_list(L); L=NULL; return 0; }
C語言寫單鏈表的創建、釋放、追加(即總是在最後的位置增加節點)