11.3 指標與函式
上冊 7.5 教過一件事:函式參數預設是複本,函式裡改了,外面的變數不痛不癢;想真的改到外面,參數要加 & 變成參考。
這一節要補的是第三種寫法:把「位置」傳進函式。理由有兩個——別人的程式碼(尤其 C 語言寫的)滿滿都是這種寫法,你要看得懂;而且上冊 7.7 那三件關於陣列參數的怪事,答案全在這裡。
¶一個交換函式,三種寫法
以「交換兩個變數」為例(內建的 swap 先擺一邊,我們自己寫):
#include <iostream>
using namespace std;
// 版本 1:傳值——只換到複本
void swap_value(int a, int b) {
int tmp = a;
a = b;
b = tmp;
}
// 版本 2:傳參考——C++ 的做法
void swap_ref(int &a, int &b) {
int tmp = a;
a = b;
b = tmp;
}
// 版本 3:傳位置——C 語言只能這樣寫
void swap_ptr(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
int main() {
int x = 2, y = 1;
swap_value(x, y);
cout << x << ' ' << y << '\n'; // 2 1:沒換到
swap_ref(x, y);
cout << x << ' ' << y << '\n'; // 1 2:換到了
swap_ptr(&x, &y);
cout << x << ' ' << y << '\n'; // 2 1:也換到了
return 0;
}
執行結果:
2 1
1 2
2 1
三個版本並排比較:
| 版本 | 參數寫法 | 呼叫時寫 | 函式裡動的是 | 換得到嗎 |
|---|---|---|---|---|
| 傳值 | int a |
swap_value(x, y) |
a(複本) |
✗ |
| 傳參考 | int &a |
swap_ref(x, y) |
a(就是外面的 x) |
✓ |
| 傳位置 | int *a |
swap_ptr(&x, &y) |
*a(照位置找到的 x) |
✓ |
指標版的關鍵在這句話:位置本身確實被複製了一份,但複本裡寫的還是同一個門牌號碼。函式拿著這張紙條走過去,開的是同一扇門——所以 *a = ... 改到的是外面的 x。傳值版之所以失敗,是因為它複製的是「值」本身,複本跟本尊從此毫無關係。
也因此,指標版有兩個地方跟參考版長得不一樣,一個都不能漏:呼叫時要寫 &x(把位置交出去)、函式裡每次用都要寫 *a(照位置取值)。
動手試試看:交換指標指向的整數 要你交出的就是上面的版本 3——評分程式會呼叫 int_swap(&a, &b);,你得讓外面的 a、b 真的互換。⚠️ 這題明文禁止呼叫內建的 swap(會檢查原始碼),就是要你自己用暫存變數配 * 完成,正好把這一節的手感練起來。
¶陣列參數的真相
上冊 7.7 留下三件說不通的怪事:陣列參數明明是傳值,函式裡卻改得動;中括號裡的大小寫了會被忽略;sizeof(arr) 在函式裡永遠是 8。
其實它們是同一件事的三個側面——寫成 int arr[] 的參數,編譯器一律當成 int *arr:
#include <iostream>
using namespace std;
void fill_ones(int *a, int n) { // 寫成 int a[] 意思完全一樣
for (int i = 0; i < n; i++) {
a[i] = 1; // a[i] 就是 *(a + i)
}
}
int main() {
int a[3] = {7, 8, 9};
fill_ones(a, 3);
cout << a[0] << ' ' << a[1] << ' ' << a[2] << '\n';
return 0;
}
執行結果:
1 1 1
於是三件怪事都有了答案:
- 為什麼改得動:函式收到的是陣列開頭的位置,順著位置找過去就是外面那片記憶體——跟
swap_ptr是同一招。 - 為什麼大小可以省:傳進去的從頭到尾只有一個位置,長度根本沒跟著走,寫了也沒用;所以長度要自己另外傳一個
n。 - 為什麼
sizeof是 8:量到的是「一個位置」的大小(64 位元電腦上是 8 個位元組),跟陣列多長無關。
動手試試看:指標反轉陣列 把這一節整個包起來考——函式收到的是 head 與 tail 兩個位置(陣列的第一格與最後一格),要把陣列原地反轉。做法是兩個指標往中間夾:把 *head 與 *tail 交換(拿個暫存變數,跟 swap_ptr 一模一樣),然後 head++、tail--,重複到兩個指標碰頭為止。順帶學到一件新事:同一個陣列裡的指標可以互相比大小,位置在前面的比較小,所以迴圈條件就寫 while (head < tail)。
動手試試看:把 swap_ptr 裡的 *a = *b; 少打一個星號改成 a = b;,猜猜會發生什麼事?(結果是印出 2 2——a = b; 只是把函式手上那張紙條改成指向 y,外面的 x 一動也沒動,接著 *b = tmp; 又把 y 寫成了 2。)再試試看把三行的星號全部拿掉,這次連編譯都過不了,錯誤訊息是 invalid conversion from 'int*' to 'int'——因為 int tmp = a; 想把一個位置塞進整數變數裡。