第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引用是基础,指针与引用的对比帮你做选择,constexpr则是进阶玩法。每个知识点都有它的应用场景。
我的建议:刚开始学C++时,先掌握引用和const引用。等你写了几千行代码后,再深入研究constexpr。别一口吃成胖子。
好了,这一章的内容就到这里。记住:引用是C++的精华之一,用好了能让你的代码既安全又高效。下次写函数时,记得先想想——这个参数该用引用还是指针?