17

有没有一种简单的方法可以将输出缩进到 ofstream 对象?我有一个空终止并包含换行符的 C++ 字符数组。我想将其输出到流中,但每行缩进两个空格。是否有一种简单的方法可以使用流操纵器来执行此操作,例如您可以使用流的特殊指令更改整数输出的基数,或者我是否必须手动处理数组并在检测到的每个换行符处手动插入额外的空格?

似乎 string::right() 操纵器很接近:

http://www.cplusplus.com/reference/iostream/manipulators/right/

谢谢。

-威廉

4

7 回答 7

21

这是使用构面的完美情况。

可以将 codecvt facet 的自定义版本灌输到流中。

所以你的用法看起来像这样:

int main()
{
    /* Imbue std::cout before it is used */
    std::ios::sync_with_stdio(false);
    std::cout.imbue(std::locale(std::locale::classic(), new IndentFacet()));

    std::cout << "Line 1\nLine 2\nLine 3\n";

    /* You must imbue a file stream before it is opened. */
    std::ofstream       data;
    data.imbue(indentLocale);
    data.open("PLOP");

    data << "Loki\nUses Locale\nTo do something silly\n";
}

刻面的定义稍微复杂一些。
但重点是使用构面的人不需要了解有关格式的任何信息。格式的应用与流的使用方式无关。

#include <locale>
#include <algorithm>
#include <iostream>
#include <fstream>

class IndentFacet: public std::codecvt<char,char,std::mbstate_t>
{
  public:
   explicit IndentFacet(size_t ref = 0): std::codecvt<char,char,std::mbstate_t>(ref)    {}

    typedef std::codecvt_base::result               result;
    typedef std::codecvt<char,char,std::mbstate_t>  parent;
    typedef parent::intern_type                     intern_type;
    typedef parent::extern_type                     extern_type;
    typedef parent::state_type                      state_type;

    int&    state(state_type& s) const          {return *reinterpret_cast<int*>(&s);}
  protected:
    virtual result do_out(state_type& tabNeeded,
                         const intern_type* rStart, const intern_type*  rEnd, const intern_type*&   rNewStart,
                         extern_type*       wStart, extern_type*        wEnd, extern_type*&         wNewStart) const
    {
        result  res = std::codecvt_base::noconv;

        for(;(rStart < rEnd) && (wStart < wEnd);++rStart,++wStart)
        {
            // 0 indicates that the last character seen was a newline.
            // thus we will print a tab before it. Ignore it the next
            // character is also a newline
            if ((state(tabNeeded) == 0) && (*rStart != '\n'))
            {
                res                 = std::codecvt_base::ok;
                state(tabNeeded)    = 1;
                *wStart             = '\t';
                ++wStart;
                if (wStart == wEnd)
                {
                    res     = std::codecvt_base::partial;
                    break;
                }
            }
            // Copy the next character.
            *wStart         = *rStart;

            // If the character copied was a '\n' mark that state
            if (*rStart == '\n')
            {
                state(tabNeeded)    = 0;
            }
        }

        if (rStart != rEnd)
        {
            res = std::codecvt_base::partial;
        }
        rNewStart   = rStart;
        wNewStart   = wStart;

        return res;
    }

    // Override so the do_out() virtual function is called.
    virtual bool do_always_noconv() const throw()
    {
        return false;   // Sometime we add extra tabs
    }

};

见:下面汤姆的笔记

于 2009-09-09T04:52:42.567 回答
2

添加此类功能的一种方法是编写一个过滤streambuf(即一个将IO操作转发到另一个streambuf但操纵传输的数据的streambuf),它将缩进作为其过滤操作的一部分。我在这里举了一个编写streambuf的例子,boost提供了一个来帮助解决这个问题。

如果您的情况,overflow() 成员将简单地测试 '\n',然后在需要时添加缩进(正是您在indentedOuput函数中所做的,除非它newline是 streambuf 的成员)。您可能有一个设置来增加或减少缩进大小(也许可以通过操纵器访问,操纵器必须执行 dynamic_cast 以确保与流关联的 streambuf 是正确的类型;有一种添加用户的机制要流式传输的数据——basic_ios::xalloc、iword 和 pword——但在这里我们要对 streambuf 进行操作)。

于 2009-09-08T06:53:35.973 回答
2

我在 Martin 的基于codecvt 方面的建议上取得了很好的成功,但是我在 OSX 上的 std::cout 上使用它时遇到了问题,因为默认情况下,这个流使用基于 basic_streambuf 的流缓冲,它忽略了灌输方面。以下行切换 std::cout 和朋友使用基于 basic_filebuf 的 streambuf,它将使用 imbued facet。

std::ios::sync_with_stdio(false);

相关的副作用是 iostream 标准流对象可以独立于标准 C 流运行。

另一个注意事项是因为这个方面没有静态 std::locale::id,这意味着在语言环境上调用 std::has_facet<IndentFacet> 总是返回 true。添加 std::local::id 意味着未使用该构面,因为 basic_filebuf 会查找基类模板。

于 2011-07-09T02:04:05.393 回答
2

嗯,这不是我正在寻找的答案,但如果没有这样的答案,这里有一种手动执行此操作的方法:

void
indentedOutput(ostream &outStream, const char *message, bool &newline)
{
  while (char cur = *message) {
    if (newline) {
      outStream << "  ";
      newline = false;
    }
    outStream << cur;
    if (cur == '\n') {
      newline = true;
    }
    ++message;
  }
}
于 2009-09-08T03:26:18.900 回答
2

我已经概括了 Loki Astarti 的解决方案来处理任意缩进级别。该解决方案有一个漂亮、易于使用的界面,但实际实现有点可疑。可以在github上找到:https ://github.com/spacemoose/ostream_indenter

github repo 中有一个更复杂的演示,但给出:

#include "indent_facet.hpp"

/// This probably has to be called once for every program:
// http://stackoverflow.com/questions/26387054/how-can-i-use-stdimbue-to-set-the-locale-for-stdwcout
std::ios_base::sync_with_stdio(false);

// This is the demo code:
std::cout << "I want to push indentation levels:\n" << indent_manip::push
          << "To arbitrary depths\n" << indent_manip::push
          << "and pop them\n" << indent_manip::pop
          << "back down\n" << indent_manip::pop
          << "like this.\n" << indent_manip::pop;

}

它产生以下输出:

I want to push indentation levels:
    To arbitrary depths
        and pop them
    back down
like this.

对于代码的实用性,我将不胜感激。

于 2015-09-11T13:23:04.227 回答
1

没有简单的方法,但是关于实现这一点的复杂方法已经写了很多。阅读这篇文章以获得对该主题的良好解释。这是另一篇文章,不幸的是德语。但 它的源代码应该可以帮助你。

例如,您可以编写一个记录递归结构的函数。对于每个级别的递归,缩进都会增加:

std::ostream& operator<<(std::ostream& stream, Parameter* rp) 
{
    stream << "Parameter: " << std::endl;

    // Get current indent
    int w = format::get_indent(stream);

    stream << "Name: "  << rp->getName();
    // ... log other attributes as well

    if ( rp->hasParameters() )
    {
        stream << "subparameter (" << rp->getNumParameters() << "):\n";

        // Change indent for sub-levels in the hierarchy
        stream << format::indent(w+4);

        // write sub parameters        
        stream << rp->getParameters();
    }

    // Now reset indent
    stream << format::indent(w);

    return stream; 

}
于 2009-09-08T04:36:48.277 回答
0

简单的空格操作符

struct Whitespace
{
    Whitespace(int n)
        : n(n)
    {
    }
    int n;
};

std::ostream& operator<<(std::ostream& stream, const Whitespace &ws)
{
    for(int i = 0; i < ws.n; i++)
    {
        stream << " ";
    }
    return stream;
}
于 2014-11-17T09:16:58.850 回答