C/C++ 學習筆記 001 – 複習 call by value & reference
來複習 call by value 與 call by reference,以確保自己在後續的學習上可以更加穩固。
1. call by value
A 函數呼叫 B 函數時,A 函數可能會需要傳遞一些變數的傳值給 B 函數,我們稱為引數,而 call by value 可以想成是 A 直接把引數的數值餵給了 B,B 收到數值以後就可以開始工作。
中間的過程則大概是:
其數值會先複製一份到堆疊(stack)內,堆疊又是啥?嗯 就先當成是一個倉庫,每個函數開始工作前,都會先準備好一個堆疊供此函數執行的時候使用,好像開門做生意之前要找倉庫然後放備料一樣。
所以 A 函數要傳遞的值會在 B 函數的堆疊內放置一份副本,然後才被 B 函數使用,等 B 函數結束,將 return 結果丟到自己的堆疊內,而 A 函式再從B 函數的堆疊取用 return 值。
所以, call by value 的傳值方式,無論 B 函數怎麼亂搞,都不會修改到 A 函數的變數值。
比方下面這個寫法,A 函數內的變數 va 永遠不會被 B 函數修改到。
... 其他程式片段 ... void B( int va) { va = 100; } void A() { int va = 3; B(va); cout << va; } ... 其他程式片段 ...
B 函數結束後,在 A 函數內輸出 va 的值,其結果依舊會是 3 ,是誰被改成 100 呢? 讀者們應該都有清晰的概念了吧? 是 B 函數堆疊內的那個 va 被改成 100 ,可是 B 函數結束後,屬於B 函數的堆疊就已經被釋放,空間還給了記憶體,資料也就不復存在了。
2. call by reference
上述已經闡明,在 cal by value 的情況下,呼叫函數所傳遞的引數是不會被改變的。
如果想要改變呢? 那麼就需要用到 reference (參照物、參考體的意思) 的技巧,reference 可以想做是一個別名、暱稱,你對這個 reference 做出修改,也能夠改到原本的數值。
如果說今天有個 C 函數 呼叫了 D 函數,其中的引數採用 reference 來傳遞的話,那麼就不會在 D 函數堆疊複製該引數的副本了,只會建立一個指向原本變數的 reference ,所以,修改 reference 是可以修改到原本的變數的。
int N; // 宣告一個整數變數 N
int &M = N; // 宣告 M 是 N 的
這個 & 記號為 reference operator (參考運算子),他的意義代表 M 與 N 會使用到同一塊記憶體位址, M 就可以當成 N 的別名來使用。無論執行 M=100; 或 N=100; 都是同一塊記憶體位址被寫入100。
來個生動的譬喻,你對著你的家人宣告說: &學校 = 清華大學 。所以,對你的家人而言,這兩個詞條都指向「新竹市光復路二段 101 號」,當你跟家人說「我回學校」跟「我回清華大學」,都是一樣的意思,你都會前往「新竹市光復路二段 101 號」。
就是這種感覺!
概念懂了,那要如何實作呢? 簡而言之就是: C 函數呼叫時傳遞引數 N ,而 D 函數的接收處以 reference M 做接收,這樣就行了。
... 其他程式片段 ... void D(int &M){M=100;} void C() { int N=3; D(N); cout << N; } ... 其他程式片段 ...
所以你可以看到我在 D 函數引數的地方令 M 是 N 的 reference ,前後連起來其實也就是 int &M = N; 的敘述囉!
之後在 D 函數內,就大方的修改變數 M 的值吧! 都會反應到同一塊記憶體位址上,而回到 C 函數之後,輸出 N 的值,即可看到改變。
3. reference operator 跟 address operator 討論
大家都發現 reference operator 跟指標的 address operator(取址運算子)是使用同一個記號 & 了吧?但是他們的意義不同! 咱們分別看一下:
例一、reference operator
int N=3;
int &M = N;
上面這兩行的動作是: M 跟 N 將指向同一塊記憶體。
例二、address operator
int N=3;
int M = &N;
這兩行的動作則是:先取得變數 N 所存放的位址(例如、0x4AE3) 後,儲存到 變數 M 內。因此 N 的值是 3 ,而 M 的值是 0x4AE3。
還有,例二的第二行其實是錯誤語法,因為 0x4AE3 做為一個指標型態的數值,其實不能寫入普通的整數 M 裡頭。這裡仍然執意如此的寫出來,只是為了說明取址動作上的概念。
真正想要寫入的話,M 應該要宣告成整數指標,而不是宣告成整數,像這樣:
int *M = &N;
改成這樣就行了,只是如果一開始就這樣寫,難免又要讓新手先困惑 *M 之意。所以上面才先用錯誤語法做講解。
4. call by address
很多教學跟教材都已經強調過,其實在C/C++原文裡,並無 call by address 這個詞彙,這個是國內自行發展出來,並成為一種流行說法,可能比較貼近國人思維吧!
事實上,call by address 就是 call by value 的一種。他們一個傳的是普通數值,另一個傳的是指標位址,也就是傳遞內容的資料型態不同。區別僅此而已,所以在整個「傳遞過程」、「傳遞原理」上,兩者毫無區別。
但在運用上,一個普通變數的用法跟一個指標變數的用法,那當然是完全不同囉!
因此,我很能認同許多人在學習過程中,需要多出一個 call by address 的稱呼,來幫助自己釐清這兩者的用法,這不是那麼難以接受的事情,能幫助你學會就是好稱呼!
所以說,如果函數呼叫的過程中,你傳遞的引數是指標的話,該怎麼做呢?
以 E 函數呼叫 F 函數為例:
... 其他程式片段 ... void F( int *vf) // 用 vf 來接收 0x3E68, vf 型態是整數指標 { cout << vf; // 你會看到輸出為 0x3E68 cout << *vf; // 你會看到輸出為 100 // 因為是去提取 0x3E68 這個位址上的數值 (*vf)++; } void E() { int ve = 3; F(&ve); //對 ve 取址,傳過去的是類似 0x3E68 這樣的位址 cout << ve; // 你會看到輸出為 101 } ... 其他程式片段 ...
Leave a Reply