- 單鏈表的存儲結構
typedef struct LNode
LNode L: L是結構體LNode實例化的實體,可以用.運算符來訪問結構體成員,即L.elem。
LinkList L: L是指向定義的LNode結構體的指針,可以用->運算符來訪問結構體成員,即L->elem,而(*L)就是個Node型結構體的實體了,可以用點運算符訪問該結構體成員,即(*L).elem;
LinkList *L: L是指向定義的LNode結構體指針的指針,所以(*L)是指向LNode結構體的指針,可以用->運算符來訪問結構體成員,即(*L)->elem,當然,(**L)就是LNode型結構體的實體了,所以可以用點運算符來訪問結構體成員,即(**L).elem;
在鏈表操作中,我們常常要用鏈表變量作為函數的參數,這時,用LinkList L還是LinkList *L就很值得考慮深究了,一個用不好,函數就會出現邏輯錯誤,其准則是:
1. 如果子函數會改變指針L的值,而你也希望子函數結束調用后保存L的值(因子函數運行結束后,所有形參,含L,會被釋放),那你就要用LinkList *L的形式傳遞參數。這樣,向子函數傳遞的就是指針的地址,結束調用后,自然就可以去改變實參指針變量的值,讓它指向新的實體;
2. 如果子函數只會修改指針所指向的內容,而不會更改指針變量的值(實體的地址),那么用LinkList L就行了。實質是,子函數修改的是形參指針 指向的內容Lnode,注意到 實參指針也指向Lnode,也就是子函數修改的就是實參指向的內容Lnode,形參指針隨着子函數結束消失不影響 子函數的功能,就是要修改實參 后面指向的內容,而不是實參指針的值。
- 初始化的鏈表T,子函數調用完畢后,L會指向一個空的鏈表,即會改變指針L的值,所以要用*L
- int InitList1(LinkList *L) {
*L = (LinkList)malloc(sizeof(Node));
if(!(*L)){return 0;}
(*L)->next = NULL;
return 1;
}
- 清空鏈表L,使L重新變為空鏈表,子函數調用完后不會改變指針L的值,只會改變指針L所指向的內容(即L->next的值)
2. void ClearList(LinkList L) {
LinkList p;
- 銷毀鏈表L,釋放鏈表L申請的內存,使L的值重新變為NULL,所以會改變L的值,得用*L
LinkList p;
while(p = (*L)->next )
free(p);
free(*L);
*L = NULL;
}
- 主函數中調用
void main()
{
LinkList T = NULL;
InitList(&T);
ClearList(T);
DestroyList(&T);
}
有同學問,老師在未了解前也有同樣疑問,鏈表初始化子函數中,形參 LinkList L,L本身就已經是指針了,為何初始化還要這里為何還要定義指針的指針 LinkList *L,何必多此一舉呢,我先把初始化代碼改成如下:
4. int InitList2(LinkList L)
L = (LinkList)malloc(sizeof(Node));// 其實只是修改指針型形參L的值(它的值是某個地址);也即: 指針型變量L,其值放的是新地址,把傳遞過來 實參的地址 覆蓋了。
if(!L){return 0;}
L->next = NULL;
return 1;
}
雖然能通過編譯,但是執行的時候卻是一串亂碼,反復思考,得出原因如下,給初學者一些幫助
C語言的函數參數是值調用(指針也是變量,只不過其值是地址),一定要記着!!!
例如:在main函數中有如下代碼
int main()
{
LinkList T;//T只是聲明,並未創建
int i;
i=InitList1(T);//目的是初始化創建T,正常情況下通過調用子函數InitList(T)即可;此處賦值給i,只是幫我們根據返回的值理解T傳入子函數的值到底是什么
printf("初始化L后:%d\n",i);
}
正常的話,子函數1執行完,鏈表T應被初始化,打印出i的值應是1,因為InitList 返回1了。這本是子函數應實現的功能,也是正確的。
分析:主函數定義了T,它是個指向 LNode節點型數據的指針,當然也是個變量,那么不妨假設它的值是系統隨機分配的一地址1000H(理論上指針變量必須引用到某一實體,即指向一實體變量,才有后續).
如果用第二種初始化方法這是調用InitList2函數,傳入T的值,此時L = 1000H, 接下來就和T沒有任何關系了,這時候給L又指向了一個節點(L的值為該節點的地址),然后節點的指針域有 L->next == NULL,返回1。
需要注意的是:子函數2確實初始化了一個鏈表L, 但L是形參,隨着子函數2的結束,所有形參,包括L也會隨之消失。
但T呢,還是剛剛在主函數里定義的一個指針而已,還是那個 隨機分配的的地址(1000H),並沒有真正的指向剛才創建的節點。
在看看第一種初始化方法方法,傳入指針的指針,也就是:形參變量L(本身是指針變量)指向實參變量T的地址。
主函數定義了T,它是個指針變量,假設它的值為1000H(聲明時隨機分配),而T的地址,假設為500H。
此時調用初始化子函數1,值傳遞,T的地址傳給L,即:L=500H(細心的會發現L和T指向值為 1000H的同一塊內存空間,也即該內容空間有兩個變量名字表示), *L = 1000H。 此時申請節點后,假設該節點地址為2000H, 其地址賦給 *L,然后返回 1。
回到主函數,在看一下 T 的地址依然為500,但是 *T呢,不錯,已經是2000H了,也就是說此時的指針T真正的指向了一個節點了。
- int InitList1(LinkList *L)同int InitList1(LinkList &L),只不過前者用在C環境(&L編譯不過去),后者 引用方式傳遞參數 用在C++環境才行(可以*L);再者,函數體中,前者(*L)->next,后者L->next. 教材上如出現&L引用方式傳遞參數,請用C++編譯環境。