下面将从**const
成员**、const
成员函数、const
对象、mutable
、constexpr
等方面,逐一详解 C++ 类中常见的 const
用法及注意事项,并配合示例。
一、const
数据成员
-
必须在初始化列表中初始化
class A { const int x; // const 成员 public: A(int v) : x(v) { } // ❌ 合法:初始化列表中初始化 // A(int v) { x = v; } // ❌ 错误:无法在函数体内赋值 };
-
不可修改
- 一旦初始化,之后在对象生存期内不能再改变。
- 可用于对外保证成员不被篡改。
-
与静态成员结合
class B { static const int N = 100; // 类内常量,若需要 ODR 定义,则在 cpp 中: // const int B::N; };
static const
整数/枚举成员可在类内直接给初始值,不占实例空间。
二、const
成员函数
在成员函数后添加 const
,表明该函数不会修改任何非 mutable
成员,也不会调用非 const
成员函数。
class C {
int x;
public:
C(int v): x(v) {}
int getX() const { // 常量成员函数
return x; // 只能访问 x,不可修改
}
void setX(int v) { // 非 const 函数
x = v;
}
};
-
调用约束
const C c1(5); c1.getX(); // ✅ 可以调用 const 成员函数 // c1.setX(7); // ❌ 错误:不能调用非 const 成员函数
-
隐式
this
类型- 在
const
成员函数中,this
的类型为const C*
,保证不可修改成员。
- 在
三、const
对象
C obj1(3); // 普通对象
const C obj2(4); // 常量对象,只能调用 const 成员函数
-
只读语义:
obj2
的所有非static
数据成员对于外部都是只读的。 -
可与指针/引用混用:
void foo(const C& c); foo(obj1); // OK,将 obj1 作为只读参数
四、mutable
修饰符
当你希望在 const
成员函数中仍然修改某些成员,可将它们声明为 mutable
:
class Logger {
mutable std::ostream& os; // 即使在 const 函数中也可修改
public:
Logger(std::ostream& _os): os(_os) {}
void log(const std::string& msg) const {
os << msg << std::endl; // OK,os 是 mutable
}
};
- 场景:缓存、延迟初始化、统计访问次数等。
五、constexpr
与常量表达式
C++11 起,可将成员函数或构造函数声明为 constexpr
,使其在编译期计算:
class Point {
int x, y;
public:
constexpr Point(int _x, int _y): x(_x), y(_y) {}
constexpr int getX() const { return x; }
constexpr int getY() const { return y; }
};
constexpr Point p(1,2);
static_assert(p.getX() == 1, ""); // 在编译期验证
-
注意:
constexpr
成员函数 必须 同时 是const
(除了构造函数)。- 只有在满足编译期求值规则时才真正成为常量表达式。
六、与继承结合
struct Base {
virtual void foo() const {
// ...
}
};
struct Derived : Base {
void foo() const override { // 覆盖 const 成员函数
// ...
}
};
- 覆盖时签名要一致:返回类型、参数列表后是否
const
都要相同,否则不构成覆盖。
七、注意事项汇总
-
忘记在初始化列表中初始化
const
成员- 会导致编译错误。
-
在
const
成员函数中尝试修改非mutable
成员- 编译器会报错,防止越界修改。
-
constexpr
函数应当尽量简单- 包含循环与分支也支持,但要遵守常量表达式的限制。
-
不要滥用
mutable
- 可能破坏对象的逻辑常量性(logical constness),只在真正需要缓存、延迟初始化时使用。
-
接口设计
- 对于只读操作,应当标记为
const
;有副作用的操作,要去除const
,让常量对象无法调用。
- 对于只读操作,应当标记为
八、综合示例
#include <iostream>
#include <vector>
class Matrix {
const int rows, cols; // 必须初始化
std::vector<double> data; // 默认构造
mutable bool dirty; // 缓存标志
public:
// constexpr 构造 + 初始化列表
constexpr Matrix(int r, int c)
: rows(r), cols(c), data(r*c), dirty(true) {}
// const 成员函数:只读访问
double get(int i, int j) const {
dirty = false; // OK:dirty 是 mutable
return data[i*cols + j];
}
// 非 const 成员函数:修改
void set(int i, int j, double v) {
data[i*cols + j] = v;
dirty = true;
}
constexpr int getRows() const { return rows; }
constexpr int getCols() const { return cols; }
bool isDirty() const { return dirty; }
};
int main() {
Matrix M(2,3);
M.set(0,0,1.23);
std::cout << M.get(0,0) << "\n"; // 可以修改 dirty
std::cout << std::boolalpha
<< M.isDirty() << "\n"; // false
constexpr Matrix N(3,3);
static_assert(N.getRows()==3, ""); // 编译期校验
return 0;
}