C++---const关键字 编译期约束保证数据的 只读性
const 是 C++ 中最基础也最核心的关键字之一,其设计初衷是通过编译期约束保证数据的“只读性”,从而提高代码的安全性、可读性和可维护性。它的用法贯穿变量、指针、引用、类、函数等几乎所有语法场景。
一、const 修饰变量:定义“只读”常量
const 最基本的用法是修饰变量,使其成为“常量”——即初始化后不可修改。这是编译期的约束,编译器会对修改行为直接报错。
const int a = 10; // 常量 a,初始化后不可修改
a = 20; // 编译错误:assignment of read-only variable 'a'
关键特性:
-
必须初始化:
const变量定义时必须初始化(否则后续无法赋值),初始化值可以是常量表达式或运行时计算结果:const int b; // 编译错误:uninitialized const 'b' const int c = 3 + 4; // 合法:常量表达式初始化 int d = 5; const int e = d; // 合法:运行时变量初始化(e 仍为只读) -
作用域与链接性:
- 局部
const变量:作用域为当前块,无链接性。 - 全局
const变量:默认内部链接(仅当前文件可见),与非const全局变量的外部链接不同。若需跨文件访问,需显式加extern:// file1.cpp extern const int g_val = 100; // 外部链接,可被其他文件访问// file2.cpp extern const int g_val; // 声明,使用 file1 中的 g_val
- 局部
二、const 与指针:三种组合形式
指针是 const 应用中最容易混淆的场景,核心是区分“指针指向的内容不可改”和“指针本身不可改”,具体有三种组合:
| 形式 | 含义 | 能否修改指针指向的内容 | 能否修改指针本身(指向新地址) |
|---|---|---|---|
const int* p | 指向常量的指针(底层 const) | 不能 | 能 |
int* const p | 常量指针(顶层 const) | 能 | 不能 |
const int* const p | 指向常量的常量指针(底层+顶层 const) | 不能 | 不能 |
示例解析:
int x = 10, y = 20;// 1. 指向常量的指针(const 修饰 int)
const int* p1 = &x;
*p1 = 30; // 编译错误:不能修改指向的内容
p1 = &y; // 合法:可以指向新地址// 2. 常量指针(const 修饰指针 p2)
int* const p2 = &x;
*p2 = 30; // 合法:可以修改指向的内容
p2 = &y; // 编译错误:不能修改指针本身// 3. 指向常量的常量指针
const int* const p3 = &x;
*p3 = 30; // 错误
p3 = &y; // 错误
底层 const 与顶层 const:
- 底层 const:修饰指针指向的对象(如
const int* p),表示对象不可改; - 顶层 const:修饰指针本身(如
int* const p),表示指针不可改。
这一区分在类型转换(如const_cast)和函数参数传递中至关重要。
三、const 与引用:只读引用
引用本质是变量的别名,const 修饰引用(const T&)表示“只读引用”——不能通过引用修改被引用的对象。
核心特性:
-
不能通过引用修改对象:
int a = 10; const int& ref = a; ref = 20; // 编译错误:assignment of read-only reference 'ref' a = 20; // 合法:对象本身可改,仅引用限制修改 -
可绑定到临时对象或不同类型的对象:
非const引用(T&)只能绑定到同类型的非临时对象,而const引用(const T&)可以绑定到:- 同类型的 const/非 const 对象;
- 临时对象(如表达式结果);
- 隐式转换后与 T 兼容的对象。
const int& ref1 = 10; // 合法:绑定到临时对象(值 10) const int& ref2 = 3.14; // 合法:double 隐式转换为 int 临时对象(值 3)这一特性使
const引用成为函数参数的常用选择(扩大参数接受范围,避免拷贝)。
四、const 在类中的应用
类是 const 用法最复杂的场景,涉及数据成员、成员函数、静态成员等,核心是保护对象的“常量性”。
1. const 数据成员
类的 const 数据成员必须在构造函数初始化列表中初始化,不能在构造函数体内赋值(因为进入函数体时成员已初始化)。
class A {
private:const int m_val; // const 数据成员
public:// 必须在初始化列表中初始化A(int val) : m_val(val) {} // 正确// A(int val) { m_val = val; } // 错误:构造函数体内不能赋值
};
2. const 成员函数
在成员函数参数列表后加 const,表示该函数是“常量成员函数”,其核心约束是:不能修改对象的非静态数据成员,也不能调用非 const 成员函数。
本质是因为 const 成员函数的 this 指针类型为 const T*(指向常量对象),而非 const 成员函数的 this 指针为 T*。
class A {
private:int m_x;static int s_y; // 静态成员(属于类,非对象)
public:// const 成员函数void print() const {// m_x = 10; // 错误:不能修改非静态成员cout << m_x << endl; // 合法:只读访问// setX(20); // 错误:不能调用非 const 成员函数s_y = 30; // 合法:可以修改静态成员(与对象无关)}// 非 const 成员函数void setX(int x) { m_x = x; }
};
const 对象与 const 成员函数的关系:
const 对象只能调用 const 成员函数,非 const 对象可以调用任意成员函数:
const A a1(5);
a1.print(); // 合法:const 对象调用 const 函数
a1.setX(10); // 错误:const 对象不能调用非 const 函数A a2(5);
a2.print(); // 合法:非 const 对象调用 const 函数
a2.setX(10); // 合法
3. mutable 关键字:突破 const 成员函数的限制
mutable 修饰的成员变量可以在 const 成员函数中被修改,用于存储“逻辑上不影响对象常量性”的辅助数据(如缓存、计数器)。
class A {
private:mutable int m_count; // 可在 const 函数中修改int m_val;
public:A(int val) : m_val(val), m_count(0) {}int getVal() const {m_count++; // 合法:mutable 成员可修改return m_val;}
};
4. 静态 const 成员
类的静态 const 成员(static const T)是“类级别的常量”,所有对象共享,其初始化规则特殊:
- 整数类型(
int、char等)可在类内直接初始化,也可类内声明+类外初始化; - 非整数类型(
double、string等)必须类内声明,类外初始化。
class A {
public:// 整数类型:类内直接初始化static const int s_int = 10;// 非整数类型:类内声明static const double s_dbl;static const string s_str;
};// 非整数类型:类外初始化(必须在全局作用域)
const double A::s_dbl = 3.14;
const string A::s_str = "hello";
C++11 后可使用 constexpr static 简化非整数类型的初始化(类内直接初始化):
class A {
public:constexpr static double s_dbl = 3.14; // 合法
};
五、const 与函数:参数和返回值
const 修饰函数参数或返回值时,用于限制参数的修改或返回值的使用方式。
1. const 函数参数
- 值传递参数:
const修饰值传递参数(如void func(const int x))意义不大,因为参数是副本,修改副本不影响实参,const仅限制函数内部修改副本。 - 引用/指针参数:
const修饰引用或指针参数(如void func(const int& x)或void func(const int* x))是核心用法,作用是:- 防止函数内部修改实参;
- 允许参数接受
const或非const类型的实参(扩大适用范围)。
void print(const string& s) { // const 引用参数// s += " world"; // 错误:不能修改实参cout << s << endl;
}int main() {string s1 = "hello";const string s2 = "world";print(s1); // 合法:非 const 实参print(s2); // 合法:const 实参print("temp"); // 合法:绑定到临时对象return 0;
}
2. const 函数返回值
- 返回值为对象:
const T func()表示返回的对象是只读的,防止外部修改:const string getStr() { return "hello"; } int main() {getStr() += " world"; // 错误:返回的 const 对象不可修改return 0; } - 返回值为引用/指针:
const T& func()或const T* func()表示返回的引用/指针指向“只读数据”,常用于避免返回内部数据被外部修改:class Container { private:vector<int> m_data; public:const vector<int>& getData() const { return m_data; } // 返回 const 引用 };int main() {Container c;const vector<int>& data = c.getData();data.push_back(10); // 错误:const 引用指向的数据不可修改return 0; }
3. const 与函数重载
const 可以作为函数重载的区分条件,主要体现在:
- 成员函数是否为
const(const成员函数与非const成员函数构成重载); - 指针/引用参数是否为
const(底层 const 差异可构成重载)。
class A {
public:// 重载:const 成员函数与非 const 成员函数void func() { cout << "non-const" << endl; }void func() const { cout << "const" << endl; }
};// 重载:const 引用参数与非 const 引用参数
void print(string& s) { cout << "non-const: " << s << endl; }
void print(const string& s) { cout << "const: " << s << endl; }int main() {A a;const A ca;a.func(); // 调用 non-const 版本ca.func(); // 调用 const 版本string s = "hello";const string cs = "world";print(s); // 调用 non-const 参数版本print(cs); // 调用 const 参数版本return 0;
}
六、const_cast:去除 const 限定
const_cast 是 C++ 四种类型转换运算符之一,用于去除指针或引用的 const 限定(只能操作指针或引用)。
用法与风险:
- 若对象本身是
non-const,通过const_cast去除const后修改是合法的; - 若对象本身是
const,通过const_cast去除const后修改会导致未定义行为(编译器可能优化常量,修改后值不确定)。
// 情况 1:对象本身 non-const(合法)
int a = 10;
const int* p = &a;
int* np = const_cast<int*>(p); // 去除 const
*np = 20; // 合法,a 的值变为 20// 情况 2:对象本身 const(未定义行为)
const int b = 10;
const int* p2 = &b;
int* np2 = const_cast<int*>(p2);
*np2 = 20; // 未定义行为:b 可能仍为 10(编译器优化)
七、const 与 constexpr 的区别
C++11 引入的 constexpr 常与 const 混淆,两者的核心差异是:
const表示“只读”,值可以在运行时确定(如const int x = get_val());constexpr表示“编译期常量”,值必须在编译时确定,可用于编译期计算(如数组大小、模板参数)。
const int x = 10; // 可以是运行时常量
constexpr int y = 20; // 必须是编译期常量int arr1[x]; // 合法(C++11 后 const 整数可作为数组大小)
int arr2[y]; // 合法int get_val() { return 30; }
const int z = get_val(); // 合法(运行时初始化)
// constexpr int w = get_val(); // 错误:get_val() 非 constexpr 函数,无法编译期求值
const 关键字通过编译期约束实现了“只读性”,其用法覆盖变量、指针、引用、类、函数等场景,核心价值在于:
- 安全性:防止意外修改数据,减少 bug;
- 可读性:明确标识“不可修改”的语义,便于代码理解;
- 灵活性:通过
const引用/指针扩大函数适用范围,减少拷贝开销。
掌握 const 的关键是区分“被修饰的实体”(变量、指针、引用、函数)及其“不可修改的范围”,结合具体场景(如类的常量成员函数、函数重载)理解其约束逻辑。
