引數與傳回值


引數傳遞是傳送值給函式上對應的參數,值會複製一份給參數,來源變數與接受的參數各有一個記憶體位址,互不相干,例如:

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 函式的參數 nn 雖然作了遞增運算,但是對 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 呼叫函式後只能傳回一個值,若在呼叫函式時,想取得兩個以上的運算結果,就可以使用指標參數。

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

函式也可以傳回位址,這意味著呼叫者可以對該位址取值或變更,例如下面的程式中,在函式中動態配置連續的 int 空間,並傳回空間的位址:

#include <stdio.h>
#include <stdlib.h>

int* ints(int, int);

int main(void) {
    int size = 0;
    int init = 0;

    printf("陣列大小:");
    scanf("%d", &size);
    printf("元素初值:");
    scanf("%d", &init);    

    int *arr = ints(size, init);
    for(int i = 0; i < size; i++) {
        printf("arr[%d] = %d\n", i, arr[i]);
    }

    free(arr);

    return 0;
}

int* ints(int size, int init) {
    int *arr = malloc(size * sizeof(int));
    for(int i = 0; i < size; i++) {
        arr[i] = init;
    }
    return arr;
}

執行結果:

陣列大小:5
元素初值:3
arr[0] = 3
arr[1] = 3
arr[2] = 3
arr[3] = 3
arr[4] = 3

由於使用動態配置的方式,被配置的空間在函式執行過後,不會自動清除,可以直接傳回位址給呼叫者,如果是底下範例,陣列空間會在函式執行完後清除,編譯器會提出警訊,傳回指標值也就沒有意義,也會造成存取錯誤:

#include <stdio.h>
#include <stdlib.h>

int* ints(int, int);

int main(void) {
    int size = 0;
    int init = 0;

    printf("陣列大小:");
    scanf("%d", &size);
    printf("元素初值:");
    scanf("%d", &init);    

    int *arr = ints(size, init);
    for(int i = 0; i < size; i++) {
        printf("arr[%d] = %d\n", i, arr[i]);
    }

    free(arr);

    return 0;
}

int* ints(int size, int init) {
    int arr[size];
    for(int i = 0; i < size; i++) {
        arr[i] = init;
    }
    return arr; // warning: function returns address of local variable
}

如果要傳遞陣列給函式,方式之一是明確宣告陣列型態,這必須包含陣列長度:

#include <stdio.h>
#include <stdlib.h>

void printInts(int arr[5]) {
    for(int i = 0; i < 5; i++) {
        printf("%d ", arr[i]);
    }
}

int main(void) {
    int arr[] = {1, 2, 3, 4, 5};
    printInts(arr);

    return 0;
}

當然,這就寫死了長度資訊,另一個方式是指定長度:

#include <stdio.h>
#include <stdlib.h>

void printInts(int len, int arr[len]) {
    for(int i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }
}

int main(void) {
    int arr[] = {1, 2, 3, 4, 5};
    printInts(5, arr);

    return 0;
}

這是因為 C99 以後,多數編譯器支援可變長度的陣列型態(variable length array type),然而記得 len 參數必須先出現,編譯器看到後才能用於後續參數。

由於 C11 將可變長度的陣列型態標示為非必要功能,如果編譯器真的不支援,可以使用傳遞陣列位址的方式:

#include <stdio.h>
#include <stdlib.h>

void printInts(int len, int *arr) {
    for(int i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }
}

int main(void) {
    int arr[] = {1, 2, 3, 4, 5};
    printInts(5, arr);

    return 0;
}

在傳遞陣列時,C99 以後可以直接傳遞常量,例如:

#include <stdio.h>
#include <stdlib.h>

void printInts(int len, int *arr) {
    for(int i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }
}

int main(void) {
    printInts(5, (int[]) {1, 2, 3, 4, 5});

    return 0;
}

(int[]) {1, 2, 3, 4, 5}int[] 轉型是必要的,編譯器需要這項資訊知道這是個 int 陣列。