成员函数上的 const
结构体把相关的数据收进一个类型之后,下一步是让类型带上行为:struct 可以把函数直接定义在成员声明之后,这类函数通过 对象.函数名(实参) 调用,读写的是自己这个实例的成员。本章只讲这类函数身上的 const 写在哪里、承诺什么。完整函数语法由后面的函数章展开。
尾部 const
成员函数的 const 写在参数列表末尾。它的承诺对象不是某个参数,而是函数所属的实例本身:
片段:查询与修改两类成员函数
// 查询函数:尾部 const。
CellState GetCellState(Position position) const;
// 修改函数:没有尾部 const,允许改写成员。
PlaceResult Place(Move move);GetCellState 只读取棋盘数据,尾部 const 把它标成查询;Place 要写入格子状态,不能标。误标的函数会在编译期被指出“const 函数里不能修改成员”,调用方不需要阅读实现就能信任这个承诺。
完整示例用一个棋盘格子演示两类函数:
const-member-functions.cpp
// 成员函数上的 const 示例:查询函数与修改函数的承诺不同。
#include <cstdint>
#include <iostream>
// 棋盘格子状态
enum class CellState : std::uint8_t {
kEmpty = 0, // 空格。
kX = 1,
kO = 2,
};
// 一个棋盘格子:查询用尾部 const,落子要写入成员。
struct Cell {
CellState state{CellState::kEmpty};
// 查询函数:尾部 const 承诺不修改实例。
CellState GetCellState() const {
return state;
}
// 修改函数:没有尾部 const,允许写入成员。
void Place(CellState new_state) {
state = new_state;
}
};
int main() {
Cell cell;
cell.Place(CellState::kX);
std::cout << "格子状态编号: " << static_cast<int>(cell.GetCellState()) << "\n";
return 0;
}四处 const 各管一段
const 写在不同位置,管住的承诺不同。下面把成员函数相关场景放在一起对照:
| 位置 | 写法 | 管住什么 |
|---|---|---|
| 成员函数尾部 | CellState GetCellState(Position) const |
不修改实例自身 |
| 返回类型 | const CellState& |
调用方拿到只读访问,不能借返回值修改 |
| 参数 | const Position& |
函数内不能通过该参数修改外部对象 |
| 局部变量 | const CellState state{...}; |
创建之后不能通过这个名字赋值 |
同一份只读数据往往在几个位置同时出现 const:参数标一个,函数体里的局部量标一个,尾部再标一个。它们各管一段,不互相替代。引用记号 & 的传参选型由下一章展开,这里只需认识“加了 const 的引用是只读借用”。
常见错误
修改型函数漏标 const
Place 要写入棋盘成员,标了尾部 const 编译器直接拒绝,判断标准是函数是否会改写成员,而不是函数“感觉上”重不重要。
实例不变的误解
尾部 const 只约束经由本函数的调用不改写成员,不承诺对象在所有路径上不可变,成员仍可被其他修改函数改写。
自测问题
先不要展开答案,试着自己把回答完整说出来,再展开对照。说不出来的部分,回到对应小节对照正文。
CellState GetCellState(Position position) const;末尾的const承诺什么?查看答案
承诺这个函数不修改自己所属的实例,只读取成员。调用方可以在 const 实例上放心调用它。
为什么
Place不能标尾部 const?查看答案
它要写入棋盘成员,尾部 const 会把这种写入挡在编译期,编译器报“const 函数里不能修改成员”。
参数上的
const和成员函数尾部的const有什么区别?查看答案
参数上的
const管住“函数内不能通过该参数修改外部对象”,尾部的const管住“函数不修改实例自身成员”,两处承诺互不替代。
本章回顾
你现在能读出成员函数尾部 const 的承诺,并把参数、返回值与局部变量的 const 放进同一张表区分。完整示例见 const-member-functions.cpp,对照与选型见 常量关键词。