指標作為函數引數,C語言指標作為函數引數詳解

2020-07-16 10:04:24
我們在前面講指標重要性的時候講過:“指標能使被調函數返回一個以上的結果”。本小節給大家寫一個經典的程式,就是通過一個函數修改主函數中好幾個變數的值。這個程式很經典,把這個程式弄清楚了,指標就算是入門了。在寫這個程式之前先來作一個鋪墊:
# include <stdio.h>
void Swap(int a, int b);  //函數宣告
int main(void)
{
    int i = 3, j = 5;
    Swap(i, j);
    printf("i = %d, j = %dn", i, j);
    return 0;
}
void Swap(int a, int b)
{
    int buf;
    buf = a;
    a = b;
    b = buf;
    return;
}
大家想一下,執行這個程式是否能互換 i 和 j 的值?不能!i 還是3,j 還是5。

因為實參和形參之間的傳遞是單向的,只能由實參向形參傳遞。被調函數呼叫完之後系統為其分配的記憶體單元都會被釋放。所以雖然將 i 和 j 的值傳給了 a 和 b,但是交換的僅僅是記憶體單元 a 和 b 中的資料,對 i 和 j 沒有任何影響。

“為什麼不用 return 語句?”因為 return 語句只能返回一個值,並不能返回兩個值。“將 printf 放在被調函數中不就行了嗎?”我們的目的是互換記憶體單元 i 和記憶體單元 j 中的資料。而 printf 的功能僅僅是將結果輸出,並不能改變資料處理的本質,互換的還是單元 a 和單元 b 中的資料。

以上傳遞方式叫作拷貝傳遞,即將記憶體 1 中的值拷貝到記憶體 2 中。拷貝傳遞的結果是:不管如何改變記憶體 2 中的值,對記憶體 1 中的值都沒有任何影響,因為它們兩個是不同的記憶體空間。

所以要想直接對記憶體單元進行操控,用指標最直接,指標的功能很強大。
# include <stdio.h>
void Swap(int *p, int *q);  //函數宣告
int main(void)
{
    int i = 3, j = 5;
    Swap(&i, &j);
    printf("i = %d, j = %dn", i, j);
    return 0;
}
void Swap(int *p, int *q)
{
    int buf;
    buf = *p;
    *p = *q;
    *q = buf;
    return;
}
輸出結果是:
i = 5, j = 3

此時實參向形參傳遞的不是變數 i 和 j 的資料,而是變數 i 和 j 的地址。其實傳遞指標也是拷貝傳遞,只不過它拷貝的不是記憶體單元中的內容,而是記憶體單元的地址,這就是天壤之別了。拷貝地址就可以直接對地址所指向的記憶體單元進行操作,即此時被調函數就可以直接對變數 i 和 j 進行操作了。有人會說:“被調函數用完就釋放了,不就把 i 和 j 都釋放了嗎?”不是的,當函數呼叫完之後,釋放的是 p 和 q,不是 i 和 j。p 和 q 中存放的是 i 和 j 的地址。所以 p 和 q 被釋放之後並不會影響 i 和 j 中的值。前面講過,修改指標變數的值不會影響所指向變數中的資料。只不過它們之間的指向關係沒有了而已。 

此外需要注意的是,形參中變數名分別為 p 和 q,變數型別都是 int* 型。所以實參 i 和 j 的地址 &i 和 &j 是分別傳遞給 p 和 q,而不是傳遞給 *p 和 *q。
 
函數引數傳指標和傳資料的區別

綜上所述,如果希望在另外一個函數中修改本函數中變數的值,那麼在呼叫函數時只能傳遞該變數的地址。如果這個變數是普通變數,那麼傳遞它的地址就可以直接操作該變數的記憶體空間。

那麼,是不是要定義一個指標變數指向它然後傳遞這個指標變數呢?不用多此一舉。比如有一個“int i;”,如果想傳遞i的地址那就直接傳遞 &i 就行了,不用專門定義一個指標變數指向它,然後再傳遞這個指標變數。

如果要傳遞的變數本身就是一個指標變數怎麼辦?如果要操作該指標變數所指向的記憶體空間是不是要傳遞該指標變數的地址呢?

指標變數本身就是地址,本身就是指向那個記憶體空間的,所以直接把它傳過去就行了。除非你要改變那個指標變數裡面存放的地址,即你要改變指標變數的指向,那麼你就必須要傳遞指標變數的地址。

此外,傳指標和傳資料相比還有一個好處就是節約記憶體。我們知道,傳資料拷貝的是記憶體單元的資料,如果資料很多的話拷貝過來都要為它們分配記憶體。而傳指標的話只需要傳遞 4 位元組的地址就行了。而且傳資料非常消耗效率,為形參分配記憶體需要時間,拷貝需要時間,最後結束了返回還是需要時間。前面說過,return 時系統會先自動建立一個臨時變數來存放返回的值。所以傳資料時很消耗效率,而傳指標就是為了提高效率。

事實上,在實際程式設計中我們都是傳遞指標!往往只有滿足下面這兩個條件的時候我們才會直接傳遞資料而不是傳遞指標,而且這兩個條件缺一不可:
  • 資料很小,比如就一個 int 型變數。
  • 不需要改變它的值,只是使用它的值。

此時不是不能用指標,當然也可以用指標,只是沒有必要。

以後在使用函數的時候,只要函數的引數不滿足上面這兩個條件,那麼全部都用指標。此外需要注意的是,陣列名本身就是地址,所以如果傳遞陣列的話直接傳遞陣列名就行了。接收的形參可以定義成陣列也可以定義為同型別的指標,這點後面再講。