【C++ 筆記】參考(Reference) - part 14

很感謝你點進來這篇文章。

你好,我並不是什麼 C++、程式語言的專家,所以本文若有些錯誤麻煩請各位鞭大力一點,我極需各位的指正及指導!!本系列文章的性質主要以詼諧的口吻,一派輕鬆的態度自學程式語言,如果你喜歡,麻煩留言說聲文章讚讚吧!

簡介(Introduction)

參考(Reference)是物件的別名(Alias),也就是替代名稱,對參考名稱存取時該有什麼行為,都參考了來源物件該有的行為,在 C++ 中,「物件」這個名詞,不單只是指類別的實例,而是指記憶體中的一塊資料。

所以參考變數是現存變數的另一個名字,將參考初始化為某個變數,就可以使用該參考名稱或變數名稱來指向變數。

參考常常與指標做比較,原因是他們的性質相同,同樣地,我們先從定義上來看:

:::success
指標(Pointer):

  • 指標是一種變數,用來儲存其他變數的記憶體位址。指標可以直接對記憶體進行運算。
  • 宣告時使用星號(*)來表示,例如:int* ptr; 表示 ptr 是一個指向整數的指標。
    :::

:::info
參考(Reference):

  • 參考是另一個變數的別名,並且必須在宣告時初始化。參考可以直接對原始變數進行運算,不需要額外的解參考運算(Deference)。
  • 宣告時使用 & 符號,例如:int& ref = original; 表示 reforiginal 的參考。
    :::

筆者列以下表格比較兩者差異:

ItemsPointerReference
初始化可以不初始化,後續可重新指向其他變數必須在宣告時初始化,且無法更改指向
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

1
int i = 0;

那麼我們可以替 i 宣告參考變數:

1
int& r = i;

& 運算子放置於資料型態後方可用於定義一個參考。

以下是一個範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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/

輸出結果:

1
2
x = 20
ref = 30

先前我們有談過函數的參數傳遞,其中有所謂的傳參考呼叫,詳情請至我的筆記 part 11,「傳送門點我」。

藉參考回傳(Return by reference)

在 C++ 中,參考也可當成回傳值使用,如同指標的做法一般。

但是必須要注意以下這三點,才能好好使用 reference:

  1. 作用域(Scope):當函數回傳一個參考時,必須確保被參考的物件在函數結束後仍然有效。回傳局域變數的參考是無效的,因為局域變數在函數結束後會被銷毀。
  2. 修改原始數據:回傳的參考允許呼叫者直接修改原始數據,可能導致意外的副作用。如果不希望修改原始數據,應使用 const 修飾詞來回傳常數參考。
  3. 靜態變數(static)的使用:可安全回傳靜態變數的參考,因為靜態變數在整個程式執行期間都存在。

以下是個範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#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;
}

輸出結果:

1
2
3
4
5
6
7
8
9
10
11
12
改變前的值:
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

程式碼解釋:

1
2
setValues(1) = 20.23;
setValues(3) = 70.8;

由於函數中回傳的是 vals[i] 的參考,呼叫後可以視為以下的程式碼,結果是相同的:

1
2
vals[1] = 20.23;
vals[3] = 70.8;

但是在這邊使用函數回傳參考值,可實現”避免了不必要的資料複製,提升效能”這句話。

總之,setValues 函數回傳 vals 陣列中指定索引的元素的參考。這樣可以直接通過這個參考來修改陣列中的值。

有關於作用域問題,以下是一個錯誤示範:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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;
}

正確示範如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
#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

C++ 筆記 (Reference & Pointer) - HackMD

參考

References in C++ - GeeksforGeeks

C++ 把引用作为返回值 | 菜鸟教程

C++ 引用 | 菜鸟教程