c++ const 关键字
const
关键字:
它是 C++ 中用于定义常量、确保程序正确性和安全性的最重要关键字之一。
1. const
的核心思想:不变性
const
的基本含义是“常量”或“只读”。它用于告诉编译器和程序员:这个对象或数据在初始化后,其值不能再被修改。任何试图修改 const
值的操作都会导致编译错误。
这带来了两大好处:
安全性:防止意外修改不该修改的数据。
可读性与可维护性:明确表达了设计意图(“这个值不会变”),使代码更易于理解。
2. const
的主要用法
1. 定义常量变量
这是最基础的用法,用于取代 C 语言中的 #define
宏常量。
cpp
const int MAX_BUFFER_SIZE = 1024; const double PI = 3.1415926535; const std::string GREETING = "Hello, World!";// MAX_BUFFER_SIZE = 2048; // 错误:不能给常量赋值
优势 over #define
:
有类型检查,更安全。
遵守作用域规则(如命名空间、类作用域)。
可以通过调试器看到符号名。
2. 指针与 const
指针的 const
用法比较复杂,有两种形式,取决于 const
相对于 *
的位置:
指向常量的指针 (Pointer to const):指针指向的内容是常量,不能通过该指针修改内容,但指针本身可以指向别的地址。
cpp
int value = 10; const int* ptr = &value; // 或者 int const* ptr // *ptr = 20; // 错误:不能通过 ptr 修改 value value = 20; // 正确:value 本身不是常量,可以直接修改 ptr = nullptr; // 正确:指针本身可以改变
常量指针 (Const pointer):指针本身是常量,初始化后不能再指向其他地址,但可以通过它修改所指的内容(除非内容也是常量)。
cpp
int value1 = 10, value2 = 20; int* const ptr = &value1; *ptr = 15; // 正确:可以修改所指的内容 // ptr = &value2; // 错误:指针本身是常量,不能改变指向
指向常量的常量指针 (Const pointer to const):指针本身和所指的内容都是常量。
cpp
int value = 10; const int* const ptr = &value; // *ptr = 20; // 错误:不能修改所指内容 // ptr = nullptr; // 错误:不能修改指针本身
记忆口诀:
const
在*
左边,指向的内容是常量 (const int*
)。
const
在*
右边,指针本身是常量 (int* const
)。
3. 函数与 const
const
形参:防止函数内部修改传入的参数。常用于传递大型对象(如const std::string&
),既避免拷贝又保证原对象不被修改。cpp
void printMessage(const std::string& msg) {std::cout << msg << std::endl;// msg.clear(); // 错误:msg 是 const 引用,不能修改 }
const
成员函数:这是const
在类中极其重要的用法。它被放在成员函数声明的末尾。cpp
class MyClass { private:int m_value;mutable int m_counter; // mutable 成员即使在 const 函数中也能被修改 public:int getValue() const { // 这是一个 const 成员函数// m_value = 100; // 错误:不能修改类的非 mutable 成员变量m_counter++; // 正确:m_counter 是 mutable 的return m_value;}void setValue(int val) { // 这是一个非 const 成员函数m_value = val; // 可以修改成员变量} };
const
成员函数的意义:
它向编译器承诺:这个函数不会修改调用它的对象的状态(除了
mutable
成员)。它允许被
const
对象调用。
cpp
const MyClass obj1; // obj1.setValue(5); // 错误:const 对象不能调用非 const 成员函数 int x = obj1.getValue(); // 正确:const 对象可以调用 const 成员函数MyClass obj2; obj2.getValue(); // 正确:非 const 对象也可以调用 const 成员函数
const
返回值:表示函数返回的值是常量,不能被修改。较少使用,但有时可以防止误操作。cpp
const int* getPointer() {static int value = 42;return &value; } // *(getPointer()) = 100; // 错误:返回值是 const int*
4. const
与引用
常量引用 (
const T&
):这是 C++ 中最常用的传递参数的方式之一,尤其是对于自定义类型。cpp
void processBigObject(const VeryLargeObject& obj) {// 可以读取 obj,但不能修改它// 避免了拷贝大型对象的开销,又保证了安全性 }
常量引用有一个重要特性:它可以绑定到右值(临时对象)。
cpp
void func(const std::string& str); func("hello"); // 正确:常量引用可以绑定到临时字符串
5. constexpr
(C++11 引入)
constexpr
是 const
的增强版,它表示“常量表达式”。不仅值不可变,而且必须在编译期就能计算出结果。
cpp
constexpr int square(int x) { return x * x; } constexpr int max_size = square(10); // 编译期计算int arr[max_size]; // 可以用作数组大小(因为编译期可知)
constexpr
比 const
要求更严格,但能提供更好的性能(编译期计算)和更广的用途(如模板元编程)。
3. const
的威力:重载
const
可以用于函数重载。编译器会根据调用对象的常量性来选择调用哪个版本。
cpp
class MyArray { public:int& operator[](size_t index) { // 用于非 const 对象,返回引用可修改return m_data[index];}const int& operator[](size_t index) const { // 用于 const 对象,返回 const 引用return m_data[index];} private:int m_data[10]; };int main() {MyArray arr1;const MyArray arr2;arr1[0] = 100; // 调用非 const 版本int a = arr2[0]; // 调用 const 版本// arr2[0] = 200; // 错误:调用的是 const 版本,返回不可修改的引用 }
4. 类型安全:const_cast
与去除常量性
虽然 const
表示不可修改,但 C++ 提供了 const_cast
运算符来移除 const
限定符。这是一项非常危险的操作,必须极其谨慎地使用。
它主要用于与旧的、没有正确使用 const
的 C 语言库接口进行交互。
cpp
void oldCLibraryFunction(char* str) { // 一个不修改 str 的C函数,但参数没声明为const// ... }int main() {const char* my_str = "Hello";// oldCLibraryFunction(my_str); // 错误:不能将 const char* 转换为 char*oldCLibraryFunction(const_cast<char*>(my_str)); // 强制转换,语法上通过了// 但如果 oldCLibraryFunction 真的修改了 my_str 指向的内容...// ...程序将导致未定义行为(UB),因为试图修改常量区的字符串字面量! }
规则:只有在你绝对确定一个对象原本就不是 const
,只是通过 const
指针或引用来访问它时,才能使用 const_cast
。
总结与实践建议
用法 | 描述 | 好处 |
---|---|---|
const 变量 | 定义运行期常量 | 替代宏,类型安全,有作用域 |
const 参数 | 函数不会修改的输入参数 | 防止意外修改,常与引用合用避免拷贝 |
const 成员函数 | 不修改对象状态的成员函数 | const 对象可用,明确接口契约 |
const 返回值 | 返回不可修改的值 | 防止客户端误修改 |
const 引用 | 只读别名 | 高效传递对象,可绑定临时对象 |
constexpr | 编译期常量 | 性能优化,可用于更多场景(如数组大小) |
最佳实践:
默认使用
const
:在定义变量、参数、引用和成员函数时,只要不需要修改,就加上const
。这是一种良好的防御性编程习惯。按需使用
const
:对于内置类型的小型参数,传值往往比传const
引用更高效。正确使用
const
成员函数:为所有不修改对象状态的getter
函数加上const
。避免使用
const_cast
:除非是在与劣质库接口的不得已情况下。优先考虑
constexpr
:如果需要在编译期确定值,使用constexpr
。