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。
更多推荐
所有评论(0)