最直接退回字符的方法是调用unget(),它将最后一次成功读取的字符推回缓冲区头部,但仅限一个字符且需检查failbit;putback(c)可放任意字符但更危险;peek()最安全,无副作用。

用 unget() 把刚读的字符塞回去
最直接的办法是调用输入流对象的 unget() 成员函数。它会把最后一次成功读取的字符(比如用 get()、peek() 或提取运算符 >> 读到但尚未处理的那个)推回流缓冲区头部,下次读取时会先拿到它。
注意:只能退一个字符;多次调用 unget() 不保证有效,且失败时不报错,只置 failbit —— 所以务必检查状态:
char c = std::cin.get();
if (std::cin.good()) {
std::cin.unget(); // 放回
if (std::cin.fail()) {
// 缓冲区满或流不支持,无法放回
}
}
-
unget()对std::ifstream、std::istringstream同样适用,但底层依赖流缓冲区是否支持“回退”(多数标准实现支持单字符) - 如果之前用的是
>>提取整数,unget()只能退回最后一个被读入的字符(比如读"123"后,流位置在'3'后,unget()会把'3'推回去,不是整个数字) - 不能对已失效的流(如 EOF 后)调用,否则直接失败
用 putback() 更灵活地放任意字符
putback(c) 允许你把**指定字符** c 放回流头,不局限于上次读到的那个。它比 unget() 略宽泛,但也更危险——放错字符可能破坏解析逻辑。
典型场景:预读一个字符判断类型,再决定怎么处理。例如识别数字开头后,想让后续 >> 自动读整数:
char c = std::cin.peek();
if (std::isdigit(c)) {
std::cin.putback(c); // 放回,留给 >> int 消费
int n; std::cin >> n;
}
-
putback()失败时同样设failbit,必须检查good()或!fail() - 某些流(如绑定到文件的
std::ifstream)可能只允许putback()一次,且仅限刚 peek 过的字符;放其他字符可能静默失败 - 和
unget()一样,不能混用多次 —— 大多数标准库只维护一个“可回退字符”的槽位
为什么 peek() 通常比“读+放回”更安全
如果你只是想“看看下一个字符但不消耗它”,直接用 peek() 是最干净的选择。它返回下一个字符(不移动读位置),返回 std::char_traits<char>::eof()</char> 表示 EOF。
- 零副作用:不会改变流状态,也不触发格式化提取逻辑
- 无需担心
unget()/putback()失败,也没有缓冲区限制问题 - 适合做分发判断:比如
if (cin.peek() == '{') { parse_object(); } - 注意:
peek()不跳过空白符,如需跳过,得先调用skipws或手动ignore()
遇到 unget() 失败怎么办
常见失败原因包括:流已到 EOF、缓冲区已满(尤其在多次 unget() 后)、底层 streambuf 不支持回退(极少见,但自定义流可能如此)。这时别硬撑,换思路:
- 改用
std::string+std::istringstream做缓冲:一次性读整行,再用字符串流反复解析,完全可控 - 用
std::istream::rdbuf()获取底层streambuf*,调用其sputbackc(c)(C++11 起),它比unget()更底层,失败时返回traits_type::eof(),便于判断 - 避免依赖回退:把“读取-判断-分支处理”逻辑改为“预读-分支-消费”,用
peek()+ 条件提取,减少状态依赖
真正容易被忽略的是:流的格式标志(如 skipws)会影响 peek() 和 get() 的行为,而 unget() 不会自动恢复这些标志 —— 如果你在关闭 skipws 后读字符再放回,下一次带空格的提取可能出乎意料。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











