文章程式碼顯示

2018年3月12日 星期一

《筆記》談談C++語言 - 3:傳參考呼叫(call by reference)與傳值及傳址呼叫之差異、宣告參考變數

傳參考呼叫(call by reference)與傳值及傳址呼叫之差異

在 C語言 系列中,我們談到了傳值呼叫(call by value) 與 傳址呼叫(call by address) (傳址呼叫又在文中被我敘述為模擬的傳參考呼叫)

而當我學到了 C++ 的時候又出現了傳參考呼叫(call by reference)

這有點混亂,但經過我查詢了許多資料後,在此連結找到一個很好的解釋

原來在 C語言中 ,只具有傳值呼叫(call by value),其它不復存在

我思考了一番後決定不更改原先 C語言 系列文章中有關於傳址呼叫(call by address)的字眼,畢竟這個字眼在網上也是流傳了許久,表示其具有一定的接受度且一定有它一番道理。

故想藉由本文來好好的理清楚這三者之間的關係為何

首先程式碼如下(我慣用 printf ,所以還是 #include "stdio.h" 而不使用 iostream)

#include "stdio.h"
#include "stdlib.h"

void callByValue(int C, int D);
void callByAddress(int* E, int* F);
void callByReference(int& G, int& H);

int A = 5;
int B = 10;

int main() {
 printf("Number A = %d, A_address = 0x%p\n", A, &A);
 printf("Number B = %d, B_address = 0x%p\n", B, &B);
 printf("\n");
 callByValue(A, B);
 printf("\n");
 callByAddress(&A, &B);
 printf("\n");
 callByReference(A, B);

 return 0;
}

void callByValue(int C, int D){
 printf("===== callByValue =====\n");
 printf("Get number A by number C = %d, C_address = 0x%p\n", C, &C);
 printf("Get number B by number D = %d, D_address = 0x%p\n", D, &D);
}

void callByAddress(int* E, int* F){
 printf("===== callByAddress =====\n");
 printf("Value of number E(A_address) = 0x%p, E_address = 0x%p, Get number A by the pointer = %d\n", E, &E, *E);
 printf("Value of number F(B_address) = 0x%p, F_address = 0x%p, Get number B by the pointer = %d\n", F, &F, *F);
}

void callByReference(int& G, int& H){
 printf("===== callByReference =====\n");
 printf("Number G = %d, E_address = 0x%p\n", G, &G);
 printf("Number H = %d, F_address = 0x%p\n", H, &H);
}


結果如下


程式碼 :

8 ~ 9 行 : 定義兩個整數型態的變數 A 及 B ,並且具有初始值 5 及 10

4 行 : 定義一個名為 callByValue 的函數,代表傳值呼叫
5 行 : 定義一個名為 callByAddress 的函數,代表傳址呼叫
6 行 : 定義一個名為 callByReference 的函數,代表傳參考呼叫

11 ~ 22 行 : 依序呼叫以上三個函數

首先我在第 12, 13 行的地方先列印出變數 A, B 的值以及位址(Address)

表示對於電腦而言使用了位址 0x00404004 放置 A 的數值,變數 B 亦同

傳值呼叫
接著我在第 15 行的地方呼叫了 callByValue 函式,並將變數 A,B 的值傳遞進去
傳遞進 callByValue 函式後,我將傳遞進去的變數 C,D 的值以及地址列印出來

結果顯示,變數 C 在位址 0x0028FF30 位置具有數值 5 ,變數 D 亦同
代表的是使用傳值呼叫的方法時,在函數內會額外建立一個記憶體的區塊,並將輸入參數的值複製過去

傳址呼叫
同理,我在第  17 行的地方呼叫了 callByAddress 函式,並將變數 A,B 的位址傳遞進去
傳遞進 callByAddress 函式後,我將傳遞進去的變數 E 的值、地址、以及使用"(依地址)取值運算子 * " 將該參考地址所存放的值列印出來

結果顯示,變數 E 在位址 0x0028FF30 位置具有數值 0x00404004 (該數值是變數 A 的位址),依地址(0x00404004)取值後,得到數值 5 ,也就是變數 A 所存放的數值。變數 F 亦同

代表使用傳址呼叫的方法時,在函數內也會額外建立一個記憶體的區塊,用來存放輸入參數的位址

傳參考呼叫
我在第  19 行的地方呼叫了 callByReference 函式,並將變數 A,B 直接傳遞進去(這裡要說傳遞數值也不是,說傳遞位址也不是)

傳遞進 callByReference函式後,我將傳遞進去的變數 G 的值以及地址列印出來

結果顯示,變數 G 在位址 0X00404004 位置具有數值 5

發現了嗎? 使用傳參考呼叫並不會額外建立一個記憶體的區塊。像是對輸入參數戴了一頂面具,換了一個名稱而已。(註 : 書中所述,變數 G 稱為參考參數,可視為輸入參數 A 的一個別名)

結語 :

經由三方比較發現,傳值呼叫以及傳址呼叫都會在記憶體中產生出一個額外的位址用來存放"東西",而傳參考呼叫則並不會。

實際上,傳址呼叫(call by address)也屬於傳值呼叫(call by value)的一種,只是傳址呼叫傳遞的 值(value) 是一個位址而已。故傳址呼叫又被稱為 " call by value of pointer(address) "

另一方面,在參考連結也有提到,沒有任何一個程式語言可以對 "記憶體位址" 直接進行算術運算

的確,我們在講指標的章節中有使用如 *(ptr +1) 等等的算術運算,當時我們就認為這是在對記憶體位址做運算;但細思後可以得知,我們是在對 "指標變數" 做算術運算,只是恰巧這東西是用來存放位址(address)的

這也是 "(依地址)取值運算子 * " 需要存在的原因,因為 pointer 終究還是 pointer ,它代表的是一個記憶體位址的 "表象",知道表像什麼事情都不能做,除非你對它進行進一步的處理(如對這個地址取值、對這個地址讀值...),才具有意義

我怎麼覺得這是個哲學問題了 !?

Hint : 以上的 call by value, call by address, .... 等等又被稱為 pass by value, pass by address, ....

宣告參考變數

#include "stdio.h"
#include "stdlib.h"

int main() {
 int A = 10;
 int &B = A; // B refers to (is an alias for) A

 printf("A = %d, B = %d\n", A, B);
 A = A + 1;
 printf("A = %d, B = %d\n", A, B);
 B = 80;
 printf("A = %d, B = %d\n", A, B);

 return 0;
}


上述程式碼中, B 就稱為參考變數

參考變數必須在宣告時就初始化,並且不能將此參考重新指定成其它變數的別名

當參考變數被宣告為其它變數的別名後,對此參考變數執行的所有操作都會作用到原始變數上(範例如下)。

別名只是原始變數的另一個名稱

#include "stdio.h"
#include "stdlib.h"

int main() {
 int A = 10;
 int& B = A; // B refers to (is an alias for) A

 printf("A = %d, B = %d\n", A, B);
 A = A + 1;
 printf("A = %d, B = %d\n", A, B);
 B = 80;
 printf("A = %d, B = %d\n", A, B);

 int C = 40;
 B = C; // 參考變數不得在中途轉換到別的變數(錯誤用法)
 printf("A = %d, B = %d, C = %d\n", A, B, C);
 B = 99; // 否則對此參考變數執行的所有操作都會作用到原始變數 A 上
 printf("A = %d, B = %d, C = %d\n", A, B, C);

 return 0;
}



↓↓↓ 連結到部落格方針與索引 ↓↓↓

Blog 使用方針與索引