【C++编程】string流输入输出
string流输入输出
#include <iostream>
#include <streambuf>
#include <fstream>
#include <vector>
#include <string>
#include <cstring>
#include <memory>
// 自定义流缓冲区类 - 带有详细日志和统计功能
class LoggingStreamBuf : public std::streambuf {
private:
std::vector<char> buffer_; // 内部缓冲区
std::ofstream output_file_; // 输出文件
size_t flush_count_; // 刷新次数统计
size_t overflow_count_; // overflow 调用次数
size_t xsputn_count_; // xsputn 调用次数
size_t total_written_; // 总写入字符数
public:
// 构造函数
LoggingStreamBuf(const std::string& filename, size_t buffer_size = 64)
: buffer_(buffer_size),
output_file_(filename),
flush_count_(0),
overflow_count_(0),
xsputn_count_(0),
total_written_(0) {
if (!output_file_.is_open()) {
throw std::runtime_error("无法打开文件: " + filename);
}
// 设置输出缓冲区的开始和结束指针
setp(buffer_.data(), buffer_.data() + buffer_.size());
std::cout << "[构造] 创建 LoggingStreamBuf, 缓冲区大小: " << buffer_size << " 字节\n";
}
// 析构函数 - 确保所有数据都被写入
~LoggingStreamBuf() {
sync(); // 同步缓冲区
print_statistics();
}
protected:
// 重写 overflow - 当缓冲区满时调用
virtual int_type overflow(int_type c = traits_type::eof()) override {
overflow_count_++;
std::cout << "[overflow #" << overflow_count_ << "] 缓冲区已满,准备刷新\n";
std::cout << " 当前缓冲区内容长度: " << (pptr() - pbase()) << " 字节\n";
// 刷新当前缓冲区内容到文件
if (flush_buffer() == -1) {
std::cout << " [错误] 刷新缓冲区失败\n";
return traits_type::eof();
}
// 如果有新字符需要写入
if (c != traits_type::eof()) {
std::cout << " 写入新字符: '" << static_cast<char>(c) << "'\n";
*pptr() = traits_type::to_char_type(c);
pbump(1);
}
return c;
}
// 重写 xsputn - 批量写入数据(这是关键优化点)
virtual std::streamsize xsputn(const char_type* s, std::streamsize count) override {
xsputn_count_++;
std::cout << "[xsputn #" << xsputn_count_ << "] 准备写入 " << count << " 个字符\n";
std::cout << " 内容预览: \"";
// 显示前20个字符作为预览
for (std::streamsize i = 0; i < std::min(count, std::streamsize(20)); ++i) {
char ch = s[i];
if (ch >= 32 && ch <= 126) {
std::cout << ch;
} else if (ch == '\n') {
std::cout << "\\n";
} else if (ch == '\t') {
std::cout << "\\t";
} else {
std::cout << "\\x" << std::hex << (unsigned char)ch << std::dec;
}
}
if (count > 20) std::cout << "...";
std::cout << "\"\n";
std::streamsize written = 0;
while (written < count) {
// 计算缓冲区剩余空间
std::streamsize available = epptr() - pptr();
std::streamsize to_copy = std::min(count - written, available);
std::cout << " 可用缓冲区空间: " << available << " 字节\n";
std::cout << " 本次复制: " << to_copy << " 字节\n";
if (to_copy > 0) {
// 直接复制到缓冲区
std::memcpy(pptr(), s + written, to_copy);
pbump(static_cast<int>(to_copy));
written += to_copy;
std::cout << " 已复制到缓冲区,缓冲区使用量: " << (pptr() - pbase()) << "/" << buffer_.size() << "\n";
}
// 如果缓冲区满了或者还有数据要写入但缓冲区空间不足
if (pptr() == epptr() && written < count) {
std::cout << " 缓冲区已满,触发 overflow\n";
if (overflow() == traits_type::eof()) {
std::cout << " [错误] overflow 失败,停止写入\n";
break;
}
}
}
total_written_ += written;
std::cout << " [xsputn 完成] 实际写入: " << written << " 字节,累计: " << total_written_ << " 字节\n\n";
return written;
}
// 重写 sync - 同步缓冲区(强制刷新)
virtual int sync() override {
std::cout << "[sync] 强制同步缓冲区\n";
return flush_buffer();
}
private:
// 私有方法:刷新缓冲区到文件
int flush_buffer() {
std::streamsize length = pptr() - pbase();
if (length > 0) {
std::cout << " [flush] 将 " << length << " 字节写入文件\n";
// 写入到文件
output_file_.write(pbase(), length);
if (!output_file_.good()) {
std::cout << " [错误] 文件写入失败\n";
return -1;
}
// 强制刷新文件缓冲区
output_file_.flush();
flush_count_++;
// 重置缓冲区指针
setp(buffer_.data(), buffer_.data() + buffer_.size());
std::cout << " [flush] 成功,累计刷新次数: " << flush_count_ << "\n";
}
return 0;
}
// 打印统计信息
void print_statistics() {
std::cout << "\n=== LoggingStreamBuf 统计信息 ===\n";
std::cout << "overflow 调用次数: " << overflow_count_ << "\n";
std::cout << "xsputn 调用次数: " << xsputn_count_ << "\n";
std::cout << "缓冲区刷新次数: " << flush_count_ << "\n";
std::cout << "总写入字符数: " << total_written_ << "\n";
std::cout << "缓冲区大小: " << buffer_.size() << " 字节\n";
std::cout << "===============================\n";
}
};
// 自定义 ostream 类
class LoggingStream : public std::ostream {
private:
std::unique_ptr<LoggingStreamBuf> buf_;
public:
LoggingStream(const std::string& filename, size_t buffer_size = 64)
: buf_(std::make_unique<LoggingStreamBuf>(filename, buffer_size)) {
rdbuf(buf_.get());
}
// 手动同步
void force_sync() {
buf_->pubsync();
}
};
// 测试函数
void test_basic_operations() {
std::cout << "\n========== 测试1: 基本操作 ==========\n";
LoggingStream log_stream("test_output.txt", 32); // 32字节小缓冲区
// 测试单个字符写入
std::cout << "\n--- 写入单个字符 ---\n";
log_stream << 'A' << 'B' << 'C';
// 测试短字符串写入
std::cout << "\n--- 写入短字符串 ---\n";
log_stream << "Hello";
// 测试长字符串写入(会触发 overflow)
std::cout << "\n--- 写入长字符串(触发overflow)---\n";
log_stream << " World! This is a longer string that will exceed the buffer size.";
// 手动同步
std::cout << "\n--- 手动同步 ---\n";
log_stream.force_sync();
}
void test_large_data() {
std::cout << "\n========== 测试2: 大数据量写入 ==========\n";
LoggingStream log_stream("large_test.txt", 64);
// 写入大量数据
std::string large_text = "这是一段重复的文本,用于测试大数据量的写入性能。";
std::cout << "\n--- 写入1000次重复文本 ---\n";
for (int i = 0; i < 1000; ++i) {
log_stream << "[" << i << "] " << large_text << "\n";
// 每100次显示一下进度
if (i % 100 == 0) {
std::cout << " 进度: " << i << "/1000\n";
}
}
std::cout << "\n--- 写入完成,准备析构 ---\n";
}
void test_mixed_operations() {
std::cout << "\n========== 测试3: 混合操作 ==========\n";
LoggingStream log_stream("mixed_test.txt", 48);
// 混合使用不同的输出操作
log_stream << "整数: " << 42 << "\n";
log_stream << "浮点数: " << 3.14159 << "\n";
log_stream << "字符串: " << std::string("C++ Streaming") << "\n";
// 使用格式化输出
log_stream << "十六进制: 0x" << std::hex << 255 << std::dec << "\n";
// 写入包含特殊字符的内容
log_stream << "特殊字符: \t制表符 \n换行符 \"引号\" \\反斜杠\n";
}
int main() {
try {
std::cout << "开始 StreamBuf 详细测试...\n";
test_basic_operations();
test_large_data();
test_mixed_operations();
std::cout << "\n所有测试完成!请检查生成的文件:\n";
std::cout << "- test_output.txt\n";
std::cout << "- large_test.txt\n";
std::cout << "- mixed_test.txt\n";
} catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << "\n";
return 1;
}
return 0;
}
1. istringstream·原型:
template <class CharT, class Traits = std::char_traits<CharT>, class Allocator = std::allocator<CharT>> class basic_istringstream;
2. ostirngstream 原型:
template <class CharT, class Traits = std::char_traits<CharT>, class Allocator = std::allocator<CharT>> class basic_ostringstream;
3. stringstream 原型:
template < CharT,class Traits = std::char_traits<CharT>, class Allocator = std::allocator<CharT>> class basic_stringstream;
【注意】
typedef basic_istringstream<char> istringstream; typedef basic_ostringstream<char> ostringstream; typedef basic_stringstream<char> stringstream;
iostream 现在是模板化的,同时支持窄字符和宽字符。下图是现在的继承体系,同时画出了 fstreams 和 stringstreams。图中方框的第二行是模板的具现化类型,也就是我们代码里常用的具体类型(通过 typedef 定义)。

这个继承体系糅合了面向对象与泛型编程,但可惜它两方面都不讨好。
再进一步加深了解,发现还有一个平行的 streambuf 继承体系,fstream 和 stringstream 的不同之处主要就在于它们使用了不同的 streambuf 具体类型。

1. 示例
1 #include <iostream> 2 #include <sstream> 3 using namespace std; 4 5 int main() 6 { 7 istringstream in; 8 string line, str1; 9 while (getline(cin, line)) // 从终端接受一行字符串,存入line中 10 { 11 in.str(line); // 把line中的字符串存入字符串流中 12 while (in >> str1) // 每次读取一个单词(以空格为例)存入str1中; 13 { 14 cout << str1 << endl; 15 } 16 } 17 return 0; 18 }
3. 示例:
1 #include <string> 2 #include <sstream> 3 #include <iostream> 4 5 int main() 6 { 7 std::stringstream stream; 8 std::string result; 9 int i = 1000; 10 stream << i; //将int输入流 11 stream >> result; //从stream中抽取前面插入的int值 12 std::cout << result << std::endl; // print the string "1000" 13 }
ostringstream的用法
1 #include <string> 2 #include <iostream> 3 #include <sstream> 4 using namespace std; 5 6 void main() 7 { 8 ostringstream ostr1; // 构造方式1 9 ostringstream ostr2("abc"); // 构造方式2 10 11 /*---------------------------------------------------------------------------- 12 *** 方法str()将缓冲区的内容复制到一个string对象中,并返回 13 ----------------------------------------------------------------------------*/ 14 ostr1 << "ostr1 " << 2012 << endl; // 格式化,此处endl也将格式化进ostr1中 15 cout << ostr1.str(); 16 17 /*---------------------------------------------------------------------------- 18 *** 建议:在用put()方法时,先查看当前put pointer的值,防止误写 19 ----------------------------------------------------------------------------*/ 20 long curPos = ostr2.tellp(); //返回当前插入的索引位置(即put pointer的值),从0开始 21 cout << "curPos = " << curPos << endl; 22 23 ostr2.seekp(2); // 手动设置put pointer的值 24 ostr2.put('g'); // 在put pointer的位置上写入'g',并将put pointer指向下一个字符位置 25 cout << ostr2.str() << endl; 26 27 28 /*---------------------------------------------------------------------------- 29 *** 重复使用同一个ostringstream对象时,建议: 30 *** 1:调用clear()清除当前错误控制状态,其原型为 void clear (iostate state=goodbit); 31 *** 2:调用str("")将缓冲区清零,清除脏数据 32 ----------------------------------------------------------------------------*/ 33 ostr2.clear(); 34 ostr2.str(""); 35 36 cout << ostr2.str() << endl; 37 ostr2.str("_def"); 38 cout << ostr2.str() << endl; 39 ostr2 << "gggghh"; // 覆盖原有的数据,并自动增加缓冲区 40 cout << ostr2.str() << endl; 41 ostr2.str(""); // 若不加这句则运行时错误,因为_df所用空间小于gggghh,导致读取脏数据 42 ostr2.str("_df"); 43 cout << ostr2.str() << endl; 44 45 // 输出随机内存值,危险 46 const char* buf = ostr2.str().c_str(); 47 cout << buf << endl; 48 49 // 正确输出_df 50 string ss = ostr2.str(); 51 const char* buffer = ss.c_str(); 52 cout << buffer << endl; 53 }
参考资料
1. istringstream解析【cppreference.com】
2. ostringstream解析【cppreference.com】
3.stringstream解析【cppreference.com】
2.

浙公网安备 33010602011771号