1. 程式人生 > >C語言寫單鏈表的創建、釋放、追加(即總是在最後的位置增加節點)

C語言寫單鏈表的創建、釋放、追加(即總是在最後的位置增加節點)

程序 mage 而不是 自己的 物理 2-66 exit 只為 對數

昨天周末給學妹講了一些指針的知識,本來我對指針就是似懂非懂的狀態,經過昨天一講,我對指針的學習就更深刻了果然給別人講課也是學習的一個方法。加上最近復習數據結構,發現我的博客裏沒有鏈表的博文,所以趁這時候加上一篇。

  在此之前,我們先談一下我要說的一些基本知識:

①函數參數為什麽是雙指針?

  我們先寫一下這麽一個程序:

# 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語言寫單鏈表的創建、釋放、追加(即總是在最後的位置增加節點)