在 C++ 的类中,成员函数通常可以分为两类。一类负责 “修改” 对象的数据,比如 setWidth()。另一类只负责 “读取” 或 “展示” 对象的数据,比如 getWidth()、getArea()、showInfo()。
对于只负责读取数据的成员函数,如果我们希望从语法上做出限制,防止在函数内部不小心改掉了对象的数据,就可以在成员函数的参数列表后面加上 const 关键字。这种函数称为 “const 成员函数”,平时也常叫做 “常成员函数”。
const 成员函数的基本用法
在 C++ 中,定义 const 成员函数非常简单,我们只需要在成员函数的参数列表后面加上 const 关键字即可。
语法:
class 类名 {
public:
返回值类型 函数名(参数列表) const {
// 函数体
}
};说明:
书写时小伙伴们要注意 const 的位置:它必须放在参数列表的右小括号 ) 后面、左花括号 { 前面,比如 int getWidth() const。如果我们把 const 写在最前面写成 const int getWidth(),修饰的就是返回值类型,而不是常成员函数了。
结合上一节学过的 this 指针就很好理解了。在函数末尾加上 const,相当于把隐含的 this 变成了指向只读对象的 const 指针(即 const 类名*),因此在函数内部只能读取当前对象的成员,不能修改。
示例 1:定义 const 成员函数
#include <iostream>
class Rectangle {
private:
int width;
int height;
public:
Rectangle(int w, int h) : width(w), height(h) {}
// 只读取宽度
int getWidth() const {
return width;
}
// 只计算面积
int getArea() const {
return width * height;
}
// 只打印信息
void showInfo() const {
std::cout << "宽:" << width << '\n';
std::cout << "高:" << height << '\n';
}
};
int main() {
Rectangle rect(10, 5);
rect.showInfo();
std::cout << "矩形面积:" << rect.getArea() << '\n';
return 0;
}运行结果如下。
宽:10
高:5
矩形面积:50分析:
在这个例子中,我们为 Rectangle 类定义了 getWidth()、getArea() 和 showInfo() 这 3 个成员函数。它们都只负责读取 width 和 height 的值,不需要修改当前对象的任何数据,因此我们在参数列表后面都加上了 const。
注意 如果把 const 成员函数拆分为 “类内声明、类外定义”,那么声明和定义的末尾都必须写上 const。比如类外定义也要写成 int Rectangle::getWidth() const,两边保持一致才行,否则会编译报错。
const 成员函数的只读限制
给成员函数加上 const 之后,相当于向编译器作出了 “不修改当前对象” 的承诺。编译器在编译阶段会严格检查函数体,一旦违反以下 2 条规则,就会直接报错:
- 不能修改当前对象的普通成员变量:在
const成员函数内部,我们可以正常读取任何成员变量,但不能修改普通成员变量的值。 - 不能调用当前对象的非 const 成员函数:在
const成员函数内部,我们只能调用其他的const成员函数,不能调用未加const的普通成员函数。因为非const成员函数有可能会修改对象的数据,如果允许调用它,就相当于绕开了只读限制。
下面我们通过一个例子,看看在 const 成员函数内部修改成员变量时会发生什么。
示例 2:违反只读限制报错
#include <iostream>
class Rectangle {
private:
int width = 10;
public:
void reset() const {
width = 0; // 错误:不能修改成员变量
}
};
int main() {
Rectangle rect;
rect.reset();
return 0;
}运行结果如下。
(报错)error: assignment of member 'Rectangle::width' in read-only object分析:
width = 0; 这句代码尝试在 reset() const 内部修改成员变量。由于函数末尾带有 const,当前对象在函数内部处于只读状态(read-only object),因此编译时直接报错,不允许对 width 重新赋值。
同理,假设类中还定义了一个只读的 getWidth() const 和一个未加 const 的 setWidth(),那么在 const 成员函数内部调用时也有明确区分。
void test() const {
// 正确方式
int w = getWidth();
// 错误方式
setWidth(20);
}初学的小伙伴有时会把 const 成员函数误解成 “里面什么成员都不能用”。其实并非如此,大家只要分清 “读” 和 “写” 即可:读取成员变量和调用其他 const 成员函数都是完全正常的,只有可能修改当前对象的操作才会被禁止。
const 对象与常引用的调用规则
除了防止我们在函数内部误改数据之外,const 成员函数还有一个非常关键的实际用途:只有 const 成员函数,才能被 “const 对象(常对象)” 或 “const 引用(常引用)” 调用。
就像普通变量加上 const 会变成常量一样,我们在创建对象时也可以在前面加上 const,将其定义为常对象。通过引用传递接收对象参数时,我们也经常把形参写成 const 引用。这样既能省去复制开销,又能防止原对象被修改。
无论是 const 对象还是 const 引用,都代表对象处于只读状态。它们与普通对象调用成员函数的权限对比如下表所示。
| 对象或引用类型 | 调用 const 成员函数 | 调用非 const 成员函数 |
|---|---|---|
| 普通对象 | 可以调用 | 可以调用 |
| const 对象(常对象) | 可以调用 | 不能调用(编译报错) |
| const 引用(常引用) | 可以调用 | 不能调用(编译报错) |
这里有一个很容易踩坑的细节:哪怕我们在普通成员函数里一行修改数据的代码都没写,只要函数声明末尾没有加上 const,编译器就会认为它 “有可能会修改对象”。一旦被判定为可能修改对象,const 对象和 const 引用就都不能调用它了。下面我们结合具体示例来看。
示例 3:不同对象的调用权限
#include <iostream>
class Counter {
private:
int value;
public:
Counter(int v) : value(v) {}
// 普通成员函数
void increase() {
value++;
}
// const 成员函数
int getValue() const {
return value;
}
};
// 形参为 const 引用
void printCounter(const Counter& c) {
std::cout << "常引用读取:" << c.getValue() << '\n';
}
int main() {
// 1. 普通对象
Counter c1(10);
c1.increase();
std::cout << "c1 的值:" << c1.getValue() << '\n';
// 2. const 对象
const Counter c2(100);
std::cout << "c2 的值:" << c2.getValue() << '\n';
// 3. 传给 const 引用形参
printCounter(c1);
return 0;
}运行结果如下。
c1 的值:11
c2 的值:100
常引用读取:11分析:
在 Counter 类中,increase() 需要让 value 加 1,因此定义为普通的非 const 成员函数。而 getValue() const 只负责返回当前数值,因此声明为 const 成员函数。
进入 main() 函数后,c1 是普通对象,调用 increase() 和 getValue() 都没有问题。而 c2 是 const 对象,printCounter() 的形参 c 是 const 引用,它们都只能调用带有 const 修饰的 getValue()。
如果我们尝试通过常对象 c2 去调用未加 const 的 increase(),下面这种写法就会直接编译报错。
const Counter c2(100);
c2.increase();反过来想,要是我们漏掉了 getValue() 末尾的 const,示例中的 c2.getValue() 和 c.getValue() 也会编译报错。因此在实际开发中,只要成员函数不需要修改成员变量(如 getXXX()、showXXX()),我们都建议在末尾加上 const。
mutable 关键字
在少数场景下,某个成员函数整体上是只读的(如 showInfo() const),但我们又希望在里面统计一下 “这个函数一共被调用了多少次”。此时如果直接修改普通的计数成员变量,编译器就会报错。
为了处理这种特殊情况,C++ 提供了 mutable 关键字,意为 “可变的”。只要在定义该成员时加上 mutable(如 mutable int count = 0;),即使在 const 成员函数内部也能正常修改它。这个关键字平时用得不多,小伙伴们简单了解一下即可。
