引用
上一课说过:传值时,函数拿到的是一份复制,改不到外面的变量。这一课讲 C++ 的另一种办法:引用。引用就是一个变量的另一个名字。
1#include <iostream>2 3int main() {4 int score = 80;5 int& ref = score;6 ref += 5;7 std::cout << score << '\n';8 std::cout << ref << '\n';9 return 0;10}
85 85
int& ref = score; 读作「ref 是 score 的引用」。类型后面的 & 表示引用。ref 不是一个新的变量,没有自己的值;它和 score 是同一个东西的两个名字。所以 ref += 5 改的就是 score。点单步,看画面上 ref 指向的是谁。
一个东西,两个名字
通过哪个名字改,另一个名字看到的都一样:
1#include <iostream>2 3int main() {4 double price = 20;5 double& p = price;6 price = 25;7 std::cout << p << '\n';8 p = p * 0.8;9 std::cout << price << '\n';10 return 0;11}
25 20
改 price,p 看到 25;改 p,price 变成 20。拿 int 变量做引用就写 int&,double 就写 double&,std::string 就写 std::string&。
小技巧: int& ref 和 int &ref 是一样的,空格放哪边都行。这门课把 & 贴着类型写。
一开始就绑定,以后不换
引用在声明时就必须说清楚是谁的名字。只写 int& ref; 是编译错误,g++ 会说 'ref' declared as reference but not initialized(声明了引用却没有初始化)。
绑定以后,它一辈子都是那个变量的名字,换不了。之后再写 ref = 别的变量,只是把值复制过去:
这段程序显示什么?
1#include <iostream>2 3int main() {4 int x = 3;5 int y = 8;6 int& r = x;7 r = y;8 y = 100;9 std::cout << x << " " << r << '\n';10 return 0;11}
r 一直是 x 的名字。r = y 不是让 r 改去当 y 的名字,而是把 y 的值 8 存进 x。后来 y 变成 100,和 x 没关系,所以 x 和 r 都是 8。
引用参数:改到外面
引用真正好用的地方是函数参数。参数写成引用,它就是调用时那个变量的另一个名字,函数里改它,外面的变量就变了:
1#include <iostream>2 3void add_bonus(int& score) {4 score += 10;5}6 7int main() {8 int score = 80;9 add_bonus(score);10 std::cout << score << '\n';11 return 0;12}
90
和上一课只差一个 &,结果从 80 变成了 90。点单步:进入 add_bonus 时,画面上多了一层,可里面的 score 不是一个新的数,而是指向 main 里的 score。
调用的地方看不出来参数是不是引用:add_bonus(score) 两种写法都一样。所以要改外面变量的函数,名字最好说清楚它会改,比如 add_bonus、reset。
交换两个数
交换两个变量的值,是引用参数的经典例子。传值的话,函数换的只是两份复制;用引用,换的就是外面的两个变量:
1#include <iostream>2 3void swap_values(int& a, int& b) {4 int temp = a;5 a = b;6 b = temp;7}8 9int main() {10 int x = 3;11 int y = 8;12 swap_values(x, y);13 std::cout << "x = " << x << ", y = " << y << '\n';14 return 0;15}
x = 8, y = 3
点单步,看 a 和 b 分别指向谁,temp 怎么帮忙:先把 a 的值存起来,不然 a = b 以后它就丢了。
标准库里已经有现成的:#include <utility> 以后写 std::swap(x, y);,做的是同样的事。
引用要有一个变量
int& 参数要的是一个能改的变量。给它一个数,或者一个算式的结果,编译器不答应:
1#include <iostream>2 3void add_bonus(int& score) {4 score += 10;5}6 7int main() {8 int score = 80;9 add_bonus(90);10 std::cout << score << '\n';11 return 0;12}
main.cpp: In function 'int main()':
main.cpp:9:15: error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'
9 | add_bonus(90);
| ^~cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int':int& 不能绑定到一个临时的值上。90 不是哪个变量,加了 10 也没地方放,所以不让。add_bonus(score + 5) 也一样。
const 引用
传值会复制一份。一个 int 复制起来不费事,可一个很长的 std::string(以后还有装着几千个元素的容器),每调用一次就复制一遍,就浪费了。
这时用 **const 引用**:const std::string& text。& 表示不复制,直接用外面那个;const 表示函数只看不改,改了就是编译错误。
1#include <iostream>2#include <string>3 4int count_char(const std::string& text, char c) {5 int n = 0;6 for (std::size_t i = 0; i < text.size(); i++) {7 if (text[i] == c) {8 n++;9 }10 }11 return n;12}13 14int main() {15 std::string fruit = "banana";16 std::cout << count_char(fruit, 'a') << '\n';17 std::cout << count_char("apple", 'p') << '\n';18 return 0;19}
3 2
const 引用还有个好处:它可以绑定到临时的值上,所以 count_char("apple", 'p') 这样直接给一个字符串也行。反正不会改它,不怕没地方放。(循环里的 std::size_t 是 size() 给出的类型,一种不会是负数的整数,下一课细讲。)
怎么传参数
| 想要 | 写法 | 例子 |
|---|---|---|
| 只看,类型很小 | 传值 | int n、double price、char c、bool ok |
| 只看,类型可能很大 | const 引用 | const std::string& name |
| 要改外面的变量 | 引用 | int& score |
一个函数可以混着用:
1#include <iostream>2#include <string>3 4void add_item(const std::string& name, double price, double& total) {5 total += price;6 std::cout << name << ":" << price << " 元\n";7}8 9int main() {10 double total = 0;11 add_item("牛奶", 6.5, total);12 add_item("面包", 12, total);13 std::cout << "合计:" << total << " 元\n";14 return 0;15}
牛奶:6.5 元 面包:12 元 合计:18.5 元
name 只看不改,用 const 引用;price 是个小小的 double,传值;total 要一路加上去,用引用。
练一练
写好代码,点检查。提示随便看,实在想不出来再看答案。
清零
✓写一个函数 reset,用引用参数把外面的变量改成 0。程序要显示 0。
0
1#include <iostream>2 3int main() {4 int count = 42;5 reset(count);6 std::cout << count << '\n';7 return 0;8}
把小的放前面
✓写一个函数 sort_two(int& a, int& b):如果 a 比 b 大,就交换它们。调用以后 low 是小的那个。程序要显示 4 9。
4 9
1#include <iostream>2 3int main() {4 int low = 9;5 int high = 4;6 sort_two(low, high);7 std::cout << low << " " << high << '\n';8 return 0;9}
修好它:分数没有加上
✓apply_curve 本想给分数加 5 分,可分数还是 70。只加一个字符,让程序显示 75。
75
1#include <iostream>2 3void apply_curve(int score) {4 score += 5;5}6 7int main() {8 int score = 70;9 apply_curve(score);10 std::cout << score << '\n';11 return 0;12}
这一课学会了
- 引用是变量的另一个名字:
int& ref = score;,通过哪个名字改都一样。 - 引用声明时就要绑定(
int& ref;是编译错误),绑定以后不能换;ref = y是把值复制过去。 - 参数写成引用(
int& score),函数就能改到外面的变量;swap是经典例子,标准库有std::swap。 int&只能绑定到变量,不能绑定到90或score + 5这样的临时值。const std::string&:不复制、也不改,还能接临时的值;大的东西只看不改时这样传。- 小类型只看就传值,大的只看传
const引用,要改就传引用。