標籤:理由 png gpo 別人 定義 大神 pos 接下來 周末
昨天周末給學妹講了一些指標的知識,本來我對指標就是似懂非懂的狀態,經過昨天一講,我對指標的學習就更深刻了果然給別人講課也是學習的一個方法。加上最近複習資料結構,發現我的部落格裡沒有鏈表的博文,所以趁這時候加上一篇。
在此之前,我們先談一下我要說的一些基本知識:
①函數參數為什麼是雙指標?
我們先寫一下這麼一個程式:
# 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呢?這就是關鍵所在。我總結一下,形參m只是實參a的一個賦值的變數,形參我們都知道是函數調用時候才分配記憶體單元,當函數調用完畢後,形參就會被幹掉了,所以上面程式可以這麼理解:定義一個a變數,它的值為1,當把a作為實參傳進Gai這個函數時,系統會定義一個變數m,並且把a的值“1”賦給了m,然後又執行m=5。所以,到整個程式結束,m=5,a=1,所以a的值根本就沒有發生改變。所以說,在main函數裡,若想通過函數來改變變數的值,那是不可能的。
接下來我們把程式修改一下:
# 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語言寫單鏈表的建立、釋放、追加(即總是在最後的位置增加節點)