使用std::ostream在每行之前插入文本

inserting text before each line using std::ostream

本文关键字:插入文本 std ostream 使用      更新时间:2023-10-16

我想知道是否有可能从std::ostream继承,并以这样一种方式重写flush(),即将一些信息(例如,行号)添加到每行的开头。然后我想通过rdbuf()将它附加到std::ofstream(或cout),这样我就得到了这样的东西:

ofstream fout("file.txt");
myostream os;
os.rdbuf(fout.rdbuf());
os << "this is the first line.n";
os << "this is the second line.n";

会将其放入file.txt

1 this is the first line.
2 this is the second line.

flush()不是在此上下文中要覆盖的函数,尽管您的思路是正确的。应该在底层的std::streambuf接口上重新定义overflow()。例如:

class linebuf : public std::streambuf
{
public:
    linebuf() : m_sbuf() { m_sbuf.open("file.txt", std::ios_base::out); }
    int_type overflow(int_type c) override
    {
        char_type ch = traits_type::to_char_type(c);
        if (c != traits_type::eof() && new_line)
        {
            std::ostream os(&m_sbuf);
            os << line_number++ << " ";
        }
        new_line = (ch == 'n');
        return m_sbuf.sputc(ch);
    }
    int sync() override { return m_sbuf.pubsync() ? 0 : -1; }
private:
    std::filebuf m_sbuf;
    bool new_line = true;
    int line_number = 1;
};

现在你可以这样做:

linebuf buf;
std::ostream os(&buf);
os << "this is the first line.n";  // "1 this is the first line."
os << "this is the second line.n"; // "2 this is the second line."
<<p> 生活例子/kbd>

James Kanze关于过滤流bufs的经典文章有一个非常相似的例子,在每行的开头放置一个时间戳。你可以修改代码

或者,您也可以使用从那篇文章的思想中衍生出来的Boost工具。

#include <boost/iostreams/filtering_stream.hpp>
#include <boost/array.hpp>
#include <cstring>
#include <limits>
// line_num_filter is a model of the Boost concept OutputFilter which
// inserts a sequential line number at the beginning of every line.
class line_num_filter
    : public boost::iostreams::output_filter
{
public:
    line_num_filter();
    template<typename Sink>
    bool put(Sink& snk, char c);
    template<typename Device>
    void close(Device&);
private:
    bool m_start_of_line;
    unsigned int m_line_num;
    boost::array<char, std::numeric_limits<unsigned int>::digits10 + 4> m_buf;
    const char* m_buf_pos;
    const char* m_buf_end;
};
line_num_filter::line_num_filter() :
    m_start_of_line(true),
    m_line_num(1),
    m_buf_pos(m_buf.data()),
    m_buf_end(m_buf_pos)
{}
// put() must return true if c was written to dest, or false if not.
// After returning false, put() with the same c might be tried again later.
template<typename Sink>
bool line_num_filter::put(Sink& dest, char c)
{
    // If at the start of a line, print the line number into a buffer.
    if (m_start_of_line) {
        m_buf_pos = m_buf.data();
        m_buf_end = m_buf_pos +
            std::snprintf(m_buf.data(), m_buf.size(), "%u ", m_line_num);
        m_start_of_line = false;
    }
    // If there are buffer characters to be written, write them.
    // This can be interrupted and resumed if the sink is not accepting
    // input, which is why the buffer and pointers need to be members.
    while (m_buf_pos != m_buf_end) {
        if (!boost::iostreams::put(dest, *m_buf_pos))
            return false;
        ++m_buf_pos;
    }
    // Copy the actual character of data.
    if (!boost::iostreams::put(dest, c))
        return false;
    // If the character copied was a newline, get ready for the next line.
    if (c == 'n') {
        ++m_line_num;
        m_start_of_line = true;
    }
    return true;
}
// Reset the filter object.
template<typename Device>
void line_num_filter::close(Device&)
{
    m_start_of_line = true;
    m_line_num = 1;
    m_buf_pos = m_buf_end = m_buf.data();
}

int main() {
    using namespace boost::iostreams;
    filtering_ostream myout;
    myout.push(line_num_filter());
    myout.push(std::cout);
    myout << "this is the first line.n";
    myout << "this is the second line.n";
}