Kodewick
第 10 课,共 30 课目录

引用

第 10 课,共 30 课 · 第 2 部分 · 流程和函数 · 约 15 分钟

上一课说过:传值时,函数拿到的是一份复制,改不到外面的变量。这一课讲 C++ 的另一种办法:引用。引用就是一个变量的另一个名字。

main.cpp
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 指向的是谁。

一个东西,两个名字

通过哪个名字改,另一个名字看到的都一样:

main.cpp
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}

引用参数:改到外面

引用真正好用的地方是函数参数。参数写成引用,它就是调用时那个变量的另一个名字,函数里改它,外面的变量就变了:

main.cpp
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。

交换两个数

交换两个变量的值,是引用参数的经典例子。传值的话,函数换的只是两份复制;用引用,换的就是外面的两个变量:

main.cpp
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& 参数要的是一个能改的变量。给它一个数,或者一个算式的结果,编译器不答应:

main.cpp
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 表示函数只看不改,改了就是编译错误。

main.cpp
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

一个函数可以混着用:

main.cpp
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 要一路加上去,用引用。

练一练

写好代码,点检查。提示随便看,实在想不出来再看答案。

练习 1

清零

写一个函数 reset,用引用参数把外面的变量改成 0。程序要显示 0。

要显示
0
exercise1.cpp
1#include <iostream>2 3int main() {4    int count = 42;5    reset(count);6    std::cout << count << '\n';7    return 0;8}
输出
点「运行」或按 Ctrl+Enter
练习 2

把小的放前面

写一个函数 sort_two(int& a, int& b):如果 a 比 b 大,就交换它们。调用以后 low 是小的那个。程序要显示 4 9。

要显示
4 9
exercise2.cpp
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}
输出
点「运行」或按 Ctrl+Enter
练习 3

修好它:分数没有加上

apply_curve 本想给分数加 5 分,可分数还是 70。只加一个字符,让程序显示 75。

要显示
75
exercise3.cpp
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}
输出
点「运行」或按 Ctrl+Enter

这一课学会了

  • 引用是变量的另一个名字:int& ref = score;,通过哪个名字改都一样。
  • 引用声明时就要绑定(int& ref; 是编译错误),绑定以后不能换;ref = y 是把值复制过去。
  • 参数写成引用(int& score),函数就能改到外面的变量;swap 是经典例子,标准库有 std::swap。
  • int& 只能绑定到变量,不能绑定到 90 或 score + 5 这样的临时值。
  • const std::string&:不复制、也不改,还能接临时的值;大的东西只看不改时这样传。
  • 小类型只看就传值,大的只看传 const 引用,要改就传引用。