目录
[学习日志|Day 6](#学习日志|Day 6)
[二、详细解释(工程 + 语法底层 + 面试全覆盖)](#二、详细解释(工程 + 语法底层 + 面试全覆盖))
[2.1 迭代器基础定义(真正用到 self 前必须介绍)](#2.1 迭代器基础定义(真正用到 self 前必须介绍))
[2.2 为什么前置++ / 后置++ 的区分不是靠函数名(⭐)](#2.2 为什么前置++ / 后置++ 的区分不是靠函数名(⭐))
[2.3 前置++:函数签名为 operator++() ------ 无参数表示"前置"](#2.3 前置++:函数签名为 operator++() —— 无参数表示“前置”)
[(2)前置++ 函数逐行深度解释](#(2)前置++ 函数逐行深度解释)
[① node = node->next;](#① node = node->next;)
[② return *this;](#② return *this;)
[③ 前置++ 的优点:](#③ 前置++ 的优点:)
[(3)前置++ 的编译器调用过程](#(3)前置++ 的编译器调用过程)
[2.4 后置++:函数签名 operator++(int) ------ int 参数表示"后置"](#2.4 后置++:函数签名 operator++(int) —— int 参数表示“后置”)
[(2)后置++ 函数逐行深度解释](#(2)后置++ 函数逐行深度解释)
[① self tmp = *this; ------ 保存"旧值"](#① self tmp = *this; —— 保存“旧值”)
[② ++(*this); ------ 修改当前对象(调用前置++)](#② ++(*this); —— 修改当前对象(调用前置++))
[③ return tmp; ------ 返回旧值(按值返回)](#③ return tmp; —— 返回旧值(按值返回))
[(3)后置++ 编译器调用过程](#(3)后置++ 编译器调用过程)
[2.5 为什么前置返回引用,后置返回对象?](#2.5 为什么前置返回引用,后置返回对象?)
[2.6 为什么后置++要写成 const self?(⭐)](#2.6 为什么后置++要写成 const self?(⭐))
[Q2:为什么后置++前面要加 const?](#Q2:为什么后置++前面要加 const?)
[Q4:后置++ 是不是比前置++ 慢?](#Q4:后置++ 是不是比前置++ 慢?)
学习日志 | Day 6
第六篇主要系统梳理了 C++ 中 前置 ++ 与 后置 ++ 的底层机制与工程实践,包括:
-
为什么前置++写成
operator++()?为什么没有 ++operator 这种写法? -
为什么后置++写成
operator++(int)?这个 int 参数到底是什么? -
为什么前置++返回引用,而后置++必须返回对象?
-
为什么后置++返回值前必须加 const?
-
前置++ 与 后置++ 在效率、语义、编译器展开中的本质差异
-
STL 迭代器为什么强烈推荐使用前置++(++it)
-
后置++(it++)在哪里用、为什么开销更大
希望大家能通过这篇学习,彻底搞清楚了 运算符重载语法规则、旧值语义、临时对象生命周期、编译器行为 等底层机制,能够完整解释面试里所有关于 ++ 重载的高频问题。
一、简要回答
-
前置++(++i) :先自增,再返回自身引用;效率高,无临时对象。
-
后置++(i++) :先保存旧值,再自增;需创建临时对象,因此返回对象而非引用。
-
自定义类型中 优先使用前置++。
-
后置++通常必须写成
self operator++(int)且返回const对象,用于禁止i++++。
二、详细解释(工程 + 语法底层 + 面试全覆盖)
2.1 迭代器基础定义(真正用到 self 前必须介绍)
在自定义迭代器(或类)中,一般会用 using(或 typedef)给自己起一个别名:
cpp
class Iterator {
public:
using self = Iterator; // self 是 Iterator 的别名
using pointer = Node*; // 常见迭代器 typedef
using reference = Node&;
private:
Node* node; // 当前底层节点
};
(1)目的
这么做有三个目的:
-
提高可读性
以后看到**self** 就知道是当前类 ,不用反复写Iterator。 -
避免类名变动带来的修改成本
如果类名从
Iterator改为ListIterator,只需改一处。 -
符合 STL Iterator 的写法风格
STL 大量使用内部 typedef:
pointer,reference,difference_type...
(2)后续定义:
cpp
self& operator++()
const self operator++(int)
里面的 self,其实就是 Iterator。
2.2 为什么前置++ / 后置++ 的区分不是靠函数名( ⭐)
C++ 语法规定:
所有运算符重载的函数名格式为:
operator + 运算符号
所以:
| 表达式运算符 | 对应重载函数名 |
|---|---|
| + | operator+ |
| - | operator- |
| [] | operator[] |
| () | operator() |
| ++ | operator++ |
函数名 operator++ 并不区分前置/后置。 并不是 ++operator 和 operator ++ ;
前置/后置的区别是看参数列表!
2.3 前置++:函数签名为 operator++() ------ 无参数表示"前置"
cpp
self& operator++() {
node = node->next;
return *this;
}
(1)为什么这是前置++?
因为:
-
没有参数 → C++ 规定这是前置版本
-
对应表达式:
++it -
编译器会翻译为(调用形式):
cpp
++it => it.operator++()
(2)前置++ 函数逐行深度解释
① node = node->next;
-
假设
Iterator维护底层节点指针node -
前置++语义:迭代器走到下一个元素
-
所以把当前节点指向
next
② return *this;
-
*this是当前对象本身 -
返回类型是
self&→ 返回引用(返回当前对象本身-引用) -
不产生临时对象
-
支持链式操作:
++++it
③ 前置++ 的优点:
-
零拷贝
-
零临时对象
-
无构造/析构成本
-
性能最好
(3)前置++ 的编译器调用过程
cpp
Iterator it;
Iterator& r = ++it;
编译器等价为:
cpp
Iterator& r = it.operator++();
执行流程:
-
进入
operator++() -
node前移 -
返回当前对象自身
*this(引用) -
r绑定到it本身
最终效果:
-
it→ 前进一步 -
r与it指向同一个对象
2.4 后置++:函数签名 operator++(int) ------ int 参数表示"后置"
cpp
const self operator++(int)
(1)为什么这是后置++?
因为:
-
C++ 规定:
带一个int占位参数的是后置++ -
这个参数不会被使用,只是语法标记
-
写
it++时编译器会翻译成:cppit.operator++(0);
(2)后置++ 函数逐行深度解释
cpp
const self operator++(int) {
self tmp = *this; // 保存旧值
++(*this); // 当前对象前进(调用前置++)
return tmp; // 返回旧值
}
① self tmp = *this; ------ 保存"旧值"
-
拷贝当前对象到
tmp -
表示"i++ 之前的值"
-
这一步会调用拷贝构造函数
→ 这是后置++性能差的核心原因
② ++(*this); ------ 修改当前对象(调用前置++)
等价于:
cpp
this->operator++();
作用:
-
推进迭代器自身
-
与前置++行为一致
③ return tmp; ------ 返回旧值(按值返回)
此处必须返回对象,因为:
-
tmp是临时变量 -
函数结束即销毁
-
如果返回引用 → 悬空引用 → 未定义行为
因此后置++必须返回 对象值(by value)。
(3)后置++ 编译器调用过程
cpp
Iterator old = it++;
编译器等价:
cpp
Iterator old = it.operator++(0);
执行流程:
-
保存旧值到
tmp -
自增当前对象(调用前置++)
-
返回旧值
tmp
最终结果:
-
it→ 新位置 -
old→ 旧位置
与内置类型完全一致:
cpp
int j = i++; // j = i old, i = i+1
2.5 为什么前置返回引用,后置返回对象?
(1)前置++返回引用(self&)
因为:
-
前置++语义是"自增后返回自己"
-
不用产生临时对象
-
返回引用最快
(2)后置++返回对象(self)
因为:
后置++语义是:
"返回旧值,但当前对象自增"
旧值必须保存到临时变量:
cpp
self tmp = *this;
临时变量函数结束就销毁 → 无法返回引用。
因此必须返回对象副本。
2.6 为什么后置++要写成 const self?(⭐)
考虑这段代码:
cpp
Iterator it;
(it++)++; // 合法吗?
如果返回非 const 对象:
-
(it++)是临时对象 -
但"临时对象是可修改的"
-
编译器会允许
(it++)++→ 这种操作毫无意义(修改旧值,对 it 无影响)
为了禁止这种行为:
我们写成:
cpp
const self operator++(int)
这样:
-
(it++)返回的临时旧值是只读 -
(it++)++会直接报错 -
行为更安全
-
与 STL Iterator 完全一致(标准要求)
- 性能更高
三、图表总结
| 对比项 | 前置++ | 后置++ |
|---|---|---|
| 语义 | 先自增再返回 | 先返回旧值再自增 |
| 返回类型 | 引用 | 对象 |
| 是否有临时对象 | ❌ 无 | ✔ 有 |
| 效率 | 高 | 低 |
| 是否推荐 | 强烈推荐 | 不推荐(除非确实需要旧值) |
| 是否需要 const | 不需要 | 通常写为 const,避免 i++++ |
| 使用场景 | 迭代器、循环 | 需要旧值时,例如 i = j++ |
四、代码示例(可直接放进你的项目)
完整前置++ / 后置++ 重载示例
cpp
class Iterator {
public:
// 前置 ++
Iterator& operator++() {
node = node->next;
return *this;
}
// 后置 ++
const Iterator operator++(int) {
Iterator tmp = *this; // 旧值
++(*this); // 前置推进
return tmp; // 返回旧值
}
};
五、面试常问
Q1:为什么后置++要返回对象而不是引用?
因为后置++需要保存旧值,旧值是临时对象,函数结束后被销毁,无法返回引用。
Q2:为什么后置++前面要加 const?
为了禁止 (i++)++ 这种无意义、错误的调用。
旧值是临时对象,加 const 可以保证其只读。
Q3:为什么自定义类型优先使用前置++?
前置++不产生临时对象 → 无额外构造/析构 → 更快、更节省内存。
Q4:后置++ 是不是比前置++ 慢?
对。因为它必须创建一个临时对象存储旧值。
Q5:如果只实现一个,应该实现哪个?
实现前置++,并在后置++里复用前置++。
六、总结
前置++ 快、无临时对象,更快、更节省内存;
后置++ 慢、需返回旧值(临时对象),一般只在需要旧值时使用。