【问题标题】:C++ stream operator questionC ++流运算符问题
【发布时间】:2011-01-23 12:04:42
【问题描述】:

我想这对于这里的所有大师来说可能是一个简单的问题,但我不知何故无法找到答案。

我希望能够像这样简单地将 csv 单元格写入流:

stream << 1 << 2 << "Tom" << std::endl;

这将创建像 1,2,Tom 这样的输出。我怎样才能做到这一点?我认为我需要创建自定义 streambuf(因为我认为这不是在流级别上执行此操作的正确方法,只是为所有类型重载

如果有任何帮助,我将不胜感激:)

干杯,

【问题讨论】:

    标签: c++ csv stream


    【解决方案1】:

    达到 98% 的难度并不难:

    #include <iostream>
    
    class add_comma { 
        std::ostream &os;
        bool begin;
        typedef add_comma &ref;
    public:
        add_comma(std::ostream &o) : os(o), begin(true) {}
    
        template <class T>
        ref operator<<(T const &t) { 
            if (!begin)
                os << ",";
            os << "\"" << t << "\"";
            begin = false;
            return *this;
        }
    
        ref operator<<(std::ostream &manip(std::ostream &o) ) {
            if (&manip == &std::endl)
                reset();
            manip(os);
            return *this;
        }
    
        void reset() { begin = true; }
    
        operator void *() { return (void *)os; }
    };
    
    int main() { 
        add_comma a(std::cout);
    
        a << 1 << 2 << "This is a string" << std::endl;
        a << 3 << 4 << "Another string" << std::endl;
        return 0;
    }
    

    编辑:我至少在某种程度上修复了代码——它现在只在写入的项目之间放置逗号,而不是在行首。然而,它只将“endl”识别为新记录的开始——例如,字符串文字中的换行符不起作用。

    【讨论】:

    • 是的,我之前确实想过这个解决方案,但它不适用于操纵器(在重载的运算符函数中可以很容易地解决到处都有逗号和引用整个单元格的其他问题,如果它包含分隔符) .现在当我再次考虑它时,我想我也可以为操纵器覆盖
    • 我已经做了更多的工作。现在已经到了它可能至少可以合理使用的程度。它不会处理带参数的操纵器,但这应该只是添加(但)另一个重载的问题。不应该太可怕,但无论如何都是痛苦的。
    • 这是一个很好的解决方案。假设有一些替代方法来定义包装类(例如,从 std::ostream 继承并覆盖某些东西,或者一些魔术 csv_mode 操纵器)。如果你想插入用户定义的类型,你会遇到麻烦。自定义operator&lt;&lt; 方法通常使用内置的operator&lt;&lt; 方法实现,因此您最终会得到额外的逗号。
    • @Dan:True,但一般来说,在 CSV 文件中,您几乎需要将任何聚合类型分解为其组成部分。通常的目标是电子表格或数据库之类的东西,您(通常)希望将这些部分中的每一个放在单独的单元格/字段中。当然,这不是你想要的,修复它会有点有趣......
    • @Dan:我记得我曾经在这里被告知我不应该定义我自己的继承自 std::*stream 的类,因为它们不是为了那个目的,而是你应该创建自定义streambuf 类,这就是为什么我问这是否是一个好的解决方案:) @Jerry:你的编辑基本上就是我的想法。我只需要创建自己的 endl 操纵器(或自己的 streambuf),因为有时我需要使用 dos endlines。
    【解决方案2】:

    虽然我可以理解重载流运算符的想法,但我会质疑手头问题的做法。

    1.面向对象的方法

    如果您愿意写入.csv 文件,那么每一行的格式应该与其他行完全相同?不幸的是,您的流操作员没有检查它。

    我认为您需要创建一个Line 对象,而不是流式传输,并且在将它们写入文件之前验证每个字段(并以正确的格式写入它们)。虽然不那么时髦,但您将有更多机会在这里实现强大的实现。

    假设(例如)您要输出 2 个整数和一个字符串:

    class Line
    {
    public:
      Line(int foo, int bar, std::string firstName):
        mFoo(foo), mBar(bar), mFirstName(firstName)
    
      friend std::ostream& operator<<(std::ostream& out, const Line& line)
      {
        return out << line.mFoo << ',' << line.mBar << ','
                   << line.mFirstName << std::endl;
      }
    private:
      int mFoo;
      int mBar;
      std::string mFirstName;
    };
    

    而且使用它仍然非常简单:

    std::cout << Line(1,3,"Tom") << Line(2,4,"John") << Line(3,5,"Edward");
    

    2。想玩得开心吗?

    现在,这可能看起来很乏味,您可能希望玩,但仍然可以控制所写的内容......好吧,让我将模板元编程引入战斗;)

    这是预期的用途:

    // Yeah, I could wrap this mpl_::vector bit... but it takes some work!
    typedef CsvWriter< mpl_::vector<int,int,std::string> > csv_type;
    
    csv_type(std::cout) << 1 << 3 << "Tom" << 2 << 4 << "John" << 3 << 5 << "Edward";
    
    csv_type(std::cout) << 1 << 2 << 3; // Compile Time Error:
                                        // 3 is not convertible to std::string
    

    现在这会很有趣吧?它将格式化该行并确保验证的度量......人们总是可以使设计复杂化,以便它做更多的事情(比如为每个字段或整行注册验证器等......)但它已经足够复杂了。

    // namespace mpl_ = boost::mpl
    
    /// Sequence: MPL sequence
    /// pos: mpl_::size_t<N>, position in the Sequence
    
    namespace result_of {
      template <class Sequence, class pos> struct operator_in;
    }
    
    template < class Sequence, class pos = mpl_::size_t<0> >
    class CsvWriter
    {
    public:
      typedef typename mpl_::at<Sequence,pos>::type current_type;
      typedef typename boost::call_traits<current_type>::param_type param_type;
    
      CsvWriter(std::ostream& out): mOut(out) {}
    
      typename result_of::operator_in<Sequence,pos>::type
      operator<<(param_type item)
      {
        typedef typename result_of::operator_in<Sequence,pos>::type result_type;
    
        if (pos::value != 0) mOut << ',';
        mOut << item;
    
        if (result_type::is_last_type::value) mOut << std::endl;              
    
        return result_type(mOut);
      }
    
    private:
      std::ostream& mOut;
    }; // class CsvWriter
    
    
    /// Lil' bit of black magic
    namespace result_of { // thanks Boost for the tip ;)
    
      template <class Sequence, class pos>
      struct operator_in
      {
        typedef typename boost::same_type<
            typename mpl_::size<Sequence>::type,
            typename mpl_::next<pos>::type
          > is_last_type;
    
        typedef typename mpl_::if_<
          is_last_type,
          CsvWriter< Sequence, mpl_::size_t<0> >,
          CsvWriter< Sequence, typename mpl_::next<pos>::type >
        >::type;
      }; // struct operator_in<Sequence,pos>
    
    } // namespace result_of
    

    这里有一个流编写器,可确保 cvs 文件格式正确...在字符串中显示换行符;)

    【讨论】:

    • 对于 1) 实际上没有:)。我通常需要以第 3 方格式编写 CSV,并且每一行可以采用不同的格式(我会说这是一种协议,csvs 用作“数据包”:))。但我说的是一般性 - 只是想向这个 CSV 对象扔一些东西,它会处理正确的 CSV 格式...... 2)哇,那个代码看起来真的很好,但我必须看看它实际上做了什么 :))
    【解决方案3】:

    如果 A 是元素的迭代器...

    copy(A, A + N, ostream_iterator<int>(cout, ","));
    

    【讨论】:

    • 嗯,不知道这对我有什么帮助?我没有将值写入流的任何容器,它们是在生成时写入的。此外,迭代器适用于特定类型,但我想以与普通流相同的方式编写任何内容。
    猜你喜欢
    • 2023-04-10
    • 2011-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-14
    • 2021-10-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多