前往
大廳
主題

簡單認識函數參數傳遞的不同方式 — 傳參考 / 傳址 / 傳值

魔化鬼鬼 | 2020-12-09 20:22:06 | 巴幣 14 | 人氣 1555

    在這篇文章,將會提到程式的函式在傳遞參數時的各種方式,之所以想寫這篇文章,是因為發現上程設助教課的時候聽助教講這個聽得很頭痛,然後也發現身邊初學程式的同學好像對這個也沒什麼概念,所以想來稍微分享一下我的想法。


  •     傳值  (Call by value / Pass by value)
    相信各位在初學函式(function)時,可能有看過類似下面這樣的程式碼範例:

#include <iostream>
using namespace std;

int add(int a, int b){
    return a + b;
}

int main(){
    int a = 5;
    int b = 15;
    
    cout << add(a, b); // 輸出: 20
}

    照理來說,有學過函式概念的人,對傳值的觀念應該已不陌生,所以我只會大略的講過去。當我們傳入 a 和 b 到 add 這個函式裡面時,在 add 這個函式裡面的 a 和 b 其實和 main 裡面的 a 跟 b 是不一樣的東西,但是他們的值是一樣的,換句話說,當你以 Pass by value 的方式傳遞參數時,函式會生成新的變數來儲存傳進來的值。

    這也代表說你沒辦法在這段函式執行時修改main裡面的 a 和 b ,因此,待會會再介紹另外兩種方式來解決這種困境。

    這種傳遞參數的方式在某些時候可能會消耗大量的記憶體,尤其當你的參數是一個非常巨大的物件時,程式會複製出一份一樣巨大的物件,多少會影響一些效能。


  • 傳址 (call by address / pass by address)
    這邊會提到指標的概念,如果對指標不熟的,可以看我上篇文章「帶你攻略程式新手村大魔王 — 指標」

    傳址,顧名思義,我們不直接傳遞參數了,我們改傳遞參數的位址,在函式中藉由取值符號 * 就可以直接操控原本的值了,我用幾行程式碼來簡單說明:

#include <iostream>
using namespace std;

void divideBy5(int* k){
    *k = *k / 5;
}

int main(){
    int a = 15;
    divideBy5(&a);
    cout << a; // 輸出: 3
    return 0;
}

    在這個例子中,我們傳遞了 a 的位址給指標變數 k ,所以 k 目前儲存了 a 的位址,藉由取值符號 *,我們可以直接把 a 位址變成 a 變數,也就是說 *k 等於 a,而 a = a / 5; 那麼 a 就會變成 3 ,就是最後cout的結果。這樣我們就解決了傳值那邊提到的「無法修改main裡變數的窘境」。

    基本上這種傳遞方式就是指標的應用,利用位址和變數的關係,來達到傳遞不同scope之間的變數。

    有一點非常值得一提,當我們傳遞 a 的位址到 devideBy5 時,實際上這個函式也生成了 k 這個變數,拿來儲存 a 的位址,有沒有發現什麼?這不就是跟 Pass by value 是同一個概念嗎!是的,Pass by address 就只是 Pass by value 的特例而已,本質上來說 Pass by address 就是 Pass by value,只是傳的值變成位址而已。


  • 傳參(考) (Call by reference / Pass by reference)
    最後一個,傳參,聽名字聽不出什麼,英文不好的我看英文也看不出個所以然,不過它的概念應該是最好懂的。

    在C++裡面,只要在宣告的時候在變數前面加上「&」,就代表這個變數是一個參考(剛學指標的朋友,別把這個跟取址符搞混喔)。那什麼是參考,很簡單當我們宣告了一個參考時,我們必須給它初始化,而初始化就是給它另一個變數,此時,你宣告的參考等同於你給他的變數,只是名字不一樣而已,下面有一個例子:

#include <bits/stdc++.h>
using namespace std;

int main(){
    int a = 0;
    int &b = a;
    
    for(int i = 0; i < 10; ++i, ++a){
        cout << a << " " << b << "\n";
    }
    return 0;
}

/** 輸出結果:
* 0 0
* 1 1
* 2 2
* 3 3
* 4 4
* 5 5
* 6 6
* 7 7
* 8 8
* 9 9
*
**/

    我宣告了一個參考叫做 b ,並且賦值為 a ,此時的 b 就等同於 a ,只是名字不同而已,為了證明他們相同,我在下方寫了一個 for 迴圈,每跑一次就把 a 加 1 ,你會發現我沒有對b做任何修改,但 a 和 b 卻同步修改了,這就是reference的概念。

    因此我們便可以利用這個特性來傳遞函數的參數,下面是兩個值swap的函式:

#include <bits/stdc++.h>
using namespace std;

void mySwap(int &x, int &y){
    int temp;
    temp = x;
    x = y;
    y = temp;
}

int main(){
    int a = 20, b = 11;
    mySwap(a, b);
    cout << "a = " << a << ", b = " << b; // 輸出: "a = 11, b = 20"
    return 0;
}

    如果你用前面的 Pass by value 的話,這個函數等於沒意義,因為交換的根本是不同的東西,不過我們加上了參考的概念,這樣就可以成功的交換 a 和 b 了,只不過他們在 mySwap 這個函式裡面叫做 x 和 y 而已。

創作回應

煙戒
讚喔><
2020-12-13 15:40:23

更多創作