7

给定一个长度未知的字符串,如何使用 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...

编辑:我正在做的家庭作业已经完成。该分配与将输出格式化为上述示例无关,因此我可能不应该包含作业标记。这只是为了我自己的启蒙。

我知道我可以计算字符串中的字符,看看我何时到达行尾,然后吐出一个换行符并每次输出 -x- 个空格。我很想知道是否有更简单、惯用的 C++ 方法来完成上述操作。

4

5 回答 5

11

如果您愿意放弃单词之间的任何多个空格和/或其他空格,这里有几个解决方案将起作用。

第一种方法是最直接的,将文本读入一个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_ptrboost::shared_ptr如果你的编译器std::tr1::shared_ptr还没有 C++0x 支持,你可以替换它。]

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

于 2011-03-12T06:15:47.287 回答
5

这仍然需要做一些工作(例如,indent可能应该将其实现为操纵器,但是带有参数的操纵器很难以可移植的方式编写——标准并不真正支持/定义它们)。可能至少有几个不完美的极端情况(例如,现在,它将退格视为正常字符)。

#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)where numberis 无论是正数还是负数都会相对于前一个值调整缩进。yourstream.indent(0) 不会做任何事情,但我特例将缩进重置为 0(绝对)。如果这被认真使用,我不确定从长远来看它会发挥出最好的效果,但至少对于演示来说它似乎足够了。

于 2011-03-12T10:44:57.910 回答
1

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

于 2011-03-12T06:13:29.613 回答
1

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

于 2011-03-12T06:14:10.100 回答
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)

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

于 2011-03-12T08:32:55.090 回答