National Chengchi University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try a Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

1

Local AI

--

Uedu Code

--

政治大學 AQI 58 25°C PM2.5 12
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 09

參考

參考是別名、以參考傳遞與 const 參考
Interaction實作 20 minute(s)

Learning Objectives

  • 說明參考是變數的別名:宣告時必須綁定,之後不能改綁到別的變數
  • 用傳參考讓函式修改呼叫端的變數,或一次帶回好幾個結果
  • 用 const 參考傳遞字串、vector 等較大的物件,避免複製又防止修改
  • 在 range-for 裡分辨 auto、auto& 與 const auto& 的差別
  • 說明回傳參考的條件,辨認懸空參考,並比較參考與指標

幫變數取第二個名字

第 6 課提到,C++ 預設是傳值呼叫:函式拿到的是複本,改不到呼叫端的變數; 第 8 課〈指標〉用「傳位址」解決了這個問題,但每次都要寫 &x、*p,還得擔心空指標。

參考(reference)是 C++ 提供的另一種做法:它是一個已經存在的變數的別名(alias),兩個名字指的是同一塊記憶體。

int score = 80;
int& alias = score;    // int& 讀作「int 的參考」;alias 就是 score
alias = 90;            // score 也變成 90

注意 & 的位置:寫在型別後面(int&)是宣告參考;寫在變數前面(&score)是取位址,兩者意思不同。

參考的三條規則:

  1. 宣告時一定要綁定一個變數:int& r; 是編譯錯誤
  2. 綁定之後不能改綁:之後對參考做的任何指定,都是在改它綁定的那個變數
  3. 一般的參考(非 const)不能綁到字面常數或運算結果:int& r = 5; 是編譯錯誤

動畫裡的參考不會顯示自己的值,而是一個指向原變數的標記,表示「這個名字就是那個變數」。

參考是別名,不能改綁

第 8、9 行印出的兩個位址完全相同,表示 alias 沒有自己的記憶體,它就是 score。第 13 行 alias = other; 看起來像是讓 alias 改綁到 other,其實是把 60 寫進 score;之後改 other,alias 不受影響。

傳參考

參數宣告成參考,呼叫時參數就直接綁定到引數,函式裡改參數就是改呼叫端的變數,稱為傳參考(pass by reference):

void swapByRef(int& a, int& b) { int t = a; a = b; b = t; }

swapByRef(x, y);       // 呼叫時照常寫變數名稱,不用寫 &

和第 8 課的指標版本 swapByPointer(&x, &y) 比較:參考版本的函式裡不用寫 *,呼叫時不用寫 &,而且參考不會是空的,不必檢查 nullptr。

傳參考的兩個常見用途:

  • 讓函式修改引數,例如交換、排序、把分數加分
  • 一次帶回好幾個結果:return 只能回傳一個值,需要兩個以上的結果時,可以用參考參數帶回來(例如同時算出最小值與最大值)

傳參考時,引數必須是變數,swapByRef(3, 8) 是編譯錯誤,因為字面常數沒有可以修改的記憶體。

傳值與傳參考的交換

比較兩次呼叫時右邊的框架:swapByValue 的 a、b 是有自己數值的複本;swapByRef 的 a、b 則標示著指回 main 的 x、y。執行第 13、14 行時,留意 main 框架裡的 x、y 跟著改變。

想一想:別名與傳參考

1.

下面的程式會印出什麼?

int a = 1, b = 2;
int& r = a;
r = b;
b = 9;
cout << a << r;
2.

下面的程式會印出什麼?

void twice(int& n) {
    n = n * 2;
}
int main() {
    int x = 3;
    twice(x);
    twice(x);
    cout << x;
}
3.

下面的程式會印出什麼?

int main() {
    int& r = 5;
    cout << r;
}
第 1 次作答

const 參考:不複製也不修改

傳值呼叫會複製引數。int 只有 4 個位元組,複製很便宜;但 string、vector 可能有成千上萬個元素,每次呼叫都複製一份很浪費。

如果函式只需要讀,參數寫成 const 參考:

int countChar(const string& text, char target);
double average(const vector<int>& v);
  • 參考:不複製,直接使用呼叫端的物件
  • const:函式裡不能修改它,改了就是編譯錯誤,呼叫的人可以放心
  • const 參考可以綁到字面常數與暫時值,所以 countChar("hello", 'l') 也能呼叫(字串字面常數會先轉成一個暫時的 string)

選擇參數寫法的經驗法則:

參數 寫法
int、double、char、bool 這類小型別,只讀 傳值:int n
string、vector 這類較大的物件,只讀 const 參考:const string& s
函式要修改呼叫端的變數 參考:int& n、vector<int>& v

const 參考參數

第一次呼叫 countChar 時,右邊的 text 指回 main 的 fruit,沒有另外複製一份字串。第二次傳的是字面常數 "hello",text 綁定的是一個暫時的 string。第 14 行如果拿掉註解,就會是編譯錯誤。

range-for 裡的參考

C++11 加入的 range-for(for (元素 : 容器))可以走訪陣列、string、vector 的每一個元素。迴圈變數的寫法決定它是不是參考:

寫法 迴圈變數是 用途
for (int x : v) 元素的複本 只讀小型別;改 x 不影響 v
for (int& x : v) 或 for (auto& x : v) 元素本身(別名) 要修改容器裡的元素
for (const auto& x : v) 元素本身,但唯讀 只讀較大的元素(例如 string),不複製

auto 讓編譯器依容器自動推導元素型別,auto& 就是「那個型別的參考」。

複本、別名與唯讀

第一個迴圈的 p 是有自己數值的複本,乘以 2 之後 prices 沒變;第二個迴圈的 p 標示著指向 prices[0]、prices[1]……,改它就是改 vector 裡的元素。

回傳參考與懸空參考

函式也可以回傳參考,呼叫的結果就是某個物件本身,可以直接放在 = 的左邊修改它:

int& largest(vector<int>& v);   // 回傳最大的元素本身
largest(v) = 0;                 // 把最大的元素改成 0

條件是:回傳的物件在函式結束之後仍然存在,例如透過參考參數傳進來的物件、或它的元素。

絕對不要回傳區域變數的參考:函式結束時區域變數就消失了,呼叫端拿到的參考綁定的是已經不存在的東西,稱為懸空參考(dangling reference), 和第 8 課的懸空指標是同一種錯誤。g++ 預設就會對這種寫法提出警告,不要忽略它。

回傳參考:可以與不可以

largest 回傳的是 v[1] 本身,所以第 23 行把 0 寫進了 vector。badRef 返回時框架消失,local 也跟著消失,動畫會在第 25 行停下來說明懸空參考。

參考與指標的比較

參考在多數編譯器裡是用位址實作的,但在語言層面,它和指標的用法很不一樣:

參考 int& r = x; 指標 int* p = &x;
宣告時 一定要綁定 可以不初始化(但不建議),也可以是 nullptr
能不能是空的 不能 可以是 nullptr,使用前要檢查
能不能改指向 不能改綁 可以:p = &y;
存取值 直接寫 r 要寫 *p
取位址 &r 就是 &x p 本身有自己的位址 &p
指標運算 沒有 有:p + 1、p++

選擇的原則:能用參考就用參考(函式參數、range-for);需要表達「可能沒有」(nullptr)、需要改指向別的物件、或要做指標運算時,才用指標。

想一想:const 參考、range-for 與指標的比較

1.

下面的程式會印出什麼?

vector<int> v = {1, 2, 3};
for (int x : v) {
    x += 10;
}
for (int& y : v) {
    y += 1;
}
cout << v[0] + v[2];
2.

下面的程式會印出什麼?

void show(const string& s) {
    s += "!";
    cout << s;
}
int main() {
    string name = "Amy";
    show(name);
}
3.

關於參考與指標,下列敘述何者錯誤?

4.

vector<string> names 有很多很長的名字,只要把它們一個一個印出來,迴圈變數最適合哪一種寫法?

第 1 次作答

動手寫:調分與計算及格人數

完成兩個函式:curve 用 range-for 搭配參考,把每個分數加上 bonus,超過 100 的改成 100;countPassed 只讀不改,回傳 60 分以上的人數。注意兩個函式的參數寫法不同。main 已經寫好。

你的輸出
預期輸出

重點回顧

  • 參考是變數的別名:int& r = x; 之後 r 就是 x,兩者位址相同
  • 參考宣告時一定要綁定,之後不能改綁;r = y; 是指定值,不是改綁
  • 傳參考讓函式修改呼叫端的變數,也能用來帶回多個結果;呼叫時不用寫 &
  • 只讀的較大物件(string、vector)用 const 參考傳遞:不複製、也不能改;const 參考可以綁定字面常數
  • range-for:int x 是複本,auto& x 可以修改元素,const auto& x 只讀不複製
  • 只能回傳「函式結束後仍然存在」的物件的參考;回傳區域變數的參考會產生懸空參考
  • 能用參考就用參考;需要 nullptr、改指向或指標運算時才用指標
這一課完成了!
Next lesson:物件導向基礎
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。