CC++ & Algorithm

C++引用传递

困难23
语言版本:C++Python
概述:引用传递就像你和你朋友共用同一本作业本,你写错了,他那里也会错。

引用传递:别让复制拖慢你的程序

你有没有和朋友共用过一本书?你翻到第50页,朋友翻到的也是第50页。你在页边画了个小星星,朋友翻开也能看到。如果朋友把星星擦掉,你看到的也会消失。
在C++里,引用传递就像这样——函数里的参数和外面的变量是同一个东西,没有复制品。你改函数里的参数,外面的变量也会跟着变。这比复制一份数据快得多,但也需要小心:你不想改的东西,可能一不小心就被改了。

什么是引用传递?——你的变量有了“小名”

每个人都有自己的名字,但你的好朋友可能给你起了一个外号。叫外号的时候,指的仍然是同一个人。
引用(Reference) 就是变量的“外号”。当你把变量通过引用传给函数时,函数内部用的参数名只是外号,实际操作的是原来那个变量本身。

看个生活中的例子:
假设你有一张记满零花钱本子的纸,上面写着“口袋里的钱:20元”。你参加了一个“变有钱”魔术活动,魔术师说“给我你钱的别名”,然后他对着这个别名说“翻倍!”——实际上他操作的是你的真实零花钱,所以你的钱就变成了40元。
如果他用的是复制(值传递),他只能复制一张新纸,在上面写“40元”,而你原来的纸上还是20元。

关键点

  • 引用不新分配内存,它只是已有内存的另一个名字。
  • 函数里对引用参数的修改,直接影响函数外面原来的变量。

引用传递怎么写?——&符号是钥匙

在函数定义中,参数类型后面加一个 & 符号,就表示这个参数是引用。比如 int &a 表示 a 是某个整型变量的引用。

#include <iostream>
using namespace std;

// 值传递:交换的是副本,不影响原件
void swapByValue(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
    // 这里 a 和 b 是拷贝的,改完就销毁了
}

对比引用传递:

#include <iostream>
using namespace std;

// 引用传递:交换的是原件
void swap(int &a, int &b) {
    int temp = a;
    a = b;
    b = temp;
    cout << "函数内部:a = " << a << ", b = " << b << endl;
}

int main() {
    int x = 5, y = 10;
    cout << "调用前:x = " << x << ", y = " << y << endl;
    swap(x, y);  // 传递的是 x 和 y 的“别名”
    cout << "调用后:x = " << x << ", y = " << y << endl;
    return 0;
}

运行结果:
调用前:x = 5, y = 10
函数内部:a = 10, b = 5
调用后:x = 10, y = 5

看到了吗?swap函数里交换的 ab 就是外面的 xy,所以外面也交换了。

引用传递有什么好处?——省时省力还能直接改

1. 避免复制大块数据

假设你有一个超长的数组(比如几百个学生的考试成绩),如果按值传递,函数会复制整个数组,既慢又占内存。用引用传递,只传递一个“别名”,什么都不用复制。

#include <iostream>
using namespace std;

// 引用方式打印数组,不复制
void printArray(const int arr[], int size) {
    // 注意:数组本身传递时是地址,但这里用 const 保护防止误改
    for (int i = 0; i < size; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
}

其实数组名本身就是地址,但引用同样能避免复制。

2. 直接修改外部变量

像上面的交换函数,或者修改成绩、增加零花钱等,用引用比用指针更直观,写法简单。

#include <iostream>
using namespace std;

// 给你的零花钱加 5 元,直接改原件
void addMoney(int &money) {
    money += 5;
}

int main() {
    int pocketMoney = 20;       // 零花钱
    cout << "原来有:" << pocketMoney << " 元" << endl;
    addMoney(pocketMoney);      // 传入引用
    cout << "现在有:" << pocketMoney << " 元" << endl;
    return 0;
}

输出:
原来有:20 元
现在有:25 元

3. 让函数返回多个值

一个函数只能 return 一个值,但用引用参数可以“带出”多个结果。比如同时计算全班平均分和最高分:

#include <iostream>
using namespace std;

// 计算平均分和最高分,通过引用输出两个结果
void calcStats(int scores[], int n, double &avg, int &maxScore) {
    int sum = 0;
    maxScore = scores[0];
    for (int i = 0; i < n; i++) {
        sum += scores[i];
        if (scores[i] > maxScore) maxScore = scores[i];
    }
    avg = (double)sum / n;
}

int main() {
    int scores[] = {88, 76, 95, 62, 81};    // 考试成绩
    int stuNum = 5;                         // 学生人数
    double average;                         // 平均分
    int highest;                            // 最高分

    calcStats(scores, stuNum, average, highest);  // 通过引用带回结果
    cout << "平均分:" << average << ",最高分:" << highest << endl;
    return 0;
}

输出:
平均分:80.4,最高分:95

新手最容易犯的错误

错误1:忘记写 &,变成值传递

这是最常见的坑。你明明想改原件,却复制了一份副本。

// 错误版本:没有 &
void wrongAdd(int a) {   // a 是副本
    a += 10;
}

int main() {
    int num = 5;
    wrongAdd(num);
    cout << num;  // 输出 5,没变!
    return 0;
}

正确写法:void rightAdd(int &a) { a += 10; }

错误2:引用绑定到临时变量(临时值)

引用必须绑定到一个变量上,不能绑定到一个计算结果或字面量上。

int &ref = 5;        // 错误!5 不是变量
int &ref2 = (3 + 4); // 错误!临时值不行

正确的引用必须绑定到一个有名字的内存变量上:

int x = 5;
int &ref = x;  // 正确

错误3:误以为引用和指针一样(但别担心,四级不要求指针)

引用比指针更安全,因为它一旦绑定就不能再指向别的变量。但新手容易混淆“别名”和“地址”。记住:引用就是同一个东西的不同名字。

完整示例:用引用传递修改考试成绩

小明考试得了78分,老师想让他加10分作为进步奖。我们用引用传递直接修改:

#include <iostream>
using namespace std;

// 给成绩加分,直接改原变量
void addBonus(int &score, int bonus) {
    score += bonus;
}

int main() {
    int xiaoMingScore = 78;     // 小明的成绩
    int xiaoHongScore = 92;     // 小红的成绩

    cout << "加分前:小明 " << xiaoMingScore << " 分,小红 " << xiaoHongScore << " 分" << endl;

    addBonus(xiaoMingScore, 10);   // 小明加10分
    addBonus(xiaoHongScore, 5);    // 小红加5分

    cout << "加分后:小明 " << xiaoMingScore << " 分,小红 " << xiaoHongScore << " 分" << endl;
    return 0;
}

运行结果:
加分前:小明 78 分,小红 92 分
加分后:小明 88 分,小红 97 分

相关知识点指引

  • 值传递:和引用传递相反,复制一份副本,函数内修改不影响外面。适合小数据且不想改变原值的情况。
  • 常量引用const int &):既能避免复制,又能防止函数内意外修改原值。比如 void show(const int &a) 只能读取不能修改。
  • 返回引用:函数可以返回引用(比如 int& getValue(int arr[], int idx)),让调用者能直接修改数组元素。但要注意不要返回局部变量的引用。
  • 指针(更高一级):也能达到类似效果,但语法更复杂。引用可以理解为“自动解引用的指针”,更安全易用。

现在你可以自信地说:“引用传递就是给变量取了一个小名,函数里叫小名,其实叫的就是你原来的名字!”

例题精讲

1单选题

关于C++中的引用传递,以下说法正确的是?

A引用传递时,形参是实参的副本,修改形参不会影响实参
B引用传递时,形参是实参的别名,修改形参会直接影响实参
C引用传递时,形参和实参共享同一块内存,但可以重新绑定到其他变量
D引用传递实际上是通过指针实现的,但引用本身可以为空
2判断题

在C++中,引用传递和指针传递都能在函数内部修改外部变量的值,但引用更安全且语法更简洁。

3填空题
以下函数使用引用交换两个整数的值,请补全代码。
void swap(int &a, int &b) {
    int temp = ___;
    a = b;
    b = ___,
}
4单选题

执行以下代码后,变量x的值是多少? void func(int &y) { y += 10; } int main() { int x = 5; func(x); return 0; }

A5
B10
C15
D不确定
5填空题
下列代码中,函数返回一个引用,请补全函数定义,使其能够返回数组元素的引用并修改该元素。
int& getElement(int arr[], int index) {
    return ___;
}
int main() {
    int nums[] = {1,2,3};
    getElement(nums, 1) = 100;
    // 现在nums[1]的值为100
    return 0;
}