文章目录
一、基本概念
在 C++ 中,const 限定符有两种不同的用法,分别称为顶层 const 和底层 const:
- 顶层 const:修饰的是对象本身,表示对象本身是常量,不能修改。例如:
const int x = 5;中x就是顶层 const。 - 底层 const:表示指针或引用所指向的对象是常量,不能通过该指针或引用修改对象。例如:
const int *p;中const修饰的是int(被指向的对象),这是底层 const。
简单记法:当
const出现在指针声明中并修饰*左侧的类型(const int*/int const*)时是底层 const;当const修饰整个对象(如int* const中修饰指针本身)时属于顶层 const。
二、参数传递
2.1 基本原则
用实参初始化形参时,会忽略掉顶层 const。也就是说,当形参有顶层 const 时,传给它常量对象或者非常量对象都是可以的。
即 void f(int) 和 void f(const int) 在函数类型上等价,不能用顶层 const 来重载。
底层 const 必须匹配或允许转换,例如 const T* 可以接受 T* 或 const T* 实参,但 T* 不能接受 const T*)。
2.2 各种参数传递情况分析
① 按值传递 - 普通参数
#include <iostream>
using namespace std;
// int cube(int i):实参可以为 int 类型,也可以为 const int 类型
// 在函数体中可以修改 i 的值,函数不会修改实参的值
int cube(int i) {
i = i + 1; // 可以修改,不影响实参
return i * i * i;
}
// ② int cube(const int i):实参可以为 int 类型,也可以为 const int 类型
// 在函数体中不可以修改 i 的值,函数不会修改实参的值
int cube2(const int i) {
// i = 0; // 错误:不能修改 const 参数
return i * i * i;
}
调用:无论实参是否 const(int 或 const int),都可以传递给上面两个函数。因为顶层 const 会被忽略用于匹配参数类型。
按值参数上的顶层 const 主要是给读代码的人看的,表示函数不会修改拷贝。
② 指针参数 - 不同 const 修饰
int *p;— 指针可变,指向的内容可变。const int *p;— 指针可变,但不能通过p修改被指向的int(底层 const)。int * const p;— 指针本身是常量(顶层 const),指针不能再指向别处,但可以修改*p。const int * const p;— 指针本身不可变,指向的内容也不可变。
// int p_cube(int* pi):实参为 int 类型的指针,不能为 const int 类型
// 可以通过修改 *pi 的值修改实参指向的对象的值,修改 pi 对实参没有影响
int p_cube(int* pi) {
*pi = 10; // 修改传入参数实际地址指向的值
return *pi * (*pi) * (*pi);
}
// int p_cube2(const int* pi):底层 const
// 实参可以为 const int 类型,也可以为 int 类型
// 不可以通过修改 *pi 的值修改实参指向的对象的值,修改 pi 对实参没有影响
int p_cube2(const int* pi) {
// *pi = 10; // 错误:不能通过 const 指针修改值
return *pi * (*pi) * (*pi);
}
// int p_cube3(int* const pi):顶层 const
// 实参为 int 类型,不能为 const int 类型
// 可以通过修改 *pi 的值修改实参指向的对象值,不可以给 pi 赋值
int p_cube3(int* const pi) {
*pi = 20; // 可以:可以修改指针指向的值
// pi = nullptr; // 错误:不能修改指针本身(顶层 const)
return *pi * (*pi) * (*pi);
}
2.3 完整测试示例
#include <iostream>
using namespace std;
// 函数声明
int cube(int i);
int cube2(const int i);
int p_cube(int* pi);
int p_cube2(const int* pi);
int p_cube3(int* const pi);
int main() {
int num1 = 10; // int 类型
const int num2 = 11; // const int 类型
// 测试按值传递
cout << "按值传递测试:" << endl;
// 实参是 int 类型和 const int 类型都是可以的
cout << "cube(num1): " << cube(num1) << endl;
cout << "cube(num2): " << cube(num2) << endl;
cout << "cube2(num1): " << cube2(num1) << endl;
cout << "cube2(num2): " << cube2(num2) << endl;
cout << "\n指针参数测试:" << endl;
// 测试指针参数
int temp1 = 5;
const int temp2 = 6;
// p_cube:实参只能为 int 类型
cout << "p_cube(&temp1): " << p_cube(&temp1) << endl;
// cout << p_cube(&temp2) << endl; // 错误:不能传递 const int* 给 int*
// p_cube2:实参可以是 int 类型,也可以是 const int 类型
cout << "p_cube2(&temp1): " << p_cube2(&temp1) << endl;
cout << "p_cube2(&temp2): " << p_cube2(&temp2) << endl;
// p_cube3:实参只能为 int 类型
cout << "p_cube3(&temp1): " << p_cube3(&temp1) << endl;
// cout << p_cube3(&temp2) << endl; // 错误:不能传递 const int* 给 int* const
return 0;
}
三、const 引用传递的性能优势
3.1 自定义类型的参数传递
对于自定义类型(如类对象),按值传递参数会触发对象的拷贝构造函数,产生额外的内存开销和性能损耗。而使用 const 引用作为参数,既能避免拷贝,又能保证对象不被修改。
#include <iostream>
using namespace std;
class LargeObject {
public:
int data[1000]; // 假设是一个大对象
LargeObject() {
// 初始化逻辑
for (int i = 0; i < 1000; ++i) {
data[i] = i;
}
cout << "LargeObject 构造函数被调用" << endl;
}
// 拷贝构造函数
LargeObject(const LargeObject& other) {
for (int i = 0; i < 1000; ++i) {
data[i] = other.data[i];
}
cout << "LargeObject 拷贝构造函数被调用" << endl;
}
};
// 按值传递:会拷贝整个 LargeObject,效率低
void processByValue(LargeObject obj) {
cout << "processByValue: 对象大小 = " << sizeof(obj) << " 字节" << endl;
}
// const 引用传递:无拷贝,且不能修改 obj
void processByConstRef(const LargeObject& obj) {
cout << "processByConstRef: 对象大小 = " << sizeof(obj) << " 字节" << endl;
// obj.data[0] = 100; // 错误:不能修改 const 引用对象
}
int main() {
LargeObject lo;
cout << "按值传递测试:" << endl;
processByValue(lo); // 拷贝整个对象,效率低
cout << "\nconst 引用传递测试:" << endl;
processByConstRef(lo); // 无拷贝,效率高且安全
return 0;
}
3.2 const 引用的灵活性
普通引用(非 const)只能绑定到可修改的左值(如变量),而 const 引用的灵活性更强:
T&—— 只能绑定到左值(可修改的变量)。const T&—— 可以绑定到左值、常量、甚至临时对象(右值)。这是非常常用的传参方式(尤其适合大对象,避免拷贝且保证不修改)。
#include <iostream>
#include <string>
using namespace std;
void print(const string& s) {
cout << s << endl;
}
string getString() {
return "临时字符串";
}
int main() {
// 1. 绑定到常量
const int& ref1 = 100; // 合法:const 引用可以绑定到字面量
// 2. 绑定到临时对象(表达式结果)
int a = 5, b = 3;
const int& ref2 = a + b; // 合法:a + b 的结果是临时对象,可被 const 引用绑定
cout << "ref2 = " << ref2 << endl;
// 3. 函数参数接收临时对象
print("hello"); // 合法:字符串字面量是临时对象
print(getString()); // 合法:函数返回的临时对象
// 对比:普通引用不能绑定到常量或临时对象
// int& ref3 = 100; // 错误
// int& ref4 = a + b; // 错误
// void badPrint(string& s); badPrint("hello"); // 错误
return 0;
}
注意:非 const 引用(T&)不能绑定到临时对象或字面量(例如 int& r = 5; 是错误的)。
四、const 成员函数 与 mutable
4.1 const 成员函数
用 void foo() const 声明成员函数,表示该函数不会修改对象的可观察状态(也就是不会修改非 mutable 成员),并且可以在 const 对象上调用。例如:
struct S {
int x;
int getX() const { return x; } // 可以在 const S 对象上调用
};
4.2 mutable
如果某成员被声明为 mutable,则即便在 const 成员函数中也可以被修改(常用于缓存/惰性计算的内部状态):
struct Cache {
mutable bool cached = false;
mutable int value = 0;
int get() const {
if (!cached) { value = /* compute */ 42; cached = true; }
return value;
}
};
五、总结
const 引用的核心意义在于在保证效率(避免拷贝)的同时,通过限制修改操作来提高代码的安全性和可读性,并扩展了引用的适用场景(如绑定常量、临时对象)。它是 C++ 中一种"零成本抽象"的典型体现——既提供了安全性,又不带来额外的性能开销。
使用:
- 函数参数传递:对于内置类型,按值传递即可;对于自定义类型,优先使用 const 引用传递
- 指针参数:根据是否需要修改指针本身或指向的内容,选择合适的 const 修饰
- 避免不必要的拷贝:对于大对象,总是使用 const 引用传递
- 提高代码安全性:使用 const 可以防止意外的修改,让代码意图更清晰

2548

被折叠的 条评论
为什么被折叠?



