C++ 引用

在日常生活中,一个人除了身份证上的正式名字之外,往往还会有一个小名。比如正式名字叫 “张小明”,小名叫 “明明”。不管别人喊哪一个名字,指的其实都是同一个人,如下图所示。

C++ 引用就像给同一个变量起别名

在 C++ 中,“引用(reference)” 也是同样的道理。简单来说,引用就是给一个已经存在的变量起一个 “别名”。一旦绑定成功,引用名和原变量名代表的就是内存中的同一个变量,程序不会把原变量的值重新复制一份。

C++ 引用的基本使用

在 C++ 中,我们可以使用 & 符号来定义一个引用,让它绑定到某个已经存在的变量上。

语法:

类型名& 引用名 = 原变量名;

说明:

在上面的语法中,类型名& 表示对应类型的引用。比如 int& 表示整型引用,而 double& 表示双精度浮点型引用。

需要注意的是,这里的 & 跟在数据类型后面,作用是声明引用,并不是执行按位与运算。虽然写成 int &ref = score 在语法上也合法,但我们更推荐写成 int& ref = score。这样可以把 int& 看作一个整体,直观看出它是整型引用。

提示 在阅读 C++ 代码时,我们经常会看到使用 ref 或 r 作为引用的名字。其中,ref 就是英文 “reference(引用)” 的缩写。

既然引用和原变量代表内存中的同一个变量,那么通过引用不仅可以直接读取原变量的值,修改引用时原变量的值也会同步改变。

示例 1:定义并使用引用

#include <iostream>

int main() {
    int score = 80;
    int& ref = score;    // 给 score 起别名
    std::cout << score << ' ' << ref << '\n';

    ref = 95;            // 通过引用修改值
    std::cout << score << '\n';

    score = 100;         // 直接修改原变量
    std::cout << ref << '\n';
    return 0;
}

运行结果如下。

80 80
95
100

分析:

int& ref = score; 表示给整型变量 score 起了一个别名 ref。这里小伙伴们不要把 ref 理解成 “又新开辟了一块内存保存 80”,实际上 score 和 ref 访问的是内存中的同一块空间。

执行 ref = 95; 时,修改 ref 就相当于直接修改 score,因此输出 score 的结果变成了 95。随后执行 score = 100; 修改原变量,再去读取 ref 时,得到的结果也跟着变成了 100。

这和我们在范围 for 循环中写 for (auto& x : nums) 是一个道理。加上 & 之后,x 就成了当前元素的引用。在循环体内修改 x,原容器里的元素也会直接改变。

C++ 引用的注意事项

在使用 C++ 引用时,我们需要严格遵守以下 3 条语法规则:

  • 定义时必须同时初始化:我们在声明引用时必须立即指明它绑定哪个变量,不能先声明一个空引用再去赋值。此外,普通的非 const 引用只能绑定有名字的变量,不能直接绑定字面量。
  • 引用类型必须与原变量一致:比如 int 类型的变量只能使用 int& 绑定,不能使用 double& 绑定。这是因为不同数据类型占用的字节数和存储格式不同。
  • 一旦绑定就不能改绑其他变量:引用在初始化绑定某个变量后,就会一直与该变量绑定在一起,中途不能再改成其他变量的别名。

对于前两条规则,我们可以对比一下正确写法与常见的错误写法。

int age = 20;

// 正确方式
int& ref1 = age;

// 错误方式 1
int& ref2;

// 错误方式 2
int& ref3 = 100;

// 错误方式 3
double& ref4 = age;

在上面的代码中,错误方式 1 定义引用时没有初始化,错误方式 2 尝试把普通引用直接绑定到字面量 100 上,错误方式 3 则使用 double& 去绑定 int 变量。这 3 种写法都会直接导致编译报错。

为什么普通引用不能绑定字面量 100 呢?这是因为普通引用允许修改目标的值。假如允许绑定 100 并执行 ref3 = 200,就相当于要把常数 100 本身改成 200,这显然是不合逻辑的。

至于第 3 条规则 “一旦绑定就不能改绑其他变量”,初学者最容易把它和普通的变量赋值搞混。看到 r = b; 这样的代码时,不少人会误以为这是把引用 r 改绑到了变量 b 身上。下面我们通过一个例子来验证一下。

示例 2:给引用重新赋值

#include <iostream>

int main() {
    int a = 10;
    int b = 20;

    int& r = a;    // r 绑定到变量 a
    r = b;         // 把 b 的值赋给 a
    b = 30;

    std::cout << "a = " << a << '\n';
    std::cout << "b = " << b << '\n';
    std::cout << "r = " << r << '\n';
    return 0;
}

运行结果如下。

a = 20
b = 30
r = 20

分析:

从运行结果可以看出,虽然把 b 的值修改成了 30,但最后输出 r 的结果依然是 20,与 a 的值完全一致。

这是因为 int& r = a; 已经让引用 r 固定绑定了变量 a,后续执行 r = b; 并不会改变 r 的绑定对象。对于这个例子来说,下面 2 种方式是等价的。

// 方式 1
r = b;

// 方式 2
a = b;

也就是说,r = b; 只是把 b 当前的值 20 赋给了 a,而 r 始终还是 a 的别名。如果我们在程序中确实需要 “先关联变量 a,中途再换成关联变量 b”,单靠引用是做不到的,需要借助指针来实现。

引用作为函数参数

在同一个函数内部,我们直接读写原变量名就可以了,平时很少特意给局部变量再起一个别名。在实际开发中,引用最主要的用武之地是作为函数的形参。

普通的值传递只会把实参复制一份,在函数内修改形参不会影响外部变量。而在函数的引用传递中,引用形参直接绑定外部实参,可以直接修改外部变量的值。

除此之外,C++ 函数的 return 语句一次只能返回一个结果。如果我们希望函数一次算出最小值和最大值等多个结果,也可以通过多个引用参数把结果直接写回外部变量中。

示例 3:通过引用参数传回多个结果

#include <iostream>

void getMinMax(int a, int b, int& minVal, int& maxVal) {
    if (a < b) {
        minVal = a;
        maxVal = b;
    } else {
        minVal = b;
        maxVal = a;
    }
}

int main() {
    int small = 0;
    int large = 0;
    getMinMax(35, 18, small, large);

    std::cout << "最小值:" << small << '\n';
    std::cout << "最大值:" << large << '\n';
    return 0;
}

运行结果如下。

最小值:18
最大值:35

分析:

在这个例子中,getMinMax() 的前两个形参 a 和 b 采用普通的值传递,用来接收待比较的两个整数。后两个形参 minVal 和 maxVal 则是引用,用来把计算结果传回函数外部。

调用 getMinMax(35, 18, small, large) 时,minVal 绑定了实参 small,maxVal 绑定了实参 large。因此在函数内给 minVal 和 maxVal 赋值 18 和 35,就直接改写了 main() 里的 small 和 large。

常量引用(const 引用)

在定义引用时,如果在类型名前面加上 const 关键字,就叫做 “常量引用”(平时也常简称为 const 引用)。因为引用本身就不能改绑其他变量,所以加上 const 的作用只有一个:禁止通过该引用修改原变量的值,相当于给目标起了一个 “只读别名”。

语法:

const 类型名& 引用名 = 目标值;

说明:

定义了 const int& ref = score; 之后,我们可以通过 ref 读取 score 的值,但不能写 ref = 90 去修改它。需要注意的是,const 引用只是锁住了 ref 这条修改通道。如果原变量 score 本身是普通变量,我们依然可以通过 score = 90 直接修改原变量。

此外,const 引用不仅可以绑定普通变量,还可以绑定常量或字面量(如 60)。因为 const 引用只能读取、不能修改,不会破坏原有的常量值。遇到字面量时,编译器会自动创建一个临时只读空间来配合绑定,所以这种写法是完全合法的。

示例 4:定义并使用 const 引用

#include <iostream>

int main() {
    int score = 80;
    const int& ref = score;       // 绑定普通变量
    std::cout << ref << '\n';

    score = 90;                   // 直接修改原变量
    std::cout << ref << '\n';

    const int MAX_SCORE = 100;
    const int& r1 = MAX_SCORE;    // 绑定 const 常量
    const int& r2 = 60;           // 绑定整数字面量
    std::cout << r1 << ' ' << r2 << '\n';
    return 0;
}

运行结果如下。

80
90
100 60

分析:

const int& ref = score; 为普通变量 score 创建了一个只读别名 ref。直接执行 score = 90; 修改原变量后,再次读取 ref 也会同步得到最新的 90。同时,只读引用 r1 和 r2 也顺利绑定了常量 MAX_SCORE 与整数字面量 60。

反过来,如果我们尝试通过 const 引用修改目标值,或者使用普通引用 int& 去绑定 const 常量,都会直接导致编译报错。下面这两种写法就是错误的。

// 错误方式 1
ref = 90;

// 错误方式 2
int& r3 = MAX_SCORE;

可能有些小伙伴会问:“既然直接写 const int r2 = 60 也能定义常量,那 const 引用到底有什么用呢?”

其实在日常项目开发中,const 引用最常见的用途是作为函数的 “只读形参”,用来传递 string 字符串或 vector 容器等占用内存较大的数据。

示例 5:函数的 const 引用参数

#include <iostream>
#include <string>

void printWelcome(const std::string& name) {
    std::cout << "欢迎你," << name << '\n';
}

int main() {
    std::string user = "Jack";
    printWelcome(user);      // 传入 string 变量
    printWelcome("Lucy");    // 直接传入字符串字面量
    return 0;
}

运行结果如下。

欢迎你,Jack
欢迎你,Lucy

分析:

在这个例子中,我们将 printWelcome() 的形参定义为 const std::string& name,这样做有以下 3 个明显的好处:

  • 省去复制开销:传入 user 时直接绑定原字符串,不需要把整段文本重新复制一份,运行效率更高。
  • 防止误改原数据:加上 const 后,函数内部只能读取 name。如果不小心写了修改代码,编译器会直接报错提醒。
  • 传参更加灵活:既能接收现成的 std::string 变量(如 user),也能直接接收字符串字面量(如 "Lucy"),省去了先定义临时变量的麻烦。如果换成普通引用 std::string&,调用 printWelcome("Lucy") 就会编译报错。

因此在 C++ 实际开发中,大家只需要记住一个实用原则:当函数只需要读取 std::string、std::vector 等较大数据时,形参优先写成 const 引用。而传递 int、double 这类占内存很小的基本类型时,直接按值传递即可。

上一篇:

下一篇:

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号