【C++ 筆記】參考(Reference) - part 14
===
目錄(Table of Contents):
[TOC]
---
很感謝你點進來這篇文章。
你好,我並不是什麼 C++、程式語言的專家,所以本文若有些錯誤麻煩請各位鞭大力一點,我極需各位的指正及指導!!本系列文章的性質主要以詼諧的口吻,一派輕鬆的態度自學程式語言,如果你喜歡,麻煩留言說聲文章讚讚吧!
簡介(Introduction)
---
> 參考(Reference)是物件的別名(Alias),也就是替代名稱,對參考名稱存取時該有什麼行為,都參考了來源物件該有的行為,在 C++ 中,「物件」這個名詞,不單只是指類別的實例,而是指記憶體中的一塊資料。
所以參考變數是現存變數的另一個名字,將參考初始化為某個變數,就可以使用該參考名稱或變數名稱來指向變數。
參考常常與指標做比較,原因是他們的性質相同,同樣地,我們先從定義上來看:
:::success
指標(Pointer):
* 指標是一種變數,用來儲存其他變數的記憶體位址。指標可以直接對記憶體進行運算。
* 宣告時使用星號(`*`)來表示,例如:`int* ptr;` 表示 ptr 是一個指向整數的指標。
:::
:::info
參考(Reference):
* 參考是另一個變數的別名,並且必須在宣告時初始化。參考可以直接對原始變數進行運算,不需要額外的解參考運算(Deference)。
* 宣告時使用 `&` 符號,例如:`int& ref = original;` 表示 `ref` 是 `original` 的參考。
:::
筆者列以下表格比較兩者差異:
| Items | Pointer | Reference |
| -------- | -------- | -------- |
| 初始化 | 可以不初始化,後續可重新指向其他變數 | 必須在宣告時初始化,且無法更改指向 |
| NULL | 可以為空(nullptr),表示不指向任何有效地址 | 不能為空,必須始終指向一個有效的變數 |
| 語法 | 使用星號(`*`)來宣告和解參考 | 使用 `&` 來宣告,如普通變數一般使用方式 |
| 靈活性 | 可以指向不同型態的變數,包括指標本身 | 只能用作變數的別名,不支援指向其他參考或空值 |
| 效能 | 可能會導致額外的解參考開銷 | 避免了不必要的資料複製,提升效能 |
有關參考的相關應用,如下四點:
1. 修改函數中傳遞的參數
2. 避免複製大型結構
3. 在 For Each 迴圈中修改所有物件
4. For Each 迴圈以避免物件的複製
Source:https://www.geeksforgeeks.org/references-in-cpp/
For Each Loop 相關資訊:https://zh.wikipedia.org/zh-tw/Foreach%E5%BE%AA%E7%8E%AF
其實,參考就像是一個人的綽號,一個人可以有很多的綽號,像是:彬彬、呆呆等等,無論有多少個綽號,這些都是指同一個人的意思,同一個人就是最原始的自己,所以是原始變數。
建立參考(Create a Reference)
---
假設我們有個原始變數叫做 `i`:
```cpp
int i = 0;
```
那麼我們可以替 `i` 宣告參考變數:
```cpp
int& r = i;
```
`&` 運算子放置於資料型態後方可用於定義一個參考。
以下是一個範例:
```cpp=
#include <iostream>
using namespace std;
int main()
{
int x = 10;
// ref 為 x 的參考
int& ref = x;
// x 的值現在變成 20
ref = 20;
cout << "x = " << x << '\n';
// x 的值現在變成 30
x = 30;
cout << "ref = " << ref << '\n';
return 0;
}
```
Source:https://www.geeksforgeeks.org/references-in-cpp/
輸出結果:
```
x = 20
ref = 30
```
先前我們有談過函數的參數傳遞,其中有所謂的傳參考呼叫,詳情請至我的筆記 part 11,「[傳送門點我](https://hackmd.io/@LukeTseng/BkR5_AFQJe)」。
藉參考回傳(Return by reference)
---
在 C++ 中,參考也可當成回傳值使用,如同指標的做法一般。
但是必須要注意以下這三點,才能好好使用 reference:
1. 作用域(Scope):當函數回傳一個參考時,必須確保被參考的物件在函數結束後仍然有效。回傳局域變數的參考是無效的,因為局域變數在函數結束後會被銷毀。
2. 修改原始數據:回傳的參考允許呼叫者直接修改原始數據,可能導致意外的副作用。如果不希望修改原始數據,應使用 const 修飾詞來回傳常數參考。
3. 靜態變數(static)的使用:可安全回傳靜態變數的參考,因為靜態變數在整個程式執行期間都存在。
以下是個範例:
```cpp=
#include <iostream>
using namespace std;
double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};
// 回傳陣列中指定索引的元素的參考
double& setValues(int i) {
return vals[i]; // 回傳第 i 個元素的參考
}
int main() {
cout << "改變前的值:" << endl;
for (int i = 0; i < 5; i++) {
cout << "vals[" << i << "] = " << vals[i] << endl;
}
// 改變第 2 和第 4 個元素
setValues(1) = 20.23;
setValues(3) = 70.8;
cout << "改變後的值:" << endl;
for (int i = 0; i < 5; i++) {
cout << "vals[" << i << "] = " << vals[i] << endl;
}
return 0;
}
```
輸出結果:
```
改變前的值:
vals[0] = 10.1
vals[1] = 12.6
vals[2] = 33.1
vals[3] = 24.1
vals[4] = 50
改變後的值:
vals[0] = 10.1
vals[1] = 20.23
vals[2] = 33.1
vals[3] = 70.8
vals[4] = 50
```
程式碼解釋:
```cpp=
setValues(1) = 20.23;
setValues(3) = 70.8;
```
由於函數中回傳的是 `vals[i]` 的參考,呼叫後可以視為以下的程式碼,結果是相同的:
```cpp=
vals[1] = 20.23;
vals[3] = 70.8;
```
但是在這邊使用函數回傳參考值,可實現"避免了不必要的資料複製,提升效能"這句話。
總之,`setValues` 函數回傳 vals 陣列中指定索引的元素的參考。這樣可以直接通過這個參考來修改陣列中的值。
有關於作用域問題,以下是一個錯誤示範:
```cpp=
#include <iostream>
using namespace std;
// 錯誤示範:回傳局域變數的參考
int& getLocalVariable() {
int localVar = 42; // 局域變數
return localVar; // 回傳局域變數的參考
}
int main() {
int& ref = getLocalVariable(); // 嘗試獲取局域變數的參考
cout << "Local variable value: " << ref << endl; // 會出現未定義行為
return 0;
}
```
正確示範如下:
```cpp=
#include <iostream>
using namespace std;
int& getLocalVariable() {
static int localVar = 42; // 局域變數
return localVar; // 回傳局域變數的參考
}
int main() {
int& ref = getLocalVariable();
cout << "Local variable value: " << ref << endl;
return 0;
}
```
參考資料
---
[Return by reference in C++ with Examples - GeeksforGeeks](https://www.geeksforgeeks.org/return-by-reference-in-c-with-examples/)
[C++ 筆記 (Reference & Pointer) - HackMD](https://hackmd.io/@B7i0qMBLTyyyRp1nasdE-Q/ryfTY48qd)
[參考](https://openhome.cc/Gossip/CppGossip/Reference.html)
[References in C++ - GeeksforGeeks](https://www.geeksforgeeks.org/references-in-cpp/)
[C++ 把引用作为返回值 | 菜鸟教程](https://www.runoob.com/cplusplus/returning-values-by-reference.html)
[C++ 引用 | 菜鸟教程](https://www.runoob.com/cplusplus/cpp-references.html)