给定一个未知长度的字符串,如何使用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 ++方法来实现上述目标。
答案 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_ptr
或std::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)
我认为这是一个众所周知的问题,我所描述的算法是动态编程的一个例子。