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
  }
 ... 其他程式片段 ...

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步瞭解 Akismet 如何處理網站訪客的留言資料