与cout缩进段落

时间:2011-03-12 05:49:32

标签: c++ string-formatting cout

给定一个未知长度的字符串,如何使用cout输出它,以便整个字符串在控制台上显示为缩进的文本块? (这样即使字符串换行到新行,第二行也会有相同的缩进级别)

示例:

cout << "This is a short string that isn't indented." << endl;
cout << /* Indenting Magic */ << "This is a very long string that will wrap to the next line because it is a very long string that will wrap to the next line..." << endl;

所需的输出:

  

这是一个没有缩进的短字符串。

    This is a very long string that will
    wrap to the next line because it is a
    very long string that will wrap to the
    next line...

编辑:我正在完成的家庭作业已完成。该赋值与将输出格式化为无关,如上例所示,因此我可能不应该包含homework标记。这只是为了我自己的启发。

我知道我可以计算字符串中的字符,看到当我到达一行的末尾,然后每次吐出换行符并输出-x-个空格数。我很想知道是否有更简单,惯用的C ++方法来实现上述目标。

5 个答案:

答案 0 :(得分:9)

如果您愿意在单词之间丢弃任何多个间距和/或其他空格,以下是一些可行的解决方案。

第一种方法,最直接的方法是将文本读入istringstream并从流中提取单词。在打印每个单词之前,检查单词是否适合当前行,如果不适合则打印换行符。这个特定的实现不会正确处理超过最大行长度的单词,但是修改它以分割长单词并不困难。

#include <iostream>
#include <sstream>
#include <string>

int main() {
    const unsigned max_line_length(40);
    const std::string line_prefix("    ");

    const std::string text(
        "Friends, Romans, countrymen, lend me your ears; I come to bury Caesar,"
        " not to praise him.  The evil that men do lives after them; The good "
        "is oft interred with their bones; So let it be with Caesar.");

    std::istringstream text_iss(text);

    std::string word;
    unsigned characters_written = 0;

    std::cout << line_prefix;
    while (text_iss >> word) {

        if (word.size() + characters_written > max_line_length) {
            std::cout << "\n" << line_prefix;
            characters_written = 0;
        }

        std::cout << word << " ";
        characters_written += word.size() + 1;
    }
    std::cout << std::endl;
}

第二个更“高级”的选项是编写一个自定义ostream_iterator,用于格式化您希望格式化的行。我已将此ff_ostream_iterator命名为“有趣的格式”,但如果您想使用它,可以将其命名为更合适的名称。此实现可以正确地拆分长字。

虽然迭代器实现有点复杂,但用法非常简单:

int main() {
    const std::string text(
        "Friends, Romans, countrymen, lend me your ears; I come to bury Caesar,"
        " not to praise him.  The evil that men do lives after them; The good "
        "is oft interred with their bones; So let it be with Caesar. ReallyLong"
        "WordThatWontFitOnOneLineBecauseItIsSoFreakinLongSeriouslyHowLongIsThis"
        "Word");

    std::cout << "    ========================================" << std::endl;

    std::copy(text.begin(), text.end(), 
              ff_ostream_iterator(std::cerr, "    ", 40));
}

迭代器的实际实现如下:

#include <cctype>
#include <iostream>
#include <iterator>
#include <memory>
#include <sstream>
#include <string>

class ff_ostream_iterator 
    : public std::iterator<std::output_iterator_tag, char, void, void, void>
{
public:

    ff_ostream_iterator() { }

    ff_ostream_iterator(std::ostream& os,
                        std::string line_prefix, 
                        unsigned max_line_length)
        : os_(&os),
          line_prefix_(line_prefix), 
          max_line_length_(max_line_length),
          current_line_length_(),
          active_instance_(new ff_ostream_iterator*(this))
    { 
        *os_ << line_prefix;
    }

    ~ff_ostream_iterator() {
        if (*active_instance_ == this)
            insert_word();
    }

    ff_ostream_iterator& operator=(char c) {
        *active_instance_ = this;
        if (std::isspace(c)) {
            if (word_buffer_.size() > 0) {
                insert_word();
            }
        }
        else {
            word_buffer_.push_back(c);
        }
        return *this;
    }

    ff_ostream_iterator& operator*()     { return *this; }
    ff_ostream_iterator& operator++()    { return *this; }
    ff_ostream_iterator  operator++(int) { return *this; }


private:

    void insert_word() {
        if (word_buffer_.size() == 0)
            return; 

        if (word_buffer_.size() + current_line_length_ <= max_line_length_) {
            write_word(word_buffer_);
        }
        else { 
            *os_ << '\n' << line_prefix_;

            if (word_buffer_.size() <= max_line_length_) {
                current_line_length_ = 0;
                write_word(word_buffer_);
            }
            else {
                for (unsigned i(0);i<word_buffer_.size();i+=max_line_length_) 
                {
                    current_line_length_ = 0;
                    write_word(word_buffer_.substr(i, max_line_length_));
                    if (current_line_length_ == max_line_length_) {
                        *os_ << '\n' << line_prefix_;
                    }
                }
            }
        }

        word_buffer_ = "";
    }

    void write_word(const std::string& word) {
        *os_ << word;
        current_line_length_ += word.size();
        if (current_line_length_ != max_line_length_) {
            *os_ << ' ';
            ++current_line_length_;
        }
    }

    std::ostream* os_;
    std::string word_buffer_;

    std::string line_prefix_;
    unsigned max_line_length_;
    unsigned current_line_length_;

    std::shared_ptr<ff_ostream_iterator*> active_instance_;
};

[如果您从上面复制并粘贴此代码段和main,如果您的编译器支持C ++ 0x std::shared_ptr,它应该编译并运行。如果你的编译器还没有C ++ 0x支持,你可以用boost::shared_ptrstd::tr1::shared_ptr替换它。]

这种方法有点棘手,因为迭代器必须是可复制的,我们必须确保任何剩余的缓冲文本只打印一次。我们依靠以下事实来做到这一点:无论何时写入输出迭代器,它的任何副本都不再可用。

答案 1 :(得分:4)

这仍然可以使用一些工作(例如,indent应该可以实现为操纵器,但带参数的操纵器 hard 可以移植写入 - 标准不会真的支持/定义它们。可能至少有几个不完美的角落情况(例如,现在,它将背景视为正常角色)。

#include <iostream>
#include <streambuf>
#include <iomanip>

class widthbuf: public std::streambuf {
public:
    widthbuf(int w, std::streambuf* s): indent_width(0), def_width(w), width(w), sbuf(s), count(0) {}
    ~widthbuf() { overflow('\n'); }
    void set_indent(int w) { 
        if (w == 0) {
            prefix.clear();
            indent_width = 0;
            width = def_width;
        }
        else {
            indent_width += w; 
            prefix = std::string(indent_width, ' ');
            width -= w; 
        }
    }
private:
    typedef std::basic_string<char_type> string;

    // This is basically a line-buffering stream buffer.
    // The algorithm is: 
    // - Explicit end of line ("\r" or "\n"): we flush our buffer 
    //   to the underlying stream's buffer, and set our record of
    //   the line length to 0.
    // - An "alert" character: sent to the underlying stream
    //   without recording its length, since it doesn't normally
    //   affect the a appearance of the output.
    // - tab: treated as moving to the next tab stop, which is
    //   assumed as happening every tab_width characters. 
    // - Everything else: really basic buffering with word wrapping. 
    //   We try to add the character to the buffer, and if it exceeds
    //   our line width, we search for the last space/tab in the 
    //   buffer and break the line there. If there is no space/tab, 
    //   we break the line at the limit.
    int_type overflow(int_type c) {
        if (traits_type::eq_int_type(traits_type::eof(), c))
            return traits_type::not_eof(c);
        switch (c) {
        case '\n':
        case '\r': {
                        buffer += c;
                        count = 0;
                        sbuf->sputn(prefix.c_str(), indent_width);
                        int_type rc = sbuf->sputn(buffer.c_str(), buffer.size());
                        buffer.clear();
                        return rc;
                   }
        case '\a':
            return sbuf->sputc(c);
        case '\t':
            buffer += c;
            count += tab_width - count % tab_width;
            return c;
        default:
            if (count >= width) {
                size_t wpos = buffer.find_last_of(" \t");
                if (wpos != string::npos) {
                    sbuf->sputn(prefix.c_str(), indent_width);
                    sbuf->sputn(buffer.c_str(), wpos);
                    count = buffer.size()-wpos-1;
                    buffer = string(buffer, wpos+1);
                }
                else {
                    sbuf->sputn(prefix.c_str(), indent_width);
                    sbuf->sputn(buffer.c_str(), buffer.size());
                    buffer.clear();
                    count = 0;
                }
                sbuf->sputc('\n');
            }
            buffer += c;
            ++count;
            return c;
        }
    }

    size_t indent_width;
    size_t width, def_width;
    size_t count;
    size_t tab_count;
    static const int tab_width = 8;
    std::string prefix;

    std::streambuf* sbuf;

    string buffer;
};

class widthstream : public std::ostream {
    widthbuf buf;
public:
    widthstream(size_t width, std::ostream &os) : buf(width, os.rdbuf()), std::ostream(&buf) {}
    widthstream &indent(int w) { buf.set_indent(w); return *this; }
};

int main() {
    widthstream out(30, std::cout);
    out.indent(10) << "This is a very long string that will wrap to the next line because it is a very long string that will wrap to the next line.\n";
    out.indent(0) << "This is\tsome\tmore text that should not be indented but should still be word wrapped to 30 columns.";
}

请注意,indent(0)是一种特殊情况。通常缩进从0开始。调用yourstream.indent(number)其中number为正或负调整相对于先前值的缩进。 yourstream.indent(0) 不会做任何事情,但我特意将它缩进为0(作为绝对值)。如果这个问题得到了认真使用,我不确定从长远来看是否能达到最佳状态,但至少对于演示而言似乎已经足够了。

答案 2 :(得分:1)

我不确定这是 的方式,但如果您知道或可以假设屏幕宽度,我首先想到的是删除第一个screenWidth - indent字符字符串并使用前面的空格打印它们,并继续这样做,直到你完成整个字符串。

答案 3 :(得分:1)

你总是可以使用'\t'来缩进行而不是一定数量的字符,但我知道没有更简单的方法来实现逻辑而不引入外部库。

答案 4 :(得分:0)

这个问题可以简化为每行最小化浪费空间量的任务。 假设我们有以下长度的单词

{ 6,7,6,8,10,3,4,10 }

如果我们计算当我们安排最后n个单词而不打破它们并将其放入表格时将浪费的空间量,那么我们可以找到在当前行上打印的最佳单词数量。

以下是20个字符宽屏幕的示例。在此表中,第一列是最后一个字的数字,第二列是从结尾开始的第n个字的长度,第三列是最小的空间浪费:

8 6 1
7 7 7
6 5 14
5 8 2
4 10 11
3 3 1 
2 4 5
1 10 10

例如,当我们只有10个字母的最后一个单词时,浪费10个字母,如果我们有2个单词,最后4个字符,那么我们将浪费5个字母(单词之间有一个空格)额外的3个字母单词将只留下一个空间浪费。添加另外10个字母的单词会让我们在2行上浪费11个字母,依此类推。

实施例

6, 7, 5 (0)
8, 10 (1)
3, 4, 10 (1) 

如果我们选择在第一行打印2个单词,浪费的空间确实是14.()中的数字显示浪费的空间。

6, 7 (6)
5, 8 (6)
10, 3, 4 (2)
4, 10 (6)

我认为这是一个众所周知的问题,我所描述的算法是动态编程的一个例子。