【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.

1. string和stringstream用法总结

posted @ 2020-10-13 22:08  苏格拉底的落泪  阅读(454)  评论(0)    收藏  举报