【C++】零基础入门 · 第 12 节:引用与 const 限定符
·
前面我们学了指针——C++ 中最强大也最容易出错的特性。这一节我们来学两个让代码更安全、更易读的工具:引用和 const 限定符。
1. 引用(Reference)
1.1 什么是引用?
引用是变量的「别名」——给一个已存在的变量取另一个名字。对引用的操作,就是对原变量的操作。
int a = 10;
int& b = a; // b 是 a 的引用(别名)
b = 20; // 修改 b,就是修改 a
cout << a; // 输出 20
关键点:
- 引用必须在声明时初始化
- 引用一旦绑定到某个变量,就不能再绑定到其他变量
- 引用不是新变量,它不占用额外的存储空间(概念上)
1.2 引用 vs 指针
引用和指针都能间接访问变量,但有本质区别:
int a = 10;
int* p = &a; // 指针:需要取地址,需要解引用
int& r = a; // 引用:直接用,语法更简洁
*p = 20; // 指针需要 * 来解引用
r = 30; // 引用直接用,就像原变量一样
| 特性 | 引用 | 指针 |
|---|---|---|
| 初始化 | 必须在声明时初始化 | 可以不初始化(但不推荐) |
| 可否为空 | 不能为 null | 可以为 null |
| 可否改变指向 | 不能 | 可以 |
| 语法 | 和原变量一样 | 需要 * 和 & |
| 安全性 | 更安全 | 容易出错 |
简单规则:能用引用就不用指针。
1.3 引用作为函数参数
引用最常见的用途是作为函数参数,实现「传引用」:
// 传值:函数内修改不影响外部
void swapBad(int a, int b) {
int temp = a;
a = b;
b = temp;
// 外部的 a 和 b 不会变!
}
// 传引用:函数内修改影响外部
void swapGood(int& a, int& b) {
int temp = a;
a = b;
b = temp;
// 外部的 a 和 b 交换了!
}
int main() {
int x = 10, y = 20;
swapGood(x, y);
cout << x << " " << y; // 输出:20 10
}
1.4 引用作为函数返回值
函数可以返回引用,但要注意:不要返回局部变量的引用!
// ❌ 错误:返回局部变量的引用
int& badFunction() {
int local = 42;
return local; // local 在函数结束后被销毁,引用变成悬空引用
}
// ✅ 正确:返回全局变量或静态变量的引用
int globalVar = 100;
int& getGlobal() {
return globalVar; // 全局变量不会被销毁
}
// ✅ 正确:返回参数的引用
int& getElement(vector<int>& arr, int index) {
return arr[index]; // 数组元素在调用者的作用域中
}
返回引用的函数调用可以出现在赋值号左边:
vector<int> arr = {1, 2, 3, 4, 5};
getElement(arr, 2) = 99; // arr[2] 变成 99
cout << arr[2]; // 输出 99
1.5 const 引用
用 const 修饰引用,表示「只读」:
void printValue(const int& x) {
// x = 100; // ❌ 编译错误:不能修改 const 引用
cout << x; // ✅ 只能读取
}
int main() {
int a = 42;
printValue(a); // 正常传入
printValue(100); // 也可以传入字面量!
// 普通引用不能绑定到字面量,const 引用可以
}
const 引用的好处:
- 防止函数意外修改参数
- 避免拷贝,提高效率
- 可以绑定到字面量和临时对象
2. const 限定符
2.1 const 变量
const 表示「常量」,一旦初始化就不能修改:
const int MAX_SIZE = 100;
// MAX_SIZE = 200; // ❌ 编译错误
const string NAME = "Alice";
// NAME = "Bob"; // ❌ 编译错误
const 变量必须在声明时初始化,因为之后无法赋值。
2.2 const 与指针
const 和指针的组合是初学者最容易混淆的地方。记住一个口诀:const 在 * 左边,指向的内容不能改;const 在 * 右边,指针本身不能改。
int a = 10, b = 20;
// 1. 指向 const 的指针(内容只读)
const int* p1 = &a;
// *p1 = 30; // ❌ 不能通过 p1 修改 a
p1 = &b; // ✅ p1 可以指向其他变量
a = 30; // ✅ 直接修改 a 是可以的
// 2. const 指针(指针本身只读)
int* const p2 = &a;
*p2 = 30; // ✅ 可以通过 p2 修改 a
// p2 = &b; // ❌ p2 不能指向其他变量
// 3. 指向 const 的 const 指针(都不能改)
const int* const p3 = &a;
// *p3 = 30; // ❌
// p3 = &b; // ❌
记忆方法:
const int*:const 修饰 int,所以 int 不能改 → 内容只读int* const:const 修饰int*,所以指针不能改 → 指针只读
2.3 const 成员函数
在类中,const 放在函数声明末尾,表示这个函数不会修改对象的状态:
class Circle {
private:
double radius;
public:
Circle(double r) : radius(r) {}
// const 成员函数:不会修改对象
double getArea() const {
// radius = 10; // ❌ 不能在 const 函数中修改成员变量
return 3.14159 * radius * radius;
}
// 非 const 成员函数:可以修改对象
void setRadius(double r) {
radius = r;
}
};
void printArea(const Circle& c) {
// c 只能调用 const 成员函数
cout << c.getArea() << endl;
// c.setRadius(5); // ❌ 不能调用非 const 函数
}
规则:如果一个函数不修改对象状态,就把它声明为 const。这样它就能被 const 引用和 const 指针调用。
2.4 常量表达式(constexpr)
C++11 引入了 constexpr,比 const 更严格:
const int x = 10; // x 是常量
constexpr int y = 20; // y 是编译期常量
const int z = getSomeValue(); // ✅ 运行时确定的常量也可以
// constexpr int w = getSomeValue(); // ❌ 必须编译期能确定
constexpr int square(int n) {
return n * n;
}
constexpr int result = square(5); // 编译期计算,result = 25
constexpr 告诉编译器:这个值在编译期就能确定,可以做更多优化。
2.5 mutable 关键字
有时候你需要在 const 成员函数中修改某些特定的成员变量(比如缓存、计数器)。mutable 就是为此设计的:
class DataFetcher {
private:
mutable int accessCount = 0; // 即使在 const 函数中也能修改
string data;
public:
string getData() const {
accessCount++; // ✅ mutable 变量可以在 const 函数中修改
return data;
}
int getAccessCount() const {
return accessCount;
}
};
3. 引用与 const 的实际应用
3.1 函数参数的最佳实践
// ✅ 最佳实践:按需选择参数传递方式
// 基本类型(int、double 等):传值
void processInt(int x) { /* ... */ }
// 不需要修改的大对象:const 引用
void printVector(const vector<int>& vec) {
for (int x : vec) cout << x << " ";
}
// 需要修改的参数:引用
void sortVector(vector<int>& vec) {
sort(vec.begin(), vec.end());
}
// 可能为空的参数:指针
void processMaybeNull(int* ptr) {
if (ptr != nullptr) {
// 处理数据
}
}
3.2 返回值的最佳实践
class Matrix {
private:
vector<vector<int>> data;
public:
// 返回 const 引用:只读访问
const vector<int>& getRow(int i) const {
return data[i];
}
// 返回非 const 引用:可修改访问
vector<int>& getRow(int i) {
return data[i];
}
// 返回值:小对象或需要返回新对象时
int getElement(int i, int j) const {
return data[i][j];
}
};
3.3 范围 for 循环中的引用
vector<int> arr = {1, 2, 3, 4, 5};
// 只读遍历:const 引用
for (const int& x : arr) {
cout << x << " "; // 高效,不拷贝
}
// 修改遍历:非 const 引用
for (int& x : arr) {
x *= 2; // 原地修改每个元素
}
// 不用引用:会拷贝每个元素(对于大对象效率低)
for (int x : arr) {
cout << x << " "; // 基本类型没问题,string 等大对象会有额外拷贝
}
4. 常见陷阱
4.1 悬空引用
int& getDangling() {
int local = 42;
return local; // ⚠️ 返回局部变量的引用
}
int main() {
int& ref = getDangling();
cout << ref; // 未定义行为!可能输出垃圾值
}
4.2 const 丢失
const int a = 10;
const int* p = &a;
// int* bad = p; // ❌ 编译错误:不能丢弃 const
int* bad = const_cast<int*>(p); // ⚠️ 强制转换,但修改 a 是未定义行为
4.3 引用的引用
int a = 10;
int& r1 = a;
int& r2 = r1; // r2 是 a 的引用,不是 r1 的引用
r2 = 20;
cout << a; // 输出 20
5. 小结
本节我们学习了引用和 const 限定符:
- 引用是变量的别名,必须初始化,不能重新绑定
- const 引用可以绑定到字面量,防止意外修改
- const 指针有两种:指向内容的 const 和指针本身的 const
- const 成员函数表示不修改对象状态
- constexpr 是编译期常量,比 const 更严格
最佳实践:
- 函数参数优先用
const 引用(只读)或引用(需修改) - 不修改对象的成员函数都声明为
const - 能用引用就不用指针
- 用
constexpr代替const定义编译期常量
下一节我们将学习类与对象基础——C++ 面向对象编程的起点。
更多推荐


所有评论(0)