(轉發)連結串列新增函式中為什麼要用指向連結串列指標的指標(引用傳參)

清風oo發表於2018-10-21

https://blog.csdn.net/shen_jz2012/article/details/50631317

 在看書的時候有個往連結串列裡新增節點的函式,程式碼中考慮到可能給出的頭指標為空,並做另外一些處理。具體程式碼如下

 1 #include <iostream>
 2 #include <string>
 3 using namespace std;
 4  
 5 struct ListNode
 6 {
 7     int val;
 8     ListNode* next;
 9 };
10  
11 void AddToTail(ListNode** pHead, int value);
12  
13 int main() {
14     // TODO
15 }
16  
17 void AddToTail(ListNode** pHead, int value) {
18     ListNode* pNew = new ListNode();
19     pNew->val = value;
20     pNew->next = NULL;
21  
22     if (*pHead == NULL) {
23         *pHead = pNew;
24     }
25     else {
26         ListNode* p = *pHead;
27         while (p->next != NULL) {
28             p = p->next;
29         }
30         p->next = pNew;
31     }
32 }

網上其他人的部落格中對函式AddToTail的引數的描述跟書中如出一轍:第一個引數pHead是一個指向指標的指標,當向一個空連結串列插入一個節點時,新插入的節點是連結串列的頭指標,此時會改動頭指標,因此必須把pHead引數設定為指向指標的指標。

        為什麼呢?在以前學習C++的時候,我們只知道在引數中,以傳值的形式作為引數的變數在函式體內被修改之後,出了函式體就會失效,準確的說這個變數沒有被修改過,因此需要傳入該變數的指標或者使用引用傳參的方式。可是上述AddToTail中已經是一個指標了啊?於是我測試了一下,不使用指標的指標會怎樣:

 1 #include <iostream>
 2 #include <string>
 3 using namespace std;
 4  
 5 struct ListNode
 6 {
 7     int val;
 8     ListNode* next;
 9 };
10  
11 void AddToTail(ListNode* pHead, int value);
12  
13 int main() {
14     // TODO
15     ListNode* head = NULL;
16     AddToTail(head, 10);
17     if (head != NULL) {
18         cout << head->val << endl;
19     }
20     else {
21         cout << "head is NULL.." << endl;
22     }
23     
24 }
25  
26 void AddToTail(ListNode* pHead, int value) {
27     ListNode* pNew = new ListNode();
28     pNew->val = value;
29     pNew->next = NULL;
30  
31     if (pHead == NULL) {
32         pHead = pNew;
33     }
34     else {
35         ListNode* p = pHead;
36         while (p->next != NULL) {
37             p = p->next;
38         }
39         p->next = pNew;
40     }
41 }

作為指標pHead竟然真的沒被修改過!

        其實真的很好理解,既然你懂得函式中的值傳參,假設int a,作為引數傳入的時候沒被修改,所以需要用指向a的指標,那麼應該也可以理解,指標變數pHead作為引數傳入的時候被修改無效,因此需要用指向pHead的指標,只不過pHead本身就是一個指標了,所以才存在有指標的指標看起來稍微複雜一點的說法。因為,指向a的指標作為引數傳入進去時,如果你對它進行修改,其實也是無效的,但是修改指標指向的內容的修改是有效的,也即,(&a)對a取地址得到的指標傳入進去之後,此時你修改這個指標也是沒有什麼實際作用的,原因我等下會說。但是,你修改指標指向的內容這就有效了,因此通常我們在函式體內是修改對指標取內容後的記憶體,即*(&a)。所以,你對指標pHead的修改時無效的,只有對指向pHead的指標指向的內容(很繞吧,其實就是pHead),這時候才是有效的,因此AddToTail的第一個引數必須用指標的指標。

 

        現在來說說為什麼對值傳參在函式體內的修改無效。因為a傳進去的時候會被複制了一份copy,此後的修改都是在臨時變數copy上,出了函式體copy被銷燬,a還是原來的a,根本就沒被修改過,所以才會值傳參對變數的修改無效。要使得對a的修改有效,一方面是傳入a的地址,也就是對指向a的指標作為值傳參(反正修改的不是a的指標,修改了也無所謂,反正只是修改a的指標的copy),此時a的指標的copy指向的內容也是a,因此對copy指向的內容修改會導致a的內容也被修改,check!另外一種方式就是引用傳參,引用傳參往往要比值傳參高效,因為它是直接將a作為引數傳入進去,而少了對a進行復制這部分的開銷,既然傳入進去的是a,那麼對a的修改肯定也生效。

 

        為了證明上述廢話,我將程式碼2中的AddToTail函式的第一個引數也作為引用引數傳入(指向指標的指標肯定正確啦,就不測試了),此時預測的結果是修改有效。程式碼如下:
----------------

 1 #include <iostream>
 2 #include <string>
 3 using namespace std;
 4  
 5 struct ListNode
 6 {
 7     int val;
 8     ListNode* next;
 9 };
10  
11 void AddToTail(ListNode* &pHead, int value);
12  
13 int main() {
14     // TODO
15     ListNode* head = NULL;
16     AddToTail(head, 10);
17     if (head != NULL) {
18         cout << head->val << endl;
19     }
20     else {
21         cout << "head is NULL.." << endl;
22     }
23     
24 }
25  
26 void AddToTail(ListNode* &pHead, int value) {
27     ListNode* pNew = new ListNode();
28     pNew->val = value;
29     pNew->next = NULL;
30  
31     if (pHead == NULL) {
32         pHead = pNew;
33     }
34     else {
35         ListNode* p = pHead;
36         while (p->next != NULL) {
37             p = p->next;
38         }
39         p->next = pNew;
40     }
41 }

 

相關文章