From Gossip@caterpillar

C Gossip: 引數傳遞、傳回值

引數傳遞時的傳值就是傳送(變數)值給函式上對應的參數,值被複製一份給參數,傳遞者與接受者兩個變數彼此各佔有一個記憶體,互不相干,例如:
int main(void) {
    int x = 10;
    ....
    printf("%d\n", increment(x));
    printf("%d\n", x);

    return 0;
}

int increment(int n) {
    n = n + 1;
    return n;
}

在這個程式片段中,x將值傳遞給increment()函式的參數n,n雖然作了遞增運算,但是對x的儲存值並無影響,x最後仍是顯示10。

在傳值應用上,您也可以將變數的記憶體位址值取出,傳遞位址值給指定的指標參數,只要使用&運算子就可以了。
int main(void) {
    int x = 10;
    ....
    printf("%d\n", increment(&x));
    printf("%d\n", x); 

    return 0;
}

int increment(int *n) {
    *n = *n + 1;
    return *n;
}

在這個程式中,increment()上的參數n是個指標,在呼叫increment()函式時,您使用取址運算子&將x變數的記憶體位 址取出並 傳遞給指標n,而在函式中,您使用取值運算子*取出這塊記憶體位址的值,並作遞增動作之後再指定回該記憶體位址,所以程式最後的x變數會顯示值11。

在函式上宣告指標參數的目的,通常目的是若作為引數的變數值同一位址上,在函式中若有變動該位址上的值時,呼叫者也可以保留這份變動的結果,這可以解決一 個問題,在C中我們在呼叫函式後只能傳回 (return)一個值,若在呼叫函式時,您希望能取得兩個以上的運算結果,就可以使用指標參數。

在定義函式時,一定要定義函式的傳回值型態,如果函式不傳回值,則使用void表示不傳回任何數值;一旦指定函式的傳回值不為void,則在函式中一定要 使用return傳回一個數值,否則編譯器將回報錯誤。

在之前的範例中,您只是使用傳值的方式傳回函式的執行結果,事實上您也可以傳回一個指標或是參考,傳回指標通常意味著您要對這個指標所指向的記憶體位置作 取值或更動的動作,例如下面的程式中,您在函式中動態建立一個陣列,並傳回它的指標值:
#include <stdio.h>
#include <stdlib.h>

int* createArray(int);
void deleteArray(int*);

int main(void) {
int m = 0;

printf("陣列大小: ");
scanf("%d", &m);

int *arr = createArray(m);
int i;
for(i = 0; i < m; i++) {
arr[i] = i;
}

for(i = 0; i < m; i++) {
printf("arr[%d] = %d\n", i, arr[i]);
}

deleteArray(arr);

return 0;
}

// 傳回建立的陣列位址
int* createArray(int m) {
int *a = malloc(m * sizeof(int));

int i;
for(i = 0; i < m; i++) {
a[i] = 0;
}

return a;
}

void deleteArray(int* arr) {
free(arr);
}

執行結果:
陣列大小: 5
arr[0] = 0
arr[1] = 1
arr[2] = 2
arr[3] = 3
arr[4] = 4


由於您使用動態配置的方式,所以在使用free()之前,這塊被配置的記憶體並不會自動清除,所以您可以直接傳回給呼 叫函式的主函式,注意如果您不是使用malloc()來配置,則在副函式中所宣告的變數記憶體,在函式執行結束後都會自動消失,則您傳回指標值也就沒有意 義,也會造成存取錯誤,因為該塊記憶體在副函式 執行完畢後已經自動回收了。

在這邊的範例您也看到了如何傳遞陣列給函式,以及如何傳回一個陣列,在C中傳遞陣列或傳回陣列一律使用傳遞記憶體位址的方法,因為陣列名稱本身就是個指標,儲存有 位址資訊。

必須注意的是,函式中的區域變數在函式開始時被配置,在函式結束後所佔有的記憶體位址也會被清除,絕對不要傳回一個區域變數的位址給呼叫者,因為您所存取的記憶體位址中資料是未知的,所以結果是不可預期的。