提到指針,我們都知道指針是用來存儲一個變量的地址。所以,當我們定義了一個指向指針的指針的時候(pointer to pointer),我們也稱之為二級指針,那針對于這個二級指針來說,第一級指針存放的是指向的變量的地址,第二級指針存放的是第一級指針的地址??梢杂孟旅孢@張圖表示他們之間的關(guān)系。
二級指針關(guān)系圖
上圖所表達的意思也就是,一級指針變量 ptr1 存放的是 var 變量的地址,二級指針變量 ptr2 存放的是一級指針變量的地址。這也就是關(guān)于二級指針的相關(guān)概念。
一級指針與二級指針關(guān)系示例
下圖是代碼運行的結(jié)果:
代碼運行結(jié)果截圖
結(jié)果也很明顯了,一級指針變量 p 存放的是變量 a 的地址,二級指針變量 q 存放的是一級指針變量 p 的地址,所以根據(jù)以上結(jié)果也能得出下面的等式:
q = &p;*q = p = &a;**q = *p = a;
在了解了上述一級指針和二級指針的一個關(guān)系之后,我們再來看另外一個例子:
現(xiàn)在有如下代碼:
int main(void){ int **ipp; int i = 5,j = 6,k = 7; int *ip1 = &i,*ip2 = &j; }
如果這個時候,我們加了這么一句代碼:
ipp = &ip1;
那么上述所涉及到的數(shù)據(jù)之間的關(guān)系是這樣的:
變量關(guān)系圖
根據(jù)上面這個圖我們也可以知道,對于 ipp 的兩次解引用的結(jié)果是 i 的值,也就是說 **ipp = 5,我想對于這個的理解并不困難,如果我繼續(xù)在這個基礎(chǔ)上添加代碼,注意,是在上條代碼的基礎(chǔ)上添加如下代碼:
*ipp = ip2;
在這條代碼的作用下,數(shù)據(jù)關(guān)系圖就發(fā)生了改變,改變?nèi)缦滤荆?/p>
數(shù)據(jù)關(guān)系圖
對于上述的變化來說,我們增加的代碼改變的是 *ipp 的值,也就是說 ipp 的值是不會發(fā)生改變的,既然 ipp 的值不會發(fā)生改變,那么 ipp 指向 ip1 的關(guān)系不會發(fā)生改變,我們增加的代碼改變了 *ipp 的值,那么也就是說改變了一級指針指向的值,而 ip2 是指向 j 的,所以也就有了上述的變化。
緊接著我們繼續(xù)在第一條增加的代碼的基礎(chǔ)上重新增加一條代碼,增加的代碼如下:
*ipp = &k;
那么這個時候所對應的數(shù)據(jù)關(guān)系圖如下圖所示:
數(shù)據(jù)關(guān)系圖
這個原理和剛才的一樣,不在這里贅述了。
二級指針的應用那再講述了上述的基本概念之后,我們知道二級指針變量是用于存放一級指針變量的地址的,那么在具體的實際應用中,又在什么地方可以用到二級指針呢?下面來看一個 C 語言函數(shù)傳址調(diào)用的例子。
我們在剛學習指針的時候,都會碰到如下這樣一個例子:
void swap(int *a,int *b){ int temp; temp = *a; *a = *b; *b = temp;}
之所以在定義函數(shù)時,把函數(shù)的形參定義為指針,而非如下這樣的形式:
void swap(int a,int b);
是因為C 語言在進行函數(shù)調(diào)用的時候,是將實參的值復制一份,并將其副本傳遞到函數(shù)調(diào)用里,如果形參定義的不是指針,那么在函數(shù)內(nèi)部改變數(shù)值,不會對實參本來的值發(fā)生改變。而將形參定義成了指針的話,那么傳到函數(shù)里面的值雖然是實參地址的一個副本,但是地址里存的值發(fā)生了改變,也就導致實參本來的值也發(fā)生了改變。
有了上述分析的基礎(chǔ)上,我們知道,如果要在一個函數(shù)內(nèi)改變一個數(shù)的值,那么就需要將形參定義為指針。同樣的,如果我們要在一個函數(shù)內(nèi)改變一個指針的值,我們就需要將形參定義了二級指針,下面來看這樣一個例子:
#include 《stdlib.h》int allocstr(int len,char **retptr){ char *p = malloc(len + 1);/*加 1 是為了 ‘\0’ */ if (p = NULL) return 0; *retptr = p; return 1;}
在調(diào)用的時候,是像下面這樣子進行調(diào)用的:
char *string = “hello world!”char *copystr;if (allostr(strlen(string),?str)) strcpy(copystr,string);else printf(“out of memory!\n”);
上述這個例子就是涉及到字符串拷貝的一個實際的例子,因為我們要在 allostr 里改變指針變量 copystr 的值(要使用 malloc 分配內(nèi)存),那么就需要把 copystr 的地址傳到函數(shù)里,那么這個時候,所定義的函數(shù)形參也就需要是二級指針了。
二級指針在單鏈表中的應用首先,我們有這樣一個單鏈表的數(shù)據(jù)結(jié)構(gòu):
typedef struct ListNode{ int data; struct ListNode *next;}ListNode;
依據(jù)這樣一個數(shù)據(jù)結(jié)構(gòu),假定我們創(chuàng)建了一個如下所示的一個單鏈表:
單鏈表
那么我們?nèi)绻獎h除鏈表中的一個結(jié)點的時候,第一時間采用的可能是如下所示的代碼:
ListNode *find_and_delete(ListNode *head,int target){ ListNode *pre = NULL; ListNode *entry; for (entry = head; entry != NULL; entry = entry-》next) { if (entry-》data == target) { /* 判斷刪除的結(jié)點是否是第一個結(jié)點*/ if (entry == head) head = entry-》next; else pre-》next = entry-》next; free(entry); break; } pre = entry; } return head;}
上述代碼所述的刪除結(jié)點的思路遵循如下圖所示的原理,首先是關(guān)于當所要刪除的結(jié)點是第一個結(jié)點的時候,刪除結(jié)點示意圖如下所示:
第一個結(jié)點刪除原理
如果要刪除的結(jié)點不是處在第一個結(jié)點的位置,那么刪除結(jié)點的原理示意圖如下圖所示:
普通結(jié)點刪除
上述就是一個使用一級指針操作鏈表的一個簡單地例子,自己在理解這個例子的時候,也存在幾個對我來說的難點,筆者寫下來和大家分享一下,首先,
第一個難點就是頭指針,在圖中畫的頭指針指向了第一個結(jié)點,圖中所示的頭指針并沒有數(shù)據(jù)域,只是單單地指向了第一個結(jié)點,在代碼中的 head 指針變量卻有數(shù)據(jù)域,并且就是第一個結(jié)點的數(shù)據(jù),這個概念的理解其實是對于指針的理解,head 指向了第一個結(jié)點,一定注意在這里的 head 是頭指針,并不是頭結(jié)點。(這是筆者個人的理解,如果大家有不同的看法,歡迎各位朋友添加筆者微信共同探討)。
第二個難點就是上述函數(shù)中,函數(shù)有一個返回值,返回了頭指針。為什么要返回呢?是因為當前傳入函數(shù)的形參是一級指針,在函數(shù)內(nèi)部改變 head ,在函數(shù)運行結(jié)束時,head 值并不會發(fā)生改變,所以要返回。
第三個難點,那么為什么鏈表操作中,又能夠刪除中間的結(jié)點呢?是因為雖然 傳進去的 head 是一級指針,但是 head 結(jié)構(gòu)體成員內(nèi)的 next 是一個指針,那這樣的話,對于 next 成員來說它是一個二級指針,對于他的變化,在函數(shù)結(jié)束時是會產(chǎn)生改變的,所以可以刪除中間的結(jié)點。
二級指針在單鏈表結(jié)點刪除的應用上面的例子中,在刪除單鏈表的結(jié)點的時候,我們形參采用的是一級指針的方式,在這個過程中,還需要引入 pre 指針來解決這個問題,還有一種很巧妙的方法,利用了二級指針的特性解決了結(jié)點刪除的問題,在這個過程中,運用二級指針,不需要進行刪除第一個結(jié)點的判斷。具體代碼如下:
void find_and_delete2(ListNode **head,int target){ for (; *head != NULL; head = &(*head)-》next) { if ((*head)-》data == target) { (*head) = (*head)-》next; break; } } }
上述的代碼沒有創(chuàng)建任何局部變量,直接利用 head 進行遍歷鏈表,因為其是二級指針,這樣子進行遍歷在函數(shù)結(jié)束后不會改變其本身的鏈表結(jié)構(gòu)。然后,在進行刪除的時候,(*head) 在函數(shù)結(jié)束后是會保持其在函數(shù)內(nèi)的變化值的,所以也就完成了結(jié)點的刪除。
評論
查看更多