分類

展開全部 | 收合全部

分類

展開全部 | 收合全部

【C++ 觀念理解 #1】C++ call by value, call by address (pointer), call by reference 總和比較整理,不想要再搞不懂了!!! (updated: 2022/8/19)

前言

C/C++ 一個超級重要的觀念就是指標,
沒學會指標基本上就還等於沒有玩出 C++ 的「精隨」,
但指標這個概念真的超複雜又容易搞錯 (這邊是以作者而言,至少我混亂過好多次XD)

這次打算來好好整理一下,以一個初學者到終於搞清楚的角度,
重新整理這部分的內容。

先釐清會常搞亂的根本原因

我到很後來才發現,自己指標學得很差的最主要原因,
就是在「call by reference」加入之後,直接一片混亂

「call by reference」是 C++ 才有的「更方便」的東西,
但也因為這個「方便」,導致觀念如果不穩,就會像我一樣把這個「方便」的功能,
變成「符號意義大混亂

因此,如果這三個你現在很混亂,
建議先從「call by value, call by address(pointer)」搞清楚再說!!!

call by value

在 C(C++) 裡面,「所有的東西基本上都是 call by value」,
在這一開始還是很和平,「沒有指標」的世界,
所有東西看起來都是非常好懂。

void foo(int a){
    a = 20;    
}

int main(){
    int a = 10;
    cout << a << endl;
    foo(a);
    cout << a << endl;
    return 0;
}

結果

10
10

結果我們卻發現了一個神奇的問題,那就是 a 並沒有被修改到,
如果以同樣的概念,除非我們改成這樣寫。

void foo(){
    return 20;    
}

int main(){
    int a = 10;
    cout << a << endl;
    a = foo();
    cout << a << endl;
    return 0;
}

結果

10
20

用「回傳」的方式,去修改 a 的值,a 才會被改到
但其實這限制了所有修改的範圍,僅能侷限在 main() 當中。

仔細觀察就發現,要達到我們的目的,所有的修改都必須要在 main 裡面
但這並不是很實際。 隨著我們程式越寫越大,不可能要所有的東西都在 main() 處理。

圖解一下

在沒有指標的觀念以前,我們不是很在乎這個值「是實際存在哪個記憶體位置
所以下面的圖,「Address 的部分」,在指標的概念出現前,
我們都是省略不看的。

但實際上,我們會發現在 void function 裡面的 a,
「複製了 10 這個值,並儲存在新的記憶體位置,
並用新的「只在 function 使用的 a」,指向這個記憶體位置。

  • function 之內的 a,因為是另外一個記憶體位置的 a 被改過值,
  • 所以不在 function 內的 a 就是另外一個 a,沒有被改過。

call by address (pointer)

在原本還是很簡單的只有 call by value 的世界,
自從我們要開始學習「指標 (pointer)」這個章節後,
一切開始複雜了起來。

但其實,掌握「指標 (pointer)」,才是真正開始「能完全掌控系統參數的控制」

上述的例子我們提到了一個問題,所有的修改僅在 main 裡面有效,
那如果要實現真正的跨 function 修改,我們就必須使用 call by address (pointer) 的方式

正如其名,call by address 就是直接呼叫他的記憶體位置進行修改,
因為我們實際儲存值的地方,都是一個又一個的記憶體位置,
我們所宣告的 a,只是暫時替我們指向這個記憶體位置而已。

而之所以也可以叫做 call by pointer,因為我們傳的是記憶體位置,
傳入 function 後,作為「指向記憶體位置的指標」,而非「參考值」,
因此也有 call by pointer 之稱 (不過我個人覺得講 call by address 更好理解。)

圖解一下

call by address,最重要的地方在於「傳了實際的記憶體位置進去function」,
也因此,我們得以進行對應值的修改

(改的是記憶體位置,原 a 也是指向同一個記憶體位置,造成 a 結果就可以從 function 內改變)

也有一種理解方式是:其實這兩者本質都相同的是 call by value
只是 call by value 指的是傳「值」
而 call by address(pointer) 我們以「記憶體位置 (address)」作為我們的「value」並傳入 function
(但如果這樣理解會混淆,我建議就還是分開想吧,以不混淆為主)

void foo(int* a){
    *a = 20;
}

int main(){
    int a = 10;
    cout << a << endl;
    foo(&a);
    cout << a << endl;
    return 0;
}

結果

10
20

call by reference (建議一定要先理解前面兩個的差別,再來理解這個以免混淆)

call by reference 是 C++ 後新增的更「方便」 的使用方式,
但也因為這個「方便」很容易造成「符號解釋大混亂」,
這就是我當初嚴重混淆的主因。

簡單來說,call by reference 與 call by address (pointer) 結果完全相同,
只有差在使用符號上的撰寫不同而已!
但因為「符號有重複的使用」,如果不熟如我,
前面觀念又不穩,到此就會開始「大混亂」。

寫法

C++ 的 call by reference 幫我們簡化了寫法,只需要在宣告 function 變數時,
多新增「& 符號」,即可以表示「此值我們也要修改原值,而非只做參考用。

void foo(int& a){
    a = 20;
}

int main(){
    int a = 10;
    cout << a << endl;
    foo(a);
    cout << a << endl;
    return 0;
}

與 call by value 比較,只多了一個 void foo(int& a) 的 「&」

但因為也常因此跟「call by address 的取址 &」搞混
造成觀念不夠熟的(如我),一開始就會大混亂。

結論

我們只把最關鍵的部分拉出來看:

// call by value
void foo(int a){}
call func: foo(a)

// call by address (pointer),傳入需使用 foo(&a) 取址,function 內使用時也需要使用 *a 從記憶體位置取得他的值。
void foo(int* a){}
call func: foo(&a) // call 的時候可以找到 & 作為關鍵!!!

// call by reference
void foo(int& a){}
call func: foo(a)

我們可以直接觀察宣告 function 時的不同,
而特別注意傳入時,因為 call by address (pointer),
是把「記憶體位置作為 value 傳入,因此需要傳入 &a」

建議把兩種「&」分開理解,避免觀念混亂。

實際使用上

實際使用上,「沒有哪一個是最萬用的,只能看情況覺得應該用哪個」,
有時我們就是只需要傳入一個「參考值」,我們就需要用 call by value,
有時我們就是要去「改原來的變數」,我們就需要用 call by address (pointer) 或 call by reference

【面試常考題】 call by address (pointer) 與 call by reference 的優缺點?

其實這兩者所達到的效果是一樣的「都會改變原來傳入 function 的值」,
但 C++ 中 call by reference 卻是一個更好的設計,
在傳入 function 時,他會去「檢查這個記憶體位置是否合法」,
如果為「不合法的記憶體位置」,則會跳錯,不去進行任何後續的動作

這樣就可以避免「我們去改動到不應該去改的記憶體位置」,造成非預期的記憶體內容被修改。

因此,這兩種寫法,在我們學會 C++ call by reference 之後,
我們會更傾向使用 「call by reference」 > 「call by address (pointer) 」

新補充:網路上找到更容易理解的方式

剛好有一次收集資料時剛好看到這個 youtube 的說明,
覺得講解的滿不錯的,在此做一個整理。

call by pointer (address)

只有宣告的指標,才能去取用「&」取得變數對應的位置。

int* ptr;
int var = 7;
ptr = &var;

call by reference

被宣告為 reference 的變數 (使用「&」,但這裡符號的意義不同,我自己搞混解釋就是在這裡。)

我們比較好的理解方式是,我們可以視為有另外一個新的如果 var 指向 7 一樣的 ref,
沒有「複製值」而「產生新的記憶體位置」,而是直接「讓 ref 指向同一個記憶體位置

int* ptr;
int var = 7;
ptr = &var;
int& ref = var;

簡易圖解

ref 只是多宣告一個如同 var 指向同一個記憶體位置的變數。
(這裡 int「&」不要用「取址」的概念去解釋,雖然看起來有點關係,但觀念真的很容易亂掉)
宣告時的「&」,在觀念不熟的情況下,請先當作另外一回事。

Reference

⭐C++ 基礎用法 相關文章整理⭐:
1.【C++】C++ compile 程式碼 使用 c++ 11 與使用相關的 package
2.【C++】C/C++ 顯示資料的類別 (type) sample code (內含範例程式碼) print C data type, cout C++ data type, get variable type in c++
3.【C++】C++ 複製 2D array的方法 copy 2d array memcpy sample code (內含範例程式碼)
⭐Modern C++ ⭐:
⭐C++ 字串處理相關文章整理⭐:
1.【C++】字串 char string stringstream 相關用法總整理 (內含範例程式碼) 與利用 sprinf, snprinf assign 值的方法
2.【C++】字串 char string stringstream 「轉換」用法總整理 (內含範例程式碼)
3.【C】printf, fprintf, sprintf, snprintf 相關用法總整理 (內含範例程式碼)
4.【C++】C++ String 用法 連接兩個 String c++ string concat
⭐C++ 系統偵測相關文章整理⭐:
1.【C++】C++ 利用 dirent.h 計算資料夾的檔案數量 count files sample code (內含範例程式碼)
2.【C++】C++ inotify sample code 偵測指定路徑底下的文件變化 (內有範例程式碼)
⭐C++ OpenCV 相關文章整理⭐:
1.【OpenCV】c++ OpenCV – 在 ubuntu 上第一次執行 OpenCV 程式 sample code (內含範例程式碼)
2.【OpenCV】c++ OpenCV - 在圖片上寫上文字 cv::putText sample code (內含範例程式碼)
3.【OpenCV】c++ OpenCV - cv::Rect 矩形用法與相關功能函數 sample code (內含範例程式碼)
4.【OpenCV】c++ OpenCV - OpenCV 中的純量 定義顏色 cv::Scalar(255,255,255) color sample code (內含範例程式碼)
5.【OpenCV】用 C++ 計算 iou 的方法 與網路算法常見錯誤(內附範例程式碼) sample code
⭐C++ Makefile 相關文章整理⭐:
1.【C++ Makefile】- 1 / 嘗試撰寫自己的第一份 Makefile
2.【C++ Makefile】- 2 / 新增自己的變數
3.【C++ Makefile】- 3 / Makefile 常用變數 -「$@」, 「$^」
4.【C++ Makefile】- 4 / Makefile 常用 fake target -「.PHONY」
5.【C++ Makefile】- 5 / Makefile 內建變數 -「$@」, 「$^」, 「$<」, 「$* 」,「$? 」
⭐【喜歡我的文章嗎? 歡迎幫我按讚~ 讓基金會請創作者喝一杯咖啡!
如果喜歡我的文章,請幫我在下方【按五下Like】 (Google, Facebook 免註冊),會由 「LikeCoin」 贊助作者鼓勵繼續創作,讀者們「只需幫忙按讚,完全不用出錢」哦!

likecoin-steps
Howard Weng
Howard Weng

我是 Howard Weng,很多人叫我嗡嗡。這個網站放了我的各種筆記。希望這些筆記也能順便幫助到有需要的人們!如果文章有幫助到你的話,歡迎幫我點讚哦!
另外,因為定位是「個人的隨手筆記」,有些文章內容「⚠️可能我理解有誤⚠️」或「?只寫到一半?」,如果有發現這樣的情況,歡迎在該文章的最下面留言提醒我!我會儘快修正或補上!感謝大家的建議與幫忙,讓網站能變得更好?

文章: 866

★留個言吧!內容有誤或想要補充也歡迎與我討論!