在寫鏈表的代碼時候遇到的一個問題,首先鏈表節點的結構體定義爲
typedef int ElementType;
typedef struct Node *PtrToNode;
struct Node {
ElementType Data;
PtrToNode Next;
};
typedef PtrToNode List;
然後在實現一個鏈表合併(Merge)的功能時,函數的實現如下,在這個函數執行的過程中,在函數體最後明確將L1/L2(鏈表的頭指針)都置爲了NULL,但是返回主函數中的L1/L2卻仍然是函數傳參前的L1/L2的值,百思不得其解,明明我傳的是指針啊?
List Merge( List L1, List L2 )
{
// 新鏈表的頭結點
List head = (List)malloc(sizeof(struct Node));
List node = head;
// 直接用L1/L2遍歷鏈表,先讓L1/L2指向首節點
L1 = L1->Next;
L2 = L2->Next;
while(L1 && L2)
{
if(L1->Data >= L2->Data)
{
node->Next = L2;
node = L2;
L2 = L2->Next;
}
else
{
node->Next = L1;
node = L1;
L1 = L1->Next;
}
}
// L2結束,L1還未結束
if(L1)
{
node->Next = L1;
}
// L1結束,L2還未結束
if(L2)
{
node->Next = L2;
}
L1 = NULL;
L2 = NULL;
return head;
}
想了一會兒想不明白,然後就寫了個簡單的程序試試,程序如下(VS2015),func的參數爲int類型指針,實參爲指向a的指針ptr,然後調試運行一下
#include <stdio.h>
#include <stdlib.h>
void func(int* p);
int main()
{
int a = 2;
int* ptr = &a;
func(ptr);
printf("%d\n", *ptr);
system("pause");
return 0;
}
void func(int** p)
{
p = NULL;
}
這裏看到,ptr和p的值是相同的,這裏應該看出來一些端倪了,即p是ptr的一份拷貝,而並不是ptr本身,這裏我們可以再取地址看一下這兩個變量是不是同一個,程序如下
#include <stdio.h>
#include <stdlib.h>
void func(int* p);
int main()
{
int a = 2;
int* ptr = &a;
int** pptr = &ptr;
func(ptr);
printf("%d\n", *ptr);
system("pause");
return 0;
}
void func(int* p)
{
int** pp = &p;
p = NULL;
}
pptr和pp分別是指向ptr和p的指針,也就是ptr和p的地址,上面兩圖可以看出,pptr和pp並不是同一個值,也即ptr和p不在內存的同一位置,ptr和p不是同一個東西,所以在函數中修改p=NULL只是把ptr的一個拷貝改成了NULL,而沒有改變main函數中的ptr,所以返回之後ptr仍然指向2。
但是如果再將程序修改一下,將func的參數改爲int** p,再次運行,程序如下
#include <stdio.h>
#include <stdlib.h>
void func(int** p);
int main()
{
int a = 2;
int* ptr = &a;
func(&ptr);
printf("%d\n", *ptr);
system("pause");
return 0;
}
void func(int** p)
{
*p = NULL;
}
這個時候再來看,可以看到func函數已經將ptr置爲了NULL,同時func中的p也是指向了ptr,所以這時才修改了main函數中的ptr,有點繞~
然後回到鏈表合併的函數
List Merge( List L1, List L2 )
{
// 新鏈表的頭結點
List head = (List)malloc(sizeof(struct Node));
List node = head;
// 直接用L1/L2遍歷鏈表,先讓L1/L2指向首節點
L1 = L1->Next;
L2 = L2->Next;
while(L1 && L2)
{
if(L1->Data >= L2->Data)
{
node->Next = L2;
node = L2;
L2 = L2->Next;
}
else
{
node->Next = L1;
node = L1;
L1 = L1->Next;
}
}
// L2結束,L1還未結束
if(L1)
{
node->Next = L1;
}
// L1結束,L2還未結束
if(L2)
{
node->Next = L2;
}
L1 = NULL;
L2 = NULL;
return head;
}
同理List是鏈表節點結構體的指針
typedef struct Node *PtrToNode;
struct Node {
ElementType Data;
PtrToNode Next;
};
typedef PtrToNode List;
在傳遞時雖然傳入了鏈表的頭指針,但在Merge函數中的L1卻只是頭指針的一個拷貝,所以最後的代碼L1=NULL並不會修改main函數中的鏈表頭指針,所以代碼可以修改如下,雖然L1是頭指針的拷貝,但是L1->Next卻是指向首節點的指針(這個指針與main函數中的指針是相同的),將這個指針置爲NULL,可以改變main函數中的L1->Next
List Merge(List L1, List L2) {
// 新鏈表的頭結點
List head = (List)malloc(sizeof(struct Node));
List node = head;
// 直接用L1/L2遍歷鏈表,先讓L1/L2指向首節點
List p1, p2;
p1 = L1->Next;
p2 = L2->Next;
while (p1 && p2)
{
if (p1->Data >= p2->Data)
{
node->Next = p2;
node = p2;
p2 = p2->Next;
}
else
{
node->Next = p1;
node = p1;
p1 = p1->Next;
}
}
// L2結束,L1還未結束
if (p1)
{
node->Next = p1;
}
// L1結束,L2還未結束
if (p2)
{
node->Next = p2;
}
L1->Next = NULL;
L2->Next = NULL;
return head;
}
也可以用一幅圖來表示這兩個指針的關係