文章程式碼顯示

2018年2月5日 星期一

《筆記》C語言 - 07_1:傳值呼叫 v.s. 模擬傳參考呼叫(傳址呼叫)

傳值呼叫(call by value)

我們在前面的章節有提到當我們建立一個函式並且讓這個函式具有輸入參數時,該傳入的參數會依據 "傳值呼叫" 來進行處理,也就是該被呼叫的參數會事先建立一個副本供函數使用,我們並不會(也不能)直接對函數外的被呼叫參數進行值的更改。

例如以下

#include "stdio.h"

void call_by_value(int number);

int main(void) {

 int number = 10; /* local */

 printf("Number in main %d\n",number);

 call_by_value(number);

 printf("Now,Number in main %d\n",number);

 return 0;
}

void call_by_value(int number){
 number = 20;
 printf("Number in call_by_value %d\n",number);
}



如上述的程式碼,我們試圖想要 "直接" 更改第 7 行的 number 為 20 ,但因為傳值呼叫的特性是沒有辦法達成的,除非改用下列的程式碼

#include "stdio.h"

int turn(int number);

int main(void) {

 int number = 10; /* local */

 printf("Number in main %d\n",number);

 number = turn(number);

 printf("Now,Number in main %d\n",number);

 return 0;
}

int turn(int number){
 number = 20;
 printf("Number in call_by_value %d\n",number);
 return number;
}


如上述,我們將函數改為具有回傳值的函數,並將 number 進行賦值

事實上這樣的作法跟我們一開始的目的是不相符的,因為我們想要的是 "直接" 修改,而不是這種拐彎式的作法(雖然最終還是得到我們想要的效果,但初衷不一樣)

模擬傳參考呼叫

模擬的傳參考呼叫與真實的傳參考呼叫(call by reference)不同
事實上 C 語言 裡面並沒有傳參考呼叫,只有在 C++ 裡面才有傳參考呼叫(call by reference)

在某些書籍裡面,會把這種模擬的傳參考呼叫稱為傳址呼叫( call by address) 或是 " call by value of pointer " 

#include "stdio.h"

void call_by_address(int* numberPtr);

int main(void) {

 int number = 10; /* local */

 printf("Number in main %d\n",number);

 call_by_address(&number);

 printf("Now,Number in main %d\n",number);

 return 0;
}

void call_by_address(int* numberPtr){
 *numberPtr = *numberPtr +10 ;
 printf("number in call_by_address is %d\n", *numberPtr);
}



在 18 行的地方,我們宣告了一個函式名稱為 call_by_address ,其輸入的參數為 int* 型別

回顧一下在上一章時我們定義了 int* door; 這個指標變數時是怎麼讀它的

讀作 「door 是一個存放位址的指標變數,
存放的位址指向一個"空間",
且這個 "空間",裡頭存放的"數值"為 int 型別」

更簡潔且被大家慣用的讀法為「door 是一個指向 int型別 的指標」( door is a pointer points to integer)

稍微對照一下,我們就知道 numberPtr 這個輸入引數應該要是一個指標(位址),且該位址存放的數值應該要是 int 型別的

也就是說若函式想接收 位址 作為參數輸入的話,必須定義一個 指標變數 做為引數,來接收這個位址

上方程式碼中的 call_by_address 的函式原型就指明該函式將接收一個 int型別 的變數的位址 作為參數輸入。

由上述結果發現我們經由一個指標變數,可以直接更改 number 的數值(也就是說我們直搗 number 的記憶體位址,去修改了它的數值)



順帶一提,我們在 06_1 的時候用了 int b[] 這種方式將陣列傳遞進函式,事實上這樣的寫法編譯器在編譯時會 自動 修改成 int* b 。也就是說函式變成了接收一個 int 型別的變數的位址

再來,我們在前面的章節有說過

「陣列是一群具有相同名稱以及相同型別(type)的變數集合,在記憶體中擁有連續存放空間的集合」(於此)

「陣列名稱的記憶體位址 = 陣列第一個元素的記憶體位址」(於此)

三者合併你就會發現,原來將陣列傳遞進入函式,實際上它傳遞的是「陣列第一個元素的記憶體位址」,而不是陣列裡面的那些元素的數值

後記:

當我們必須將 "結構(struct)化的資料" 傳給函式時,亦可以使用指向常數資料的指標(後述),若使用普通的傳値呼叫讓一個結構當作引數,則它傳遞的會是整個結構(struct)的副本。這會增加執行時的負擔,可能是記憶體空間之類的。

使用指向常數的指標一方面可以獲得模擬傳參考呼叫(傳址呼叫)的效率性,另一方面也可以如傳値呼叫般保護結構裡面的資料。

效率性是什麼意思呢?

當傳遞一個指向結構體的指標(位址)時,你只需要複製一份該結構體所存放的 "位址" 即可。也就是說我們只需要傳遞"位址(4bytes)"的資料(一個指標變數的資料長度),而不是數百或數千個 byte 的資料(亦即整個結構體可能的大小)

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

Blog 使用方針與索引