C++ 引用

引用(reference)是 C++ 区别于 C 的第一项重大改进。简单说,引用就是"变量的别名"。它和指针有相似的作用(都能间接操作变量),但更安全、更优雅。这一章我们看引用的所有用法,以及它和指针的本质区别。

一、引用的基本概念

引用就是给已存在的变量起个别名。语法是在类型后加 &:

#include <iostream>
using namespace std;

int main() {
    int x = 10;

    // 引用:给变量起别名,& 在类型后
    int& ref = x;     // ref 是 x 的别名

    cout << x << endl;     // 10
    cout << ref << endl;   // 10(同一份数据)

    ref = 20;              // 通过 ref 修改,实际改的是 x
    cout << x << endl;     // 20

    // 关键:引用必须初始化,且不能再绑定到别的变量
    // int& bad;            // 编译错误:未初始化
    return 0;
}

引用的三大铁律(牢记):

这三条和指针恰好相反,所以引用比指针安全——不存在空指针解引用、不存在悬空指针重新赋值的问题。

二、引用 vs 指针

引用和指针都能"间接操作变量",但本质完全不同:

#include <iostream>
using namespace std;

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

    // 指针
    int* p = &a;        // p 存储 a 的地址
    cout << *p << endl; // 10(解引用)
    p = &b;             // 可以让 p 指向另一个变量
    cout << *p << endl; // 20
    if (p != nullptr) { // 指针可以为空,使用前要检查
        cout << *p << endl;
    }

    // 引用
    int& r = a;         // r 是 a 的别名
    cout << r << endl;  // 10
    r = b;              // 注意!这是赋值,不是重新绑定
    cout << a << endl;  // 20(a 被改成 20,r 仍然引用 a)
    // 没有"引用为空"的概念
    return 0;
}

对比表:

何时用引用:函数参数(避免拷贝、要修改)、返回值。 何时用指针:数组遍历、可能为空、需要重新指向。能引用就不指针。

三、引用的经典应用:swap

对比指针和引用两种 swap 实现,引用明显更优雅:

#include <iostream>
using namespace std;

// 用指针实现的 swap
void swapP(int* a, int* b) {
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

// 用引用实现的 swap(更优雅)
void swapR(int& a, int& b) {
    int tmp = a;
    a = b;
    b = tmp;
}

int main() {
    int x = 1, y = 2;
    swapP(&x, &y);    // 调用时要传地址
    cout << x << " " << y << endl;   // 2 1

    int m = 5, n = 6;
    swapR(m, n);      // 调用时直接传变量
    cout << m << " " << n << endl;   // 6 5
    return 0;
}

引用的优势一目了然:

四、const 引用:避免拷贝的利器

这是引用最常见的工程用途:

#include <iostream>
#include <string>
using namespace std;

// const 引用最常见的用途:避免拷贝大对象
// 比如传 string/vector/自定义类
void print(const string& s) {
    // s = "modified";   // 编译错误:const 不能修改
    cout << s << endl;
}

// const 引用还能绑定到临时对象(右值)
// 非 const 引用不能绑定右值
int main() {
    string big = "very long string ..........................";
    print(big);              // 传引用,零拷贝

    const int& r = 42;       // OK:const 引用绑定字面量
    cout << r << endl;       // 42

    const string& s = "hi";  // OK:const 引用绑定字符串字面量
    cout << s << endl;       // hi
    return 0;
}

const 引用的两个关键点:

记住口诀:小类型(int/double)按值,大类型(string/vector)按 const 引用

五、函数返回引用:能当左值

返回引用可以让函数调用结果当左值用,但不能返回局部变量的引用:

#include <iostream>
#include <vector>
using namespace std;

// 危险:返回局部变量的引用(悬空引用)
int& bad() {
    int x = 42;
    return x;   // 函数返回后 x 被销毁,引用指向无效内存!
}

// 安全:返回静态/全局变量的引用
int& counter() {
    static int c = 0;
    c++;
    return c;
}

// 安全:返回参数中传入的引用
int& select(int& a, int& b) {
    return a > b ? a : b;
}

int main() {
    // int& r = bad();     // 编译警告,运行时未定义行为
    // cout << r << endl;

    counter() = 100;        // 返回引用可以当左值!把 counter() 改成 100
    cout << counter() << endl;  // 101

    int x = 10, y = 20;
    select(x, y) = 0;       // 把较大的 y 改成 0
    cout << y << endl;      // 0
    return 0;
}

悬空引用(dangling reference)是 C++ 的经典坑:

六、右值引用与 std::move(C++11)

这是 C++11 引入的重要概念,主要用于移动语义(性能优化)。简单理解:

#include <iostream>
#include <string>
#include <utility>   // std::move
using namespace std;

// C++11 引入右值引用(&&),用于实现"移动语义"
// - 左值(lvalue):有名字、有地址,可以取地址。如变量名 x
// - 右值(rvalue):没名字、临时存在。如字面量 42、表达式 x+y

void takeLvalue(int& x) {
    cout << "lvalue ref: " << x << endl;
}

void takeRvalue(int&& x) {
    cout << "rvalue ref: " << x << endl;
}

int main() {
    int a = 10;

    takeLvalue(a);        // OK:a 是左值
    // takeLvalue(42);    // 错误:42 是右值,不能绑定到非 const 左值引用

    // takeRvalue(a);     // 错误:a 是左值,不能绑定到右值引用
    takeRvalue(42);       // OK:42 是右值
    takeRvalue(a + 1);    // OK:a+1 是右值

    // std::move:把左值"强转"成右值,允许移动
    string s1 = "hello";
    string s2 = move(s1);   // s1 的内容被"搬走"给 s2
    cout << s2 << endl;     // hello
    cout << s1.size() << endl; // 0(s1 进入"有效但未指定"状态)
    return 0;
}

左值和右值的区分:

七、为什么要有移动语义?

这是 C++11 最重要的性能优化:

#include <iostream>
#include <vector>
#include <string>
using namespace std;

// 假设 vector 内部存了一个大缓冲区(比如 1MB)
// 拷贝:重新分配 + 复制 1MB 数据,很慢
// 移动:只把指针挪一下,几乎零成本

vector<string> makeData() {
    vector<string> data(100000, "long string ...");
    return data;   // 返回值:编译器会用移动语义(RVO/NRVO 优化)
}

int main() {
    vector<string> v1 = {string(1000000, 'x')};  // 1MB 字符串
    vector<string> v2 = v1;        // 拷贝:复制 1MB,慢
    vector<string> v3 = move(v1);  // 移动:只搬指针,飞快,v1 变空
    return 0;
}

// 总结:右值引用 + move 语义是 C++11 的核心性能优化
// 让你写"返回大对象"的代码无需担心性能代价

对比拷贝和移动:

移动语义的实际用途:

新手不用一上来就精通移动语义,先记住:函数返回大对象时直接 return,编译器会自动优化

八、引用总结

小结

这一章你认识了 C++ 的引用,知道了它和指针的本质区别(必须初始化/不可改绑定/不为空),学会了用 const 引用做高效参数,接触了右值引用和移动语义。下一篇进入 C++ 的核心——类与对象,开始面向对象编程。

← 上一篇 C++ 函数

下一篇 C++ 类与对象

✈️💬