2011-03-12 101 views
6

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

编辑:家庭作业我的工作就完成了。与上面的例子一样,赋值与获取输出格式无关,因此我可能不应该包含作业标记。这仅仅是为了我自己的启发。

我知道我可以通过字符串中的字符进行计数,看看我什么时候到达行的末尾,然后吐出一个换行符并每次输出-x-个空格。我很想知道是否有一种更简单,惯用的C++方法来完成上述任务。

+0

你到目前为止尝试过什么?你是否编写了读取给定文本的代码? – 2011-03-12 05:51:24

+0

我曾尝试使用std :: setw()。我编写了读取给定文本的代码。我试图看看是否有一种简单的方法让cout自动将每行(包括从linewrapping产生的行)与给定数量的字符一起填充。 – 2011-03-12 05:53:35

+0

我可能不应该使用作业标签 - 我正在做家庭作业,但作业与用于输出的格式无关。我只是好奇这种格式如何完成。 – 2011-03-12 05:54:43

回答

7

下面是几个解决方案,如果你愿意抛出任何多个间距和/或其他空白字符之间的工作。

第一种方法最直接的方法是将文本读入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来代替。]

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

+0

嗯;我现在意识到我失去了“短串不缩进”的要求。对于那个很抱歉;我有点偏侧。然而,这不会太难实现,因为您可以事先测试字符串的长度并决定是将其缩进还是缩进。 – 2011-03-12 08:10:32

+0

你能好好看看我的答案吗?我在大部分其他人发布之后发布了它,所以我不认为这是被看到的。我会特别感兴趣的是,如果你能想到我错过的任何事情(或者对如何处理'\ b'有很好的建议 - 我已经想到了,但我不确定所有需要的东西,特别是如果你回退一个标签,超过行的开头,等等) – 2011-03-12 16:12:42

+0

哦,还有一件事:对于缩进,你认为最好是使用空格字符,或者对流设置了“填充”字符至? – 2011-03-12 16:27:12

1

我不知道这是做到这方式,但如果你知道或者可以假设屏幕的宽度,我首先想到的是从字符串中删除第一screenWidth - indent字符,并与前面的空格打印出来,并继续这样做,直到你完成了整个字符串。

1

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

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个字母的单词会给我们带来11个字母,总共浪费在2行上,依此类推。

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) 

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

4

这仍然可以使用工作的一点点(例如,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)其中number可以是正值或负值,用于调整相对于先前值的缩进值。yourstream.indent(0)不会做任何事情,但我已经专门将它重置为0(作为绝对)的缩进。如果这个问题得到了认真的使用,我不确定长期能够发挥出最好的效果,但至少在演示中它似乎相当方便。

+0

非常好。基于'streambuf'的方法肯定比'stream_iterator'更有意义。有'reset_indent(int)'而不是0黑客可能会更清洁。我不知道处理'\ b':我想最自然的方法是保持缓冲,直到'overflow'得到'\ r'或'\ n',因为你(通常?)不能退格换行符,但你可能最终会做很多不必要的缓冲。这是一个关于空格与填充字符的好问题;我想使用填充字符会更“灵活”,因为你仍然可以将它设置为空格。 – 2011-03-12 18:03:16