operator[] 必须是非静态成员函数,因需隐式 this 指针访问对象数据;必须同时提供 const 与非 const 版本,返回 const t& 和 t&,参数推荐 size_t,越界应做安全防护(如 assert)。
![c++如何重载数组下标运算符[]](https://img.php.cn/upload/article/001/221/864/178590080760791.png?x-oss-process=image/resize,p_40)
operator[] 必须是类的非静态成员函数
因为 operator[] 需要隐式访问 this 指针来定位当前对象的内部数据,它不能是全局函数或静态成员。如果你在类外定义、或加了 static,编译器会直接报错:error: 'operator[]' must be a non-static member function。
常见错误是想用友元函数实现双参数版本(比如支持 const/non-const 分离),但 C++ 规定 operator[] 只能有一个显式参数(即下标值),this 是隐式的 —— 所以它天然就是单参数运算符,没法靠友元绕过。
- 正确写法:在类内声明为
int& operator[](size_t i);和const int& operator[](size_t i) const; - 返回类型建议用引用(
T&/const T&),否则无法支持a[i] = x;这类赋值操作 - 参数类型推荐用
size_t或有符号整型(如int),但要注意越界检查逻辑需匹配类型范围
必须同时提供 const 和非 const 版本才能避免隐式转换问题
如果只写了非 const 版本,那么对 const 对象调用 [] 会失败;反之,如果只写 const 版本,非 const 对象虽然能调,但返回的是 const T&,导致无法赋值 —— 编译器不会自动帮你转成可修改引用。
典型报错:error: no viable overloaded '=' 或 error: binding reference of type 'int&' to 'const int' discards qualifiers。
- 两个版本函数体可以复用逻辑,例如都调用一个私有辅助函数
at(size_t i) - const 版本必须返回
const T&,非 const 版本返回T&,不可混用 - 不要试图只靠
const_cast在一个函数里“兼顾”,这破坏 const 正确性,且可能引发未定义行为
下标越界处理:不抛异常也得做安全防护
C++ 标准库(如 std::vector::operator[])不检查越界,这是性能考量;但你自己实现时,若不做任何防护,用户一越界就直接读写野指针,崩溃都算轻的。
实际项目中更推荐的做法是:开发期用断言,发布版静默截断或返回默认值(视场景而定),而不是无条件抛异常 —— 因为 operator[] 语义上不承诺异常安全,用户通常也不期待它抛。
- 开发调试可用
assert(i ,配合 <code>-DNDEBUG控制是否启用 - 不想用 assert?可内部记录错误状态(如设
last_error_ = OUT_OF_RANGE),供后续get_last_error()查询 - 避免在
operator[]中 throw,除非你明确文档化并要求调用方捕获 —— 否则和std::vector::at()的职责混淆了
返回代理对象的场景:需要支持 a[i][j] 或延迟求值
当你的“数组”其实是二维结构(如稀疏矩阵)、或元素访问涉及计算(如位域、压缩存储),直接返回 T& 不可行 —— 这时得返回一个代理类(proxy class),重载它的 operator= 和类型转换。
例如 std::bitset 就用 reference 代理来支持 b[5] = true;,因为单个 bit 无法取地址。
- 代理对象必须持有原始容器的引用(或指针)和索引信息
- 代理类需定义
operator=(bool)、operator bool() const等,按需补充 - 注意生命周期:代理对象不能比原容器活得久,否则引用悬空
operator[] 看似简单,但 const 正确性、返回类型选择、越界策略、代理设计这几处,稍不留意就会埋下运行时隐患或接口误用。尤其当别人把你的类当 std::vector 那样用时,行为差异会在集成阶段集中爆发。C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











