第9章 引用与const:C++中的“别名”艺术

说实话,引用这个概念,我当年学C++时觉得它挺鸡肋的。不就是给变量起个外号吗?直到我在项目中遇到一个诡异的bug——函数传参时整个结构体被复制了三次,性能直接崩了。从那以后,我才真正理解了引用的价值。

今天咱们就来聊聊引用、const引用,还有那个让人又爱又恨的constexpr。嗯,内容不少,但都是干货。

9.1 引用的概念与使用

引用,说白了就是给一个已经存在的变量起个“别名”。你叫张三,你朋友叫你“三哥”,你妈叫你“儿子”,都是同一个人。引用就是这个道理。

int a = 10;
int& ref = a;  // ref 是 a 的引用
ref = 20;       // 现在 a 也变成了 20

这里有个关键点:引用必须在定义时初始化,而且一旦绑定,就不能再绑定到别的变量上。我见过不少新手写这样的代码:

int a = 10, b = 20;
int& ref;      // 编译错误!引用必须初始化
ref = a;       // 这行根本不会执行到

我个人习惯把引用用在函数参数上,尤其是传递大对象时。你想想看,如果不加引用,每次调用函数都要复制整个对象,那性能损失可不是闹着玩的。

核心要点:引用就是别名,不占额外内存(从语义上讲),操作引用就是操作原变量。

9.2 const引用

const引用,就是给一个变量起一个“只读”的别名。你可以通过它看数据,但不能改数据。

int a = 10;
const int& cref = a;  // 只读引用
// cref = 20;         // 编译错误!不能通过const引用修改
a = 30;               // 但可以直接修改a

这里有个很实用的场景:函数参数用const引用。既能避免复制开销,又能保证函数内部不会意外修改参数。我在写代码时,只要函数不需要修改参数,一律用const引用。

void printData(const std::vector<int>& data) {
    for (int v : data) {
        std::cout << v << " ";
    }
    // data.push_back(100);  // 编译错误!const引用不能修改
}

const引用还有一个神奇的特性:可以绑定到临时对象。比如:

const int& r = 42;  // 合法!临时对象42的生命周期被延长了

这个特性在函数返回引用时特别有用。我曾经在项目中遇到过一个bug,就是因为返回了局部变量的引用,结果数据全乱了。用const引用绑定临时对象,能避免这类问题。

注意:非const引用不能绑定到临时对象。比如 int& r = 42; 是编译错误。这是C++的设计哲学——你不能修改一个即将消失的东西。

9.3 指针与引用的区别

很多初学者搞不清指针和引用。我当年也迷糊过。其实它们的区别很清晰:

特性 指针 引用
初始化 可以不初始化(但危险) 必须初始化
重新绑定 可以指向不同对象 一旦绑定,不能改变
空值 可以为 nullptr 不能为空
语法 需要 * 解引用 直接使用
内存占用 通常占8字节(64位) 不占额外空间(编译器优化)

我个人经验是:能用引用就别用指针。引用更安全,语法更简洁。但有些场景必须用指针:

  • 需要重新绑定到不同对象时
  • 需要表示“没有对象”时(用nullptr)
  • 需要动态内存分配时(new返回的就是指针)

举个例子,链表操作就离不开指针:

struct Node {
    int data;
    Node* next;  // 必须用指针,因为要指向下一个节点
};

而函数参数传递,我几乎只用引用或const引用。你想想看,如果函数参数用指针,调用时还得写个 & 取地址,多麻烦。

小技巧:如果你在写一个函数,不确定用指针还是引用,先问自己三个问题:1) 参数可以为空吗?2) 需要修改参数本身(而不是指向的内容)吗?3) 调用方是否期望看到 & 符号?答案会帮你做决定。

9.4 constexpr关键字

constexpr是C++11引入的关键字,用来声明编译期常量。说白了,就是告诉编译器:“这个值在编译时就能算出来,你帮我算好,别等到运行时再算。”

constexpr int square(int x) {
    return x * x;
}

int arr[square(5)];  // 合法!square(5)在编译时就算出25了

constexpr和const有什么区别?const只是保证“不修改”,但不保证“编译期可知”。比如:

int x = 10;
const int cx = x;      // 合法,但cx的值在运行时才确定
constexpr int cex = x; // 编译错误!x不是编译期常量

我在项目中用constexpr的场景主要是:

  • 定义数组大小
  • 模板参数
  • 编译期计算(比如查表、哈希)

从C++14开始,constexpr函数可以包含循环和分支了。C++17更是支持了constexpr if。这让编译期计算变得非常强大。

constexpr int factorial(int n) {
    int result = 1;
    for (int i = 2; i <= n; ++i) {
        result *= i;
    }
    return result;
}

static_assert(factorial(5) == 120);  // 编译期验证

重要提示:constexpr函数不一定在编译期执行。如果参数不是编译期常量,它会在运行时执行。所以constexpr函数是“兼容”编译期和运行时的。

知识体系总览

下面这张图帮你理清本章的知识结构:

引用与const 引用概念与使用 必须初始化 · 不能重新绑定 · 别名语义 const引用 只读别名 · 可绑定临时对象 · 函数参数首选 指针 vs 引用:安全性与灵活性 constexpr 编译期常量 · 函数可编译期/运行时 C++11起 · C++14/17增强 常见陷阱 返回局部引用 · 引用空指针 const引用修改原值

这张图把本章的核心知识点串起来了。你看,引用和const引用是基础,指针与引用的对比帮你做选择,constexpr则是进阶玩法。每个知识点都有它的应用场景。

我的建议:刚开始学C++时,先掌握引用和const引用。等你写了几千行代码后,再深入研究constexpr。别一口吃成胖子。

好了,这一章的内容就到这里。记住:引用是C++的精华之一,用好了能让你的代码既安全又高效。下次写函数时,记得先想想——这个参数该用引用还是指针?

公众号:蓝海资料掘金营,微信deep3321