1. 程式人生 > >連結串列中的指標

連結串列中的指標

中期答辯改在了國慶之後,終於有時間可以看看劍指offer了。在看到單向連結串列的部分,對指標,尤其是頭指標有點疑惑。首先容易理解的是連結串列的節點是一個結構體,該結構體包含一個數據(一般是int型),還包含一個指向該結構體型別的指標。通過指標的指向一個個遍歷,也是通過指標一次次分配記憶體。這使得連結串列不同於陣列,連結串列中的記憶體不是連續的,我們想要訪問一個結點只能從頭結點開始。其實陣列之所以能通過陣列下標進行訪問,也是因為事先設定了一個基準點。只不過在連結串列中這個基準點(頭指標)很重要。現在就來好好研究一下頭指標、頭結點的那些事。

在書中提供的程式碼中,無論是在連結串列末尾插入新的結點,還是在連結串列中刪除具有某值的結點,都有兩個共同點。一是函式的第一個引數型別是指向結點資料型別的指標的指標,一是將程式碼分為兩部分,第一部分都是通過頭指標是否是空指標判斷該連結串列是否是空連結串列,或者判斷頭結點中的資料是否和引數相等。這裡提到了兩個重要的概念,頭指標和頭結點。

struct ListNode
{
	int value;
	ListNode * Next;

};

void addTotail(ListNode ** head, int value){//第一個引數是頭指標的指標,因為頭指標在變
	ListNode* newlistnode = new ListNode();
	newlistnode->value = value;
	newlistnode->Next = NULL;//新建一個節點,資料等於引數,指標為空
	if (*head == NULL){//頭指標,指向下一個節點
		*head = newlistnode;//空連結串列中插入節點,則頭指標不再是空指標,第一個形參應改變
	}
	else {
		//連結串列不空時,遞迴直到連結串列尾(空指標)
		ListNode* pNode = *head;
		while (pNode->Next != NULL){
			pNode = pNode->Next;
		}
		pNode->Next = newlistnode;
	}
}

可以看到空指標相對於陣列名,是連結串列的名字,它是指向下一個結點的指標的指標,至於為什麼是雙重指標,這涉及到函式中引數值的傳遞。因為在連結串列中插入我們要討論該連結串列是否是空連結串列,進行不同的操作,而插入這個操作會使得空連結串列不再空,即使得原來空連結串列的頭指標指向空指標變成了指向一個真實的結構體,我們需要在執行完一次函式後修改頭指標這個形參,方便之後再次呼叫這個函式。

函式中值的傳遞方式有三種:值傳遞,地址傳遞,引用傳遞。

值傳遞,只傳遞了形參的值,因為在函式內部會新建值等於引數的副本(形參),函式中的操作只是對副本進行修改,不會改變實參的原始大小。實參是主函式呼叫函式時傳遞的值,形參是函式定義時使用的值。一個很形象的例子就是餐廳中有保鮮膜包好的樣品菜,你指給廚師說要菜A,廚師會拿新的食材重新做一份,而不是直接把樣品給你。如果真的想要樣品中的菜,那麼就要藉助裝樣品的盤子了,盤子就相當於地址,這就是地址傳遞。Java中只有按值傳遞,這也是java簡單的原因。

引用傳遞。首先搞清楚引用是怎麼回事,引用是兩個變數名錶示同一個東西,不會分配新的記憶體,可以認為引用是目標變數的一個別名。通過引用修改變數勢必引起原始變數發生變化。

下面說一下今天遇到的主角,指標傳遞。這裡更特殊的是使用了雙重指標。這裡有一句比較容易引起混淆的話:其實一切傳遞的方式本質都是按值傳遞。簡單來說,雙重指標通過傳遞地址變數和地址指標指向的內容實現了可以通過函式對引數的地址和內容值進行修改。

先看下面一個程式碼,程式碼中第一個引數是指向int型的指標,傳入a的地址。可以理解為&a是按值傳遞的,這樣在執行函式時會有一個a的地址的拷貝,這個拷貝同樣指向a,這樣當我們在函式中修改拷貝中的內容,即便這個拷貝最後被釋放,還是會改變a的值。

void change(int *_a,int &b){
*_a=b;
}
void main(){
int a=10,b=20;
int *p_a=&a;
change(p_a,b);
printf("%d",*p_a);
}

既然單指標作為形參已經可以實現通過函式修改實參,為什麼還要使用雙重函式呢?可以注意到我們剛才把a的地址當做按值傳遞,而函式目標是修改按值傳遞的地址指向的內容,但是當我們函式的目的是修改地址本身時,又回到了按值傳遞的老路上,這樣是不能實現目的的。借鑑剛才的通過指標改變指標指向的內容,我們再加一層指標,指向變數的地址,即指標的指標,這就有了雙重指標。

再說回頭指標和頭結點。頭指標是必要的,頭結點更多是為了操作統計和方便,其資料域一般無意義,可以用作監視哨或者存放連結串列的長度。程式碼中,只要頭指標運用了next操作就自動建立了頭結點。再從表示的意義理解一下雙重指標ListNode ** pHead,它是指向結構體ListNode的指標的指標,那麼*pHead表示的就是指向ListNode的一個指標。

Reference: