【问题标题】:How to easily indent output to ofstream?如何轻松地将输出缩进到 ofstream?
【发布时间】:2009-09-08 02:58:05
【问题描述】:

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

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

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

谢谢。

-威廉

【问题讨论】:

  • 也许是时候有人为此编写一个库了 :)
  • 它已经可用。它被称为一个方面。它用于格式化流输出。因此,流的用户可以正常输出数据。然后 facet 可以独立执行任何格式(因此输出格式可以仅通过更改流使用的 facet 来更改,而无需更改生成输出的代码)。

标签: c++ stream ofstream


【解决方案1】:

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

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
    }

};

见:Tom's notes below

【讨论】:

  • 这里的预期结果是什么?似乎不像这里宣传的那样工作:http://liveworkspace.org/code/T4tCi$0
  • @sehe:std::cout 很有趣。如果流已被使用,则 imbue() 将不适用于任何流。一些实现在 main() 之前使用 std::cout,因此上述代码中的 imbue 可能会失败()。但它始终适用于文件。所以检查文件 PLOP 的内容。
  • 我想我也看到它不适用于 ostringstream。我稍后会尝试检查
  • 使用 clang++ 编译时,我发出以下警告:警告:'IndentFacet' 没有外线虚拟方法定义;它的 vtable 将在每个翻译单元 [-Wweak-vtables] 中发出。这可以避免吗?
  • @MathieuDutourSikiric:是的。而不是将头文件中的所有函数拆分为两个文件,一个带有声明的头文件和一个带有定义的源文件。我只是将它们放在一起以便于在 stackoverflow 上显示。 但是我不会打扰我会关闭该警告(这只是意味着如果您更改此类,则需要强制重建包含此标头的所有翻译单元)。如果您不打算更改课程,那么这不是问题。
【解决方案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;
  }
}

【讨论】:

    【解决方案3】:

    添加此类功能的一种方法是编写一个过滤流缓冲区(即将 IO 操作转发到另一个流缓冲区但操纵传输的数据的流缓冲区),将缩进添加为其过滤操作的一部分。我举了一个编写 streambuf here 的例子,boost 提供了一个 library 来帮助。

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

    【讨论】:

      【解决方案4】:

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

      std::ios::sync_with_stdio(false);
      

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

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

      【讨论】:

      • 谢谢。多年来,我一直在寻找解决方案。
      【解决方案5】:

      我已经概括了 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.
      

      如果您对代码的实用性提供任何反馈,我将不胜感激。

      【讨论】:

      • 虽然这是一个使用 codecvt 进行自动缩进的好主意,但您的源代码几乎没有问题:1) 在循环中打印表格时,您不检查 _to 缓冲区溢出,这个很容易解决; (在下一条评论中继续)
      • 2) 解决了第一个问题后,libstdc++实现的codecvt还有一个问题,如果最后一次操作的结果是std::codecvt_base::partial,它只调用一次do_out,所以如果该行只有很少的字符(例如,左大括号)并且缩进级别很大,一些字符会丢失。我不知道如何正确解决这个问题,我知道的唯一丑陋的解决方案是覆盖do_max_length() 以返回一个很大的值,迫使 libstdc++ 分配足够大的输出缓冲区。
      • @segfault 所有这些疯狂的方面解决方案,需要非常了解这些细节、角落和彻底的错误;是精神错乱。希望使用 rangeV3 或 C++20,您只需执行 cout &lt;&lt; (mydata | indent_view) &lt;&lt; "\n"; 并完成您的一天。
      【解决方案6】:

      没有简单的方法,但是关于复杂的已经写了很多 实现这一目标的方法。 Read this article 很好的解释 话题。 Here is another article,不幸的是德语。但 its source code 应该可以帮到你。

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

      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; 
      
      }
      

      【讨论】:

        【解决方案7】:

        简单的空格操作符

        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;
        }
        

        【讨论】:

          猜你喜欢
          • 2015-05-23
          • 1970-01-01
          • 1970-01-01
          • 2012-10-05
          • 2012-06-15
          • 1970-01-01
          • 2015-09-13
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多