在C++的文件流操作中,写入数据前经常需要根据后续待写入内容的特征调整写入逻辑,比如过滤特定字符、校验格式、拼接前缀等。此时如果直接读取内容会移动文件指针,影响后续写入位置,而peek预读功能可以在不移动指针的情况下查看下一个字符,完美适配这类场景需求。

peek预读功能的基本特性
peek是C++标准库中istream和ostream流类都提供的成员函数,其核心特性是返回流中下一个字符的副本,但是不会从流中提取该字符,也就是不会移动流的读取/写入位置指针。当流处于末尾时,peek会返回EOF。
对于写入文件流场景,我们通常是对待写入的缓冲区内容或者另一个输入流进行peek预读,再决定当前的写入规则。下面先来看peek的基础用法示例:
#include <iostream>
#include <fstream>
#include <sstream>
using namespace std;
int main() {
// 构造一个待写入的字符串流,模拟待写入的内容
stringstream input_ss;
input_ss << "abc123def";
// 使用peek预读下一个字符,不移动指针
char next_char = input_ss.peek();
cout << "预读的下一个字符是: " << next_char << endl; // 输出 a
// 再次读取字符,确认指针没有移动
char first_char;
input_ss >> first_char;
cout << "实际读取的第一个字符是: " << first_char << endl; // 输出 a
return 0;
}
写入文件流时结合peek实现复杂判断的实战场景
场景1:过滤待写入内容中的特定字符
假设我们需要将一个输入流的内容写入文件,但是要求过滤掉所有的数字字符,只写入字母部分。此时可以在写入前用peek预读下一个字符,判断是否为数字,再决定是否写入。
#include <iostream>
#include <fstream>
#include <sstream>
#include <cctype>
using namespace std;
int main() {
// 模拟待写入的内容流
stringstream input_ss;
input_ss << "hello123world456";
// 打开输出文件流
ofstream out_file("output.txt");
if (!out_file.is_open()) {
cout << "文件打开失败" << endl;
return 1;
}
char current_char;
// 循环读取输入流内容
while (input_ss.get(current_char)) {
// 预读下一个字符,判断是否为数字
char next_char = input_ss.peek();
// 如果当前字符不是数字,才写入文件
if (!isdigit(current_char)) {
out_file << current_char;
}
// 如果下一个字符是数字,跳过所有连续的数字
while (isdigit(input_ss.peek())) {
input_ss.get(); // 提取数字字符,跳过不写入
}
}
out_file.close();
cout << "写入完成,数字已被过滤" << endl;
return 0;
}
上述代码中,我们先通过input_ss.get(current_char)读取当前字符,再用peek()查看下一个字符是否为数字,如果是数字则循环跳过所有连续数字,只把非数字字符写入文件。最终output.txt的内容会是helloworld。
场景2:写入前校验内容格式并自动补全
再比如我们需要将用户输入的日志内容写入文件,要求每条日志必须以换行符结尾,如果下一条日志的开头不是换行符,就自动补全一个换行符再写入。这种场景也可以用peek预读实现。
#include <iostream>
#include <fstream>
#include <sstream>
using namespace std;
int main() {
// 模拟多条待写入的日志,其中第二条没有换行符结尾
stringstream log_ss;
log_ss << "2024-05-01 10:00:00 系统启动n";
log_ss << "2024-05-01 10:01:00 用户登录";
log_ss << "2024-05-01 10:02:00 数据加载完成n";
ofstream log_file("system.log");
if (!log_file.is_open()) {
cout << "日志文件打开失败" << endl;
return 1;
}
string line;
// 按行读取待写入内容
while (getline(log_ss, line)) {
// 写入当前行内容
log_file << line;
// 预读输入流的下一个字符,判断是否为换行符
char next_char = log_ss.peek();
// 如果下一个字符不是换行符,也不是EOF,就补全换行符
if (next_char != 'n' && next_char != EOF) {
log_file << 'n';
}
}
log_file.close();
cout << "日志写入完成,格式已自动校验补全" << endl;
return 0;
}
这个例子中,每次写入一行日志后,用peek查看输入流的下一个字符,如果下一个字符不是换行符且不是流末尾,就自动补一个换行符,避免日志内容粘连,保证格式规范。
场景3:根据后续内容动态调整写入前缀
假设我们要把一段文本写入文件,当预读到下一个单词是关键字error时,就给当前内容加上[错误]前缀再写入。实现逻辑如下:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
using namespace std;
int main() {
stringstream content_ss;
content_ss << "程序运行正常 error 模块加载失败 warn 配置读取成功";
ofstream out_file("result.txt");
if (!out_file.is_open()) {
cout << "文件打开失败" << endl;
return 1;
}
string word;
while (content_ss >> word) {
// 预读下一个字符,判断下一个单词的开头是否是e
// 简单逻辑:如果下一个字符是e,假设下一个单词是error
char next_char = content_ss.peek();
if (next_char == 'e') {
out_file << "[错误]" << word << " ";
} else {
out_file << word << " ";
}
}
out_file.close();
cout << "写入完成,关键字前缀已添加" << endl;
return 0;
}
使用peek预读的注意事项
- peek返回的是int类型,而不是char类型,当流到达末尾时返回EOF,EOF通常是-1,所以判断是否为末尾时要和EOF比较,不要直接和char类型比较。
- peek不会修改流的状态,但是如果流已经处于失败状态(比如打开失败、读取错误),peek会直接返回EOF。
- 对于写入文件流本身,不能直接对ofstream调用peek查看已写入的内容,peek通常用于预读待写入的输入流内容,再决定写入策略。
- 如果预读后需要跳过该字符,要显式调用get或者ignore函数提取该字符,peek本身不会移动指针。
通过peek预读功能,我们可以在C++写入文件流的过程中灵活实现各类复杂的判断逻辑,避免不必要的指针移动和内容重复读取,让文件写入的逻辑更加高效和可控。实际开发中可以根据具体的业务需求,组合peek和其他流操作函数,实现更多定制化的功能。