如何打印出矢量的内容?

How do I print out the contents of a vector?

提问人:forthewinwin 提问时间:5/25/2012 最后编辑:cigienforthewinwin 更新时间:8/8/2023 访问量:1190993

问:

如何将 a 的内容打印到屏幕上?std::vector


实现以下内容的解决方案也很好:operator<<

template<container C, class T, String delim = ", ", String open = "[", String close = "]">
std::ostream & operator<<(std::ostream & o, const C<T> & x)
{
  // ... What can I write here?
}

这是我到目前为止所拥有的,没有单独的功能:

#include <iostream>
#include <fstream>
#include <string>
#include <cmath>
#include <vector>
#include <sstream>
#include <cstdio>
using namespace std;

int main()
{
    ifstream file("maze.txt");
    if (file) {
        vector<char> vec(istreambuf_iterator<char>(file), (istreambuf_iterator<char>()));
        vector<char> path;
        int x = 17;
        char entrance = vec.at(16);
        char firstsquare = vec.at(x);
        if (entrance == 'S') { 
            path.push_back(entrance); 
        }
        for (x = 17; isalpha(firstsquare); x++) {
            path.push_back(firstsquare);
        }
        for (int i = 0; i < path.size(); i++) {
            cout << path[i] << " ";
        }
        cout << endl;
        return 0;
    }
}
C++ 矢量 输出 stdvector cout

评论

0赞 Matthieu M. 1/31/2011
作为信息,我发现“简洁”地做到这一点的唯一方法是黑客 - >在命名空间中添加重载(以便它们被 ADL 拾取)并将调用转发到通用打印范围方法......我对这次讨论的结果非常感兴趣,感谢您提问:)operator<<std
0赞 kirill_igum 8/7/2014
如果有异类类型,则混合使用 STL 容器和元组。将 IO 与漂亮的打印一起使用。boost.fusioncout << vector<tuple<int,array<int,3>>>(...) << endl;
0赞 markgalassi 3/18/2023
提醒:ENDL 几乎从来都不是人们想象的那样;它应该从这个例子中删除!除非您在终端上执行特殊字符图形操作,否则您实际上永远不应该使用 ENDL。请改用“\n”。我想知道是否有办法将消息发送给所有 C++ 答案。

答:

15赞 Marcelo Cantos 1/31/2011 #1

这是一个工作库,作为一个完整的工作程序呈现,我刚刚破解了它:

#include <set>
#include <vector>
#include <iostream>

#include <boost/utility/enable_if.hpp>

// Default delimiters
template <class C> struct Delims { static const char *delim[3]; };
template <class C> const char *Delims<C>::delim[3]={"[", ", ", "]"};
// Special delimiters for sets.                                                                                                             
template <typename T> struct Delims< std::set<T> > { static const char *delim[3]; };
template <typename T> const char *Delims< std::set<T> >::delim[3]={"{", ", ", "}"};

template <class C> struct IsContainer { enum { value = false }; };
template <typename T> struct IsContainer< std::vector<T> > { enum { value = true }; };
template <typename T> struct IsContainer< std::set<T>    > { enum { value = true }; };

template <class C>
typename boost::enable_if<IsContainer<C>, std::ostream&>::type
operator<<(std::ostream & o, const C & x)
{
  o << Delims<C>::delim[0];
  for (typename C::const_iterator i = x.begin(); i != x.end(); ++i)
    {
      if (i != x.begin()) o << Delims<C>::delim[1];
      o << *i;
    }
  o << Delims<C>::delim[2];
  return o;
}

template <typename T> struct IsChar { enum { value = false }; };
template <> struct IsChar<char> { enum { value = true }; };

template <typename T, int N>
typename boost::disable_if<IsChar<T>, std::ostream&>::type
operator<<(std::ostream & o, const T (&x)[N])
{
  o << "[";
  for (int i = 0; i != N; ++i)
    {
      if (i) o << ",";
      o << x[i];
    }
  o << "]";
  return o;
}

int main()
{
  std::vector<int> i;
  i.push_back(23);
  i.push_back(34);

  std::set<std::string> j;
  j.insert("hello");
  j.insert("world");

  double k[] = { 1.1, 2.2, M_PI, -1.0/123.0 };

  std::cout << i << "\n" << j << "\n" << k << "\n";
}

它目前仅适用于 和 ,但可以通过扩展专业化来使它适用于大多数容器。我没有过多考虑这个代码是否最小,但我不能立即想到任何我可以剥离为多余的东西。vectorsetIsContainer

编辑:只是为了踢球,我提供了一个处理数组的版本。我不得不排除 char 数组以避免进一步的歧义;它可能仍然会遇到麻烦。wchar_t[]

评论

2赞 Marcelo Cantos 1/31/2011
@Nawaz:正如我所说,这只是解决方案的开始。您可以通过专门化运算符或定义 for 来支持。std::map<>operator<<std::pair<>
0赞 Nawaz 1/31/2011
但是,+1 用于使用类模板!Delims
0赞 Kerrek SB 1/31/2011
@MC:那好。这看起来很有希望!(顺便说一句,你需要返回类型“std::ostream &”,我最初忘记了。
0赞 Kerrek SB 1/31/2011
嗯,在 std::vector<int 和 std::set<std::string> 上尝试此操作时,我遇到了“模棱两可的重载>...
0赞 Marcelo Cantos 1/31/2011
是的,我目前正在弄清楚如何防止歧义,这是由于模板几乎与任何东西都匹配而引起的。operator<<
22赞 CashCow 1/31/2011 #2

这已经编辑了几次,我们决定将包装集合的主类称为 。RangePrinter

一旦你写了一次性重载,这应该会自动适用于任何集合,除了你需要一个特殊的地图来打印对,并且可能想要在那里自定义分隔符。operator<<

您还可以在项目上使用一个特殊的“打印”函数,而不仅仅是直接输出它,有点像 STL 算法允许您传入自定义谓词。您将以这种方式使用它,并使用自定义打印机进行 .mapstd::pair

您的“默认”打印机只会将其输出到流中。

好的,让我们在自定义打印机上工作。我将把我的外部类改为 .因此,我们有 2 个迭代器和一些分隔符,但尚未自定义如何打印实际项目。RangePrinter

struct DefaultPrinter
{
   template< typename T >
   std::ostream & operator()( std::ostream& os, const T& t ) const
   {
     return os << t;
   }

   // overload for std::pair
   template< typename K, typename V >
   std::ostream & operator()( std::ostream & os, std::pair<K,V> const& p)
   {
      return os << p.first << '=' << p.second;
   }
};

// some prototypes
template< typename FwdIter, typename Printer > class RangePrinter;

template< typename FwdIter, typename Printer > 
  std::ostream & operator<<( std::ostream &, 
        RangePrinter<FwdIter, Printer> const& );

template< typename FwdIter, typename Printer=DefaultPrinter >
class RangePrinter
{
    FwdIter begin;
    FwdIter end;
    std::string delim;
    std::string open;
    std::string close;
    Printer printer;

    friend std::ostream& operator<< <>( std::ostream&, 
         RangePrinter<FwdIter,Printer> const& );

public:
    RangePrinter( FwdIter b, FwdIter e, Printer p,
         std::string const& d, std::string const & o, std::string const& c )
      : begin( b ), end( e ), printer( p ), open( o ), close( c )
    {
    } 

     // with no "printer" variable
    RangePrinter( FwdIter b, FwdIter e,
         std::string const& d, std::string const & o, std::string const& c )
      : begin( b ), end( e ), open( o ), close( c )
    {
    } 

};


template<typename FwdIter, typename Printer>
std::ostream& operator<<( std::ostream& os, 
          RangePrinter<FwdIter, Printer> const& range )
{
    const Printer & printer = range.printer;

    os << range.open;
    FwdIter begin = range.begin, end = range.end;

    // print the first item
    if (begin == end) 
    { 
      return os << range.close; 
    }

    printer( os, *begin );

    // print the rest with delim as a prefix
    for( ++begin; begin != end; ++begin )
    {
       os << range.delim;
       printer( os, *begin );
    }
    return os << range.close;
}

现在,默认情况下,只要键和值类型都是可打印的,并且您可以放入自己的特殊项目打印机,以备不时之需(就像使用任何其他类型一样),或者如果您不希望“=”作为分隔符。

我现在正在移动 free-function 来创建这些:

自由函数(迭代器版本)看起来像这样,你甚至可以有默认值:

template<typename Collection>
RangePrinter<typename Collection::const_iterator> rangePrinter
    ( const Collection& coll, const char * delim=",", 
       const char * open="[", const char * close="]")
{
   return RangePrinter< typename Collection::const_iterator >
     ( coll.begin(), coll.end(), delim, open, close );
}

然后,您可以将其用于std::set

 std::cout << outputFormatter( mySet );

您还可以编写采用自定义打印机的自由功能版本和需要两个迭代器的版本。在任何情况下,它们都会为您解析模板参数,并且您将能够将它们作为临时参数传递。

评论

0赞 Kerrek SB 1/31/2011
明白了。这与马塞洛·坎托斯(Marcelo Cantos)的想法相似,不是吗?我将尝试将其变成一个工作示例,谢谢!
0赞 Björn Pollex 1/31/2011
我发现这个解决方案比 Marcelo 的解决方案干净得多,而且它提供了相同的灵活性。我喜欢必须将输出显式包装到函数调用中的方面。为了真正酷,您可以添加对直接输出一系列迭代器的支持,这样我就可以了.std::cout << outputFormatter(beginOfRange, endOfRange);
1赞 Matthieu M. 1/31/2011
@CashCow:这个解决方案有一个问题,它似乎不适用于递归集合(即集合的集合)。 是“内藏”最基本的例子。std::pair
0赞 Kerrek SB 1/31/2011
我非常喜欢这个答案,因为它没有依赖关系,也不需要知道它支持的容器。我们能弄清楚它是否可以轻松处理,以及它是否适用于集合?不过,我很想接受这个答案。我希望马塞洛不介意,他的解决方案也有效。std::map
0赞 CashCow 1/31/2011
@Matthieu M.这取决于您如何打印内部集合。如果你只是使用操作系统<<打开<< *iter <<关闭,那么你就会有问题,但如果你允许你的用户按照我的建议传入自定义打印机,那么你可以打印任何你喜欢的东西。
84赞 Sven 6/5/2011 #3

此解决方案的灵感来自 Marcelo 的解决方案,但进行了一些更改:

#include <iostream>
#include <iterator>
#include <type_traits>
#include <vector>
#include <algorithm>

// This works similar to ostream_iterator, but doesn't print a delimiter after the final item
template<typename T, typename TChar = char, typename TCharTraits = std::char_traits<TChar> >
class pretty_ostream_iterator : public std::iterator<std::output_iterator_tag, void, void, void, void>
{
public:
    typedef TChar char_type;
    typedef TCharTraits traits_type;
    typedef std::basic_ostream<TChar, TCharTraits> ostream_type;

    pretty_ostream_iterator(ostream_type &stream, const char_type *delim = NULL)
        : _stream(&stream), _delim(delim), _insertDelim(false)
    {
    }

    pretty_ostream_iterator<T, TChar, TCharTraits>& operator=(const T &value)
    {
        if( _delim != NULL )
        {
            // Don't insert a delimiter if this is the first time the function is called
            if( _insertDelim )
                (*_stream) << _delim;
            else
                _insertDelim = true;
        }
        (*_stream) << value;
        return *this;
    }

    pretty_ostream_iterator<T, TChar, TCharTraits>& operator*()
    {
        return *this;
    }

    pretty_ostream_iterator<T, TChar, TCharTraits>& operator++()
    {
        return *this;
    }

    pretty_ostream_iterator<T, TChar, TCharTraits>& operator++(int)
    {
        return *this;
    }
private:
    ostream_type *_stream;
    const char_type *_delim;
    bool _insertDelim;
};

#if _MSC_VER >= 1400

// Declare pretty_ostream_iterator as checked
template<typename T, typename TChar, typename TCharTraits>
struct std::_Is_checked_helper<pretty_ostream_iterator<T, TChar, TCharTraits> > : public std::tr1::true_type
{
};

#endif // _MSC_VER >= 1400

namespace std
{
    // Pre-declarations of container types so we don't actually have to include the relevant headers if not needed, speeding up compilation time.
    // These aren't necessary if you do actually include the headers.
    template<typename T, typename TAllocator> class vector;
    template<typename T, typename TAllocator> class list;
    template<typename T, typename TTraits, typename TAllocator> class set;
    template<typename TKey, typename TValue, typename TTraits, typename TAllocator> class map;
}

// Basic is_container template; specialize to derive from std::true_type for all desired container types
template<typename T> struct is_container : public std::false_type { };

// Mark vector as a container
template<typename T, typename TAllocator> struct is_container<std::vector<T, TAllocator> > : public std::true_type { };

// Mark list as a container
template<typename T, typename TAllocator> struct is_container<std::list<T, TAllocator> > : public std::true_type { };

// Mark set as a container
template<typename T, typename TTraits, typename TAllocator> struct is_container<std::set<T, TTraits, TAllocator> > : public std::true_type { };

// Mark map as a container
template<typename TKey, typename TValue, typename TTraits, typename TAllocator> struct is_container<std::map<TKey, TValue, TTraits, TAllocator> > : public std::true_type { };

// Holds the delimiter values for a specific character type
template<typename TChar>
struct delimiters_values
{
    typedef TChar char_type;
    const TChar *prefix;
    const TChar *delimiter;
    const TChar *postfix;
};

// Defines the delimiter values for a specific container and character type
template<typename T, typename TChar>
struct delimiters
{
    static const delimiters_values<TChar> values; 
};

// Default delimiters
template<typename T> struct delimiters<T, char> { static const delimiters_values<char> values; };
template<typename T> const delimiters_values<char> delimiters<T, char>::values = { "{ ", ", ", " }" };
template<typename T> struct delimiters<T, wchar_t> { static const delimiters_values<wchar_t> values; };
template<typename T> const delimiters_values<wchar_t> delimiters<T, wchar_t>::values = { L"{ ", L", ", L" }" };

// Delimiters for set
template<typename T, typename TTraits, typename TAllocator> struct delimiters<std::set<T, TTraits, TAllocator>, char> { static const delimiters_values<char> values; };
template<typename T, typename TTraits, typename TAllocator> const delimiters_values<char> delimiters<std::set<T, TTraits, TAllocator>, char>::values = { "[ ", ", ", " ]" };
template<typename T, typename TTraits, typename TAllocator> struct delimiters<std::set<T, TTraits, TAllocator>, wchar_t> { static const delimiters_values<wchar_t> values; };
template<typename T, typename TTraits, typename TAllocator> const delimiters_values<wchar_t> delimiters<std::set<T, TTraits, TAllocator>, wchar_t>::values = { L"[ ", L", ", L" ]" };

// Delimiters for pair
template<typename T1, typename T2> struct delimiters<std::pair<T1, T2>, char> { static const delimiters_values<char> values; };
template<typename T1, typename T2> const delimiters_values<char> delimiters<std::pair<T1, T2>, char>::values = { "(", ", ", ")" };
template<typename T1, typename T2> struct delimiters<std::pair<T1, T2>, wchar_t> { static const delimiters_values<wchar_t> values; };
template<typename T1, typename T2> const delimiters_values<wchar_t> delimiters<std::pair<T1, T2>, wchar_t>::values = { L"(", L", ", L")" };

// Functor to print containers. You can use this directly if you want to specificy a non-default delimiters type.
template<typename T, typename TChar = char, typename TCharTraits = std::char_traits<TChar>, typename TDelimiters = delimiters<T, TChar> >
struct print_container_helper
{
    typedef TChar char_type;
    typedef TDelimiters delimiters_type;
    typedef std::basic_ostream<TChar, TCharTraits>& ostream_type;

    print_container_helper(const T &container)
        : _container(&container)
    {
    }

    void operator()(ostream_type &stream) const
    {
        if( delimiters_type::values.prefix != NULL )
            stream << delimiters_type::values.prefix;
        std::copy(_container->begin(), _container->end(), pretty_ostream_iterator<typename T::value_type, TChar, TCharTraits>(stream, delimiters_type::values.delimiter));
        if( delimiters_type::values.postfix != NULL )
            stream << delimiters_type::values.postfix;
    }
private:
    const T *_container;
};

// Prints a print_container_helper to the specified stream.
template<typename T, typename TChar, typename TCharTraits, typename TDelimiters>
std::basic_ostream<TChar, TCharTraits>& operator<<(std::basic_ostream<TChar, TCharTraits> &stream, const print_container_helper<T, TChar, TDelimiters> &helper)
{
    helper(stream);
    return stream;
}

// Prints a container to the stream using default delimiters
template<typename T, typename TChar, typename TCharTraits>
typename std::enable_if<is_container<T>::value, std::basic_ostream<TChar, TCharTraits>&>::type
    operator<<(std::basic_ostream<TChar, TCharTraits> &stream, const T &container)
{
    stream << print_container_helper<T, TChar, TCharTraits>(container);
    return stream;
}

// Prints a pair to the stream using delimiters from delimiters<std::pair<T1, T2>>.
template<typename T1, typename T2, typename TChar, typename TCharTraits>
std::basic_ostream<TChar, TCharTraits>& operator<<(std::basic_ostream<TChar, TCharTraits> &stream, const std::pair<T1, T2> &value)
{
    if( delimiters<std::pair<T1, T2>, TChar>::values.prefix != NULL )
        stream << delimiters<std::pair<T1, T2>, TChar>::values.prefix;

    stream << value.first;

    if( delimiters<std::pair<T1, T2>, TChar>::values.delimiter != NULL )
        stream << delimiters<std::pair<T1, T2>, TChar>::values.delimiter;

    stream << value.second;

    if( delimiters<std::pair<T1, T2>, TChar>::values.postfix != NULL )
        stream << delimiters<std::pair<T1, T2>, TChar>::values.postfix;
    return stream;    
}

// Used by the sample below to generate some values
struct fibonacci
{
    fibonacci() : f1(0), f2(1) { }
    int operator()()
    {
        int r = f1 + f2;
        f1 = f2;
        f2 = r;
        return f1;
    }
private:
    int f1;
    int f2;
};

int main()
{
    std::vector<int> v;
    std::generate_n(std::back_inserter(v), 10, fibonacci());

    std::cout << v << std::endl;

    // Example of using pretty_ostream_iterator directly
    std::generate_n(pretty_ostream_iterator<int>(std::cout, ";"), 20, fibonacci());
    std::cout << std::endl;
}

与 Marcelo 的版本一样,它使用is_container类型特征,该特征必须专门用于要支持的所有容器。也许可以使用特征来检查 、 、 /,但我不确定我是否建议这样做,因为它可能与符合这些条件但实际上不是容器的东西匹配,例如 .也像 Marcelo 的版本一样,它使用可以专门用于指定要使用的分隔符的模板。value_typeconst_iteratorbegin()end()std::basic_string

主要区别在于我围绕 构建了我的版本,它的工作方式类似于 ,但不会在最后一项之后打印分隔符。容器的格式设置由 完成,它可以直接用于打印没有is_container特征的容器,或指定不同的分隔符类型。pretty_ostream_iteratorstd::ostream_iteratorprint_container_helper

我还定义了is_container和分隔符,因此它适用于具有非标准谓词或分配器的容器,以及 char 和 wchar_t。运算符<<函数本身也被定义为同时处理 char 和 wchar_t 流。

最后,我使用了 ,它作为 C++0x 的一部分提供,适用于 Visual C++ 2010 和 g++ 4.3(需要 -std=c++0x 标志)及更高版本。这样就不依赖于 Boost。std::enable_if

评论

0赞 Dennis Zickefoose 6/6/2011
如果我没看错的话,为了让一对像在一个函数和另一个函数中一样打印,你必须定义一个全新的类型,有几个静态成员,以便将该类型传递给?这似乎过于复杂。为什么不使用实际对象,您可以根据具体情况设置字段,并且专业化只是提供不同的默认值?<i, j>[i j]print_container_helper
0赞 Kerrek SB 6/6/2011
这样看:如果有一堆你个人喜欢的分隔符,你可以一劳永逸地使用静态成员创建几个类,然后只使用它们。当然,你是对的,使用并不像 .当然,您可以随时更改源代码,或者只是为您喜欢的容器添加显式专用化,例如 for 和 for .归根结底,这是一个权衡功能与便利性的问题。欢迎提出改进建议!print_container_helperoperator<<pair<int, int>pair<double, string>
0赞 Kerrek SB 6/6/2011
...为了跟进这一点,如果你已经需要以不同的格式对相同的数据类型进行情境打印,你可能必须至少编写一个小包装器。这不是一个高度可配置的格式库,而是一个零努力的合理默认库,它神奇地让你不假思索地打印容器......(但是,如果您想要更大的全局灵活性,我们可能会添加一些 #macros,以使默认值易于操作。
0赞 Sven 6/6/2011
真正的问题是,尽管我可以轻松地修改print_container_helper以使用自定义分隔符的参数,但除了专门使用分隔符模板之外,实际上没有任何方法可以为内部容器(或对)指定分隔符。实现这一目标将非常复杂。
0赞 Kerrek SB 6/6/2011
我几乎设法使用类型擦除实现方便的自定义分隔符解决方案。如果你已经有一个分隔符类,那么我可以说.我现在能做的是说,因为你不能有模板参数。使分隔符编译时间常量的决定对那里的易用性施加了一些限制,但我认为这是非常值得的。MyDelsstd::cout << CustomPrinter<MyDels>(x);std::cout << CustomDelims<"{", ":", "}">(x);const char *
1赞 Leonid Volnitsky #4

我的解决方案是simple.h,它是scc包的一部分。所有标准容器、映射、集合、c 阵列都是可打印的。

评论

0赞 Kerrek SB 6/19/2011
有趣。我喜欢容器的模板方法,但它是否适用于自定义容器和具有非标准谓词或分配器的 STL 容器?(我做了类似的事情,尝试使用可变参数模板在 C++0x 中实现双映射。此外,您似乎没有在打印例程中通用迭代器;为什么要显式使用计数器?i
0赞 Leonid Volnitsky 6/26/2011
什么是具有非标准谓词的容器?将打印与签名匹配的自定义容器。目前不支持非标准分配器,但很容易修复。我只是现在不需要这个。
0赞 Leonid Volnitsky 6/26/2011
没有充分的理由使用索引而不是迭代器。历史原因。当我有时间时会修复它。
0赞 Kerrek SB 6/27/2011
我所说的“具有非标准谓词的容器”是指带有自定义比较器或具有自定义相等的unordered_map。支持这些建设非常重要。std::set
8赞 MSalters 5/25/2012 #5

问题可能出在上一个循环中:

(x = 17; isalpha(firstsquare); x++)

此循环将根本不运行(如果不是字母顺序)或将永远运行(如果它是字母顺序)。原因是它不会随着递增而改变。firstsquarefirstsquarex

3赞 miguelbernadi 5/25/2012 #6

我看到两个问题。如中所述

for (x = 17; isalpha(firstsquare); x++)

要么有一个无限循环,要么根本没有执行过,如果入口字符与 Nothing 不同,则 Nothing 在推送到路径向量,使其为空,从而在屏幕上不打印任何内容。您可以测试后者检查或打印。if (entrance == 'S')'S'path.empty()path.size()

无论哪种方式,使用字符串而不是向量不是更好吗?您也可以像数组一样访问字符串内容,查找字符,提取子字符串并轻松打印字符串(无需循环)。

用字符串做这一切可能是让它以一种不那么复杂的方式编写它的方法,并且更容易发现问题。

528赞 Zorawar 5/26/2012 #7

如果你有 C++ 11 编译器,我建议使用基于范围的 for 循环(见下文);或者使用迭代器。但是您有几种选择,我将在下面解释所有这些选择。

基于范围的 for 循环 (C++11)

在 C++11(及更高版本)中,您可以使用新的基于范围的 for 循环,如下所示:

std::vector<char> path;
// ...
for (char i: path)
    std::cout << i << ' ';

for-loop 语句中的类型应为向量元素的类型,而不是整数索引类型。换言之,由于 是 类型,因此在基于范围的 for 循环中应该出现的类型是 。但是,您可能经常看到显式类型替换为占位符类型:charpathpathstd::vector<char>charauto

for (auto i: path)
    std::cout << i << ' ';

无论使用显式类型还是关键字,对象都具有一个值,该值是对象中实际项的副本。因此,对循环中的所有更改本身都不会保留:autoipathipath

std::vector<char> path{'a', 'b', 'c'};

for (auto i: path) {
    i = '_'; // 'i' is a copy of the element in 'path', so although
             // we can change 'i' here perfectly fine, the elements
             // of 'path' have not changed
    std::cout << i << ' '; // will print: "_ _ _"
}

for (auto i: path) {
    std::cout << i << ' '; // will print: "a b c"
}

如果您也想禁止在 for 循环中更改此复制值,您可以强制 of 的类型如下所示:iiconst char

for (const auto i: path) {
    i = '_'; // this will now produce a compiler error
    std::cout << i << ' ';
}

如果要修改 中的项,以便这些更改保留在 for 循环之外,那么可以使用如下所示的引用:pathpath

for (auto& i: path) {
    i = '_'; // changes to 'i' will now also change the
             // element in 'path' itself to that value
    std::cout << i << ' ';
}

即使你不想修改,如果对象的复制成本很高,你也应该使用 const 引用而不是按值复制:path

for (const auto& i: path)
    std::cout << i << ' ';

迭代器

在 C++11 之前,规范的解决方案是使用迭代器,这仍然是完全可以接受的。它们的用法如下:

std::vector<char> path;
// ...
for (std::vector<char>::const_iterator i = path.begin(); i != path.end(); ++i)
    std::cout << *i << ' ';

如果要在 for 循环中修改向量的内容,请使用 而不是 。iteratorconst_iterator

补充:typedef / 类型别名 (C++11) / 自动 (C++11)

这不是另一种解决方案,而是对上述解决方案的补充。如果您使用的是 C++11 标准(或更高版本),则可以使用关键字来帮助提高可读性:iteratorauto

for (auto i = path.begin(); i != path.end(); ++i)
    std::cout << *i << ' ';

这里的类型将是非常量的(即,编译器将用作 的类型)。这是因为我们调用了该方法,因此编译器从中推断出 的类型。如果我们改用该方法(“c”表示 const),那么将是一个:istd::vector<char>::iteratoribeginicbeginistd::vector<char>::const_iterator

for (auto i = path.cbegin(); i != path.cend(); ++i) {
    *i = '_'; // will produce a compiler error
    std::cout << *i << ' ';
}

如果你对编译器推导类型不满意,那么在 C++11 中,你可以使用类型别名来避免一直键入向量(这是一个养成的好习惯):

using Path = std::vector<char>; // C++11 onwards only
Path path; // 'Path' is an alias for std::vector<char>
// ...
for (Path::const_iterator i = path.begin(); i != path.end(); ++i)
    std::cout << *i << ' ';

如果您无法访问 C++ 编译器(或者出于某种原因不喜欢类型别名语法),那么您可以使用更传统的:typedef

typedef std::vector<char> Path; // 'Path' now a synonym for std::vector<char>
Path path;
// ...
for (Path::const_iterator i = path.begin(); i != path.end(); ++i)
    std::cout << *i << ' ';

旁注:

在这一点上,你以前可能遇到过迭代器,也可能没有遇到过迭代器,你可能听说过,也可能没有听说过迭代器是你“应该”使用的,并且可能想知道为什么。答案并不容易理解,但简而言之,这个想法是迭代器是一种抽象,可以保护您免受操作细节的影响。

有一个对象(迭代器)来执行您想要的操作(例如顺序访问)而不是您自己编写详细信息(“详细信息”是实际访问向量元素的代码)是很方便的。你应该注意到,在for循环中,你只要求迭代器返回一个值(迭代器在哪里)——你永远不会直接与迭代器本身交互。逻辑是这样的:你创建一个迭代器,给它一个你想循环的对象(),然后你要做的就是让迭代器为你获取下一个值();你永远不必担心迭代器是如何做到这一点的——这是它的事,而不是你的事。*iipathiterator i = path.begin()*i

好吧,但有什么意义呢?好吧,想象一下,如果获得一个值并不简单。如果它涉及一些工作怎么办?你不需要担心,因为迭代器已经为你处理了这个问题——它会整理细节,你需要做的就是向它询问一个值。此外,如果您将容器从其他容器更改为其他容器怎么办?从理论上讲,即使访问新容器中的元素的细节发生了变化,你的代码也不会改变:请记住,迭代器会在幕后为你整理所有细节,所以你根本不需要改变你的代码——你只需要求迭代器提供容器中的下一个值,就像以前一样。std::vector

因此,虽然这对于遍历向量来说似乎是令人困惑的矫枉过正,但迭代器的概念背后有充分的理由,所以你不妨习惯使用它们。

索引

您还可以使用整数类型显式地索引 for 循环中向量的元素:

for (int i=0; i<path.size(); ++i)
    std::cout << path[i] << ' ';

如果要执行此操作,最好使用容器的成员类型(如果它们可用且合适)。 具有为此作业调用的成员类型:它是方法返回的类型。std::vectorsize_typesize

typedef std::vector<char> Path; // 'Path' now a synonym for std::vector<char>
for (Path::size_type i=0; i<path.size(); ++i)
    std::cout << path[i] << ' ';

为什么不优先使用它而不是解决方案呢?对于简单的情况,您可以这样做,但使用 an 会带来几个优点,我在上面简要概述了这些优点。因此,我的建议是避免使用这种方法,除非你有充分的理由。iteratoriterator

std::copy (C++ 11)

请看约书亚的回答。您可以使用 STL 算法将矢量内容复制到输出流中。我没有什么要补充的,只是说我不使用这种方法;但除了习惯之外,没有充分的理由。std::copy

std::ranges::copy (C++20)

为了完整起见,C++ 引入了范围,它可以作用于 a 的整个范围,因此不需要 和 :std::vectorbeginend

#include <iterator> // for std::ostream_iterator
#include <algorithm> // for std::ranges::copy depending on lib support

std::vector<char> path;
// ...
std::ranges::copy(path, std::ostream_iterator<char>(std::cout, " "));

除非您有一个最新的编译器(在 GCC 上显然至少是 10.1 版),否则即使您可能有一些可用的 C++20 功能,您也可能没有范围支持。

重载 std::ostream::operator<<

另请参阅下面的 Chris 的回答。这更像是对其他答案的补充,因为您仍然需要在重载中实现上述解决方案之一,但好处是代码更简洁。以下是使用上述解决方案的方法:std::ranges::copy

#include <iostream>
#include <vector>
#include <iterator> // for std::ostream_iterator
#include <algorithm> // for std::ranges::copy depending on lib support

using Path = std::vector<char>; // type alias for std::vector<char>

std::ostream& operator<< (std::ostream& out, const Path& v) {
    if ( !v.empty() ) {
        out << '[';
        std::ranges::copy(v, std::ostream_iterator<char>(out, ", "));
        out << "\b\b]"; // use two ANSI backspace characters '\b' to overwrite final ", "
    }
    return out;
}

int main() {
    Path path{'/', 'f', 'o', 'o'};

    // will output: "path: [/, f, o, o]"
    std::cout << "path: " << path << std::endl;

    return 0;
}

现在,您可以像基本类型一样将对象传递到输出流。使用上述任何其他解决方案也应该同样简单。Path

结论

此处介绍的任何解决方案都将起作用。这取决于你(以及上下文或你的编码标准)哪一个是“最好的”。任何比这更详细的东西可能最好留给另一个问题,在那里可以正确评估利弊,但一如既往,用户偏好将始终发挥作用:提出的解决方案都不是客观错误的,但有些解决方案对每个编码人员来说看起来都更好。

补遗

这是我之前发布的解决方案的扩展解决方案。由于那篇帖子一直受到关注,我决定扩展它并参考这里发布的其他优秀解决方案,至少是我个人过去至少使用过一次的解决方案。但是,我鼓励读者查看下面的答案,因为可能有一些很好的建议我已经忘记或不知道了。

评论

0赞 Ed S. 7/5/2012
如果从直通循环,并且向量未在循环中修改,则无需使用并产生额外的边界检查开销。也就是说,我会按照你的建议使用迭代器。0vector::size()at()
1赞 Zorawar 7/25/2012
@Ed:是的,如果循环中没有任何东西修改向量,那么使用是没有意义的,但我想我会提到它,以防万一向量在循环中被修改(不推荐这样做),因为它从未被提及,至少知道它可能很有用。at
0赞 underscore_d 7/15/2015
基于范围的 for 循环可以重写为使用引用,这在大型子对象的情况下可能很重要,如下所示:for (auto const &i: path) std::cout << i << ' ';
0赞 Zorawar 7/19/2015
@underscore_d:谢谢。我已经清理了该部分,我希望它现在更完整,更清晰。
0赞 M.M 2/2/2017
“过载运算符<<”不是一个好的解决方案;由于参数相关的查找,重载运算符的至少一个操作数应该是程序定义的类
254赞 Joshua Kravitz 7/5/2012 #8

一个更简单的方法是使用标准复制算法

#include <iostream>
#include <algorithm> // for copy
#include <iterator> // for ostream_iterator
#include <vector>

int main() {
    /* Set up vector to hold chars a-z */
    std::vector<char> path;
    for (int ch = 'a'; ch <= 'z'; ++ch)
        path.push_back(ch);

    /* Print path vector to console */
    std::copy(path.begin(), path.end(), std::ostream_iterator<char>(std::cout, " "));

    return 0;
}

ostream_iterator就是所谓的迭代器适配器。它被模板化到要打印到流的类型上(在本例中为 )。 (又名控制台输出)是我们要写入的流,空格字符 () 是我们希望在存储在向量中的每个元素之间打印的内容。charcout" "

这个标准算法很强大,许多其他算法也是如此。标准库为您提供的功能和灵活性使它如此出色。试想一下:您只需行代码即可将矢量打印到控制台。您不必处理带有分隔符的特殊情况。您无需担心 for 循环。标准库为您完成所有工作。

评论

3赞 mtk 10/24/2015
如果我的向量是 .如何使用上述方法打印此矢量?vector<pair<int, struct node>>
7赞 Quigi 12/28/2015
分隔符字符串写在每个元素之后,而不是在元素之间,即也写在最后一个元素之后。如果您只想将其介于两者之间,则可能需要处理特殊情况,即作为分隔符。
3赞 ShoeLace 6/30/2017
@mtk,您可以为特定对声明一个函数<>。operator<<
0赞 dfrib 7/30/2017
添加了一个答案,显示了类似的方法,但考虑到上面关于额外尾随分隔符的 @Quigi:s 注释。
0赞 thegreatcoder 2/24/2019
@ShoeLace 难道就没有别的办法了吗?
5赞 CashCow #9

我将在这里添加另一个答案,因为我提出了一种与上一个不同的方法,那就是使用语言环境方面。

基础知识在这里

从本质上讲,你要做的是:

  1. 创建一个派生自 的类。轻微的缺点是您需要在某个地方使用编译单元来保存其 ID。我们称之为 MyPrettyVectorPrinter。你可能会给它一个更好的名字,并为配对和地图创建一个。std::locale::facet
  2. 在流函数中,您可以检查std::has_facet< MyPrettyVectorPrinter >
  3. 如果返回 true,则使用std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
  4. 小面对象将具有分隔符的值,您可以读取它们。如果未找到分面,则打印函数 () 将提供默认分面。请注意,您可以对读取向量执行相同的操作。operator<<

我喜欢这种方法,因为您可以使用默认打印,同时仍然能够使用自定义覆盖。

缺点是,如果在多个项目中使用,则需要为分面提供一个库(因此不能只是标题),并且您需要注意创建新区域设置对象的费用。

我把它写成一个新的解决方案,而不是修改我的另一个解决方案,因为我相信这两种方法都是正确的,你可以选择。

评论

0赞 Kerrek SB 2/15/2013
让我直截了当地说:使用这种方法,我是否需要主动将要使用的每种容器类型列入白名单?
0赞 CashCow 2/19/2013
好吧,除了自己的类型之外,人们不应该扩展 std,但是您为每个容器类型(vector、map、list、deque)编写一个运算符<<重载以及您希望能够打印的对。当然,有些可能共享一个方面(例如,您可能希望打印列表、矢量和标记都相同)。您提供“默认”打印方法,但允许用户在打印之前创建分面和区域设置并填充。有点像 boost 打印他们date_time的方式。默认情况下,还可以将其 facet 加载到全局区域设置上以以这种方式打印。
79赞 Shoe 12/11/2013 #10

在 C++11 中,您现在可以使用基于范围的 for 循环

for (auto const& c : path)
    std::cout << c << ' ';

评论

0赞 Brian 12/22/2014
只有当向量的大小在范围 for 循环的主体中没有改变时,这才有效。
10赞 Shoe 12/22/2014
@BrianP。是的。打印容器的元素不会修改容器的范围。
0赞 kleinfreund 5/17/2015
这里更可取的是什么 - c 作为值复制或作为常量引用以避免复制元素?
0赞 Shoe 5/17/2015
@kleinfreund 这取决于向量的内容。例如,对于 s 的向量,通过常量引用传递实际上可能比通过值传递的成本更高。但在这里,我们谈论的是超微优化。char
53赞 Chris Redford 5/1/2014 #11

我认为最好的方法是通过将此函数添加到您的程序中来重载:operator<<

#include <vector>
using std::vector;
#include <iostream>
using std::ostream;

template<typename T>
ostream& operator<< (ostream& out, const vector<T>& v) {
    out << "{";
    size_t last = v.size() - 1;
    for(size_t i = 0; i < v.size(); ++i) {
        out << v[i];
        if (i != last) 
            out << ", ";
    }
    out << "}";
    return out;
}

然后,您可以在任何可能的向量上使用运算符,假设其元素也定义了:<<ostream& operator<<

vector<string>  s = {"first", "second", "third"};
vector<bool>    b = {true, false, true, false, false};
vector<int>     i = {1, 2, 3, 4};
cout << s << endl;
cout << b << endl;
cout << i << endl;

输出:

{first, second, third}
{1, 0, 1, 0, 0}
{1, 2, 3, 4}

评论

3赞 JDiMatteo 10/9/2014
将 v.size() - 1 存储为 int 可能会降低精度。我在接受同行评审的编辑 (stackoverflow.com/revisions/23397700/5) 中修复了这个问题,但此后再次编辑以恢复可能的精度损失。我想这在实践中并不重要,因为向量通常没有那么大。
0赞 Chris Redford 10/10/2014
不将其存储为变量会降低代码的可读性,这是我不同意的编辑的一部分。我已将 的类型更改为 .lastsize_t
0赞 Vladimir Gamalyan 7/30/2016
size_t last = v.size() - 1;看起来多余,你可以在链接之前使用条件if (i) out << ", ";out << v[i];
4赞 M.M 2/2/2017
ADL 找不到此运算符,因为它不在其任何参数的命名空间中。因此,它将被任何其他命名空间的 .operator<<
0赞 WhozCraig 11/9/2019
如果你要这样做,为什么要在循环每次都进行测试?相反,如果容器不为空,则 (a) 发送第一个元素,然后 (b) 循环发送其余元素,首先打印分隔符(作为前缀)。不需要内部回路测试(除了回路条件本身)。只需要一个环外测试。if (i != last)
22赞 Maxim Chetrusca 10/14/2014 #12

for_each + lambda 表达式怎么样:

#include <vector>
#include <algorithm>
// ...
std::vector<char> vec;
// ...
std::for_each(
              vec.cbegin(),
              vec.cend(),
              [] (const char c) {std::cout << c << " ";} 
              );
// ...

当然,对于这项具体任务,基于范围的解决方案是最优雅的解决方案,但这个解决方案也提供了许多其他可能性。

解释

该算法采用一个输入范围和一个可调用对象,在该范围的每个元素上调用此对象输入范围由两个迭代器定义。可调用对象可以是函数、指向函数的指针、重载类的对象,或者在本例中是 lambda 表达式。此表达式的参数与 vector 中的元素类型匹配。for_each() operator

这种实现的美妙之处在于您从 lambda 表达式中获得的强大功能 - 您可以将这种方法用于更多的事情,而不仅仅是打印向量。

2赞 Yakk - Adam Nevraumont 11/25/2014 #13

这里的目标是使用 ADL 来自定义我们的打印方式。

传入格式化程序标记,并覆盖标记命名空间中的 4 个函数(before、after、between 和 descend)。这改变了格式化程序在迭代容器时打印“装饰”的方式。

一个默认的格式化程序,用于映射、元组、字符串以及包含的其他所有内容。{(a->b),(c->d)}(a,b,c)"hello"[x,y,z]

它应该“只适用于”第三方可迭代类型(并将它们视为“其他一切”)。

如果您想为第三方可迭代对象自定义装饰,只需创建自己的标签即可。处理地图下降需要一些工作(您需要重载才能返回)。也许有一种更干净的方法可以做到这一点,不确定。pretty_print_descend( your_tagpretty_print::decorator::map_magic_tag<your_tag>

一个小库来检测可迭代性和元组性:

namespace details {
  using std::begin; using std::end;
  template<class T, class=void>
  struct is_iterable_test:std::false_type{};
  template<class T>
  struct is_iterable_test<T,
    decltype((void)(
      (void)(begin(std::declval<T>())==end(std::declval<T>()))
      , ((void)(std::next(begin(std::declval<T>()))))
      , ((void)(*begin(std::declval<T>())))
      , 1
    ))
  >:std::true_type{};
  template<class T>struct is_tupleoid:std::false_type{};
  template<class...Ts>struct is_tupleoid<std::tuple<Ts...>>:std::true_type{};
  template<class...Ts>struct is_tupleoid<std::pair<Ts...>>:std::true_type{};
  // template<class T, size_t N>struct is_tupleoid<std::array<T,N>>:std::true_type{}; // complete, but problematic
}
template<class T>struct is_iterable:details::is_iterable_test<std::decay_t<T>>{};
template<class T, std::size_t N>struct is_iterable<T(&)[N]>:std::true_type{}; // bypass decay
template<class T>struct is_tupleoid:details::is_tupleoid<std::decay_t<T>>{};

template<class T>struct is_visitable:std::integral_constant<bool, is_iterable<T>{}||is_tupleoid<T>{}> {};

一个库,允许我们访问可迭代或元组类型对象的内容:

template<class C, class F>
std::enable_if_t<is_iterable<C>{}> visit_first(C&& c, F&& f) {
  using std::begin; using std::end;
  auto&& b = begin(c);
  auto&& e = end(c);
  if (b==e)
      return;
  std::forward<F>(f)(*b);
}
template<class C, class F>
std::enable_if_t<is_iterable<C>{}> visit_all_but_first(C&& c, F&& f) {
  using std::begin; using std::end;
  auto it = begin(c);
  auto&& e = end(c);
  if (it==e)
      return;
  it = std::next(it);
  for( ; it!=e; it = std::next(it) ) {
    f(*it);
  }
}

namespace details {
  template<class Tup, class F>
  void visit_first( std::index_sequence<>, Tup&&, F&& ) {}
  template<size_t... Is, class Tup, class F>
  void visit_first( std::index_sequence<0,Is...>, Tup&& tup, F&& f ) {
    std::forward<F>(f)( std::get<0>( std::forward<Tup>(tup) ) );
  }
  template<class Tup, class F>
  void visit_all_but_first( std::index_sequence<>, Tup&&, F&& ) {}
  template<size_t... Is,class Tup, class F>
  void visit_all_but_first( std::index_sequence<0,Is...>, Tup&& tup, F&& f ) {
    int unused[] = {0,((void)(
      f( std::get<Is>(std::forward<Tup>(tup)) )
    ),0)...};
    (void)(unused);
  }
}
template<class Tup, class F>
std::enable_if_t<is_tupleoid<Tup>{}> visit_first(Tup&& tup, F&& f) {
  details::visit_first( std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f) );
}
template<class Tup, class F>
std::enable_if_t<is_tupleoid<Tup>{}> visit_all_but_first(Tup&& tup, F&& f) {
  details::visit_all_but_first( std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f) );
}

一个漂亮的打印库:

namespace pretty_print {
  namespace decorator {
    struct default_tag {};
    template<class Old>
    struct map_magic_tag:Old {}; // magic for maps

    // Maps get {}s. Write trait `is_associative` to generalize:
    template<class CharT, class Traits, class...Xs >
    void pretty_print_before( default_tag, std::basic_ostream<CharT, Traits>& s, std::map<Xs...> const& ) {
      s << CharT('{');
    }

    template<class CharT, class Traits, class...Xs >
    void pretty_print_after( default_tag, std::basic_ostream<CharT, Traits>& s, std::map<Xs...> const& ) {
      s << CharT('}');
    }

    // tuples and pairs get ():
    template<class CharT, class Traits, class Tup >
    std::enable_if_t<is_tupleoid<Tup>{}> pretty_print_before( default_tag, std::basic_ostream<CharT, Traits>& s, Tup const& ) {
      s << CharT('(');
    }

    template<class CharT, class Traits, class Tup >
    std::enable_if_t<is_tupleoid<Tup>{}> pretty_print_after( default_tag, std::basic_ostream<CharT, Traits>& s, Tup const& ) {
      s << CharT(')');
    }

    // strings with the same character type get ""s:
    template<class CharT, class Traits, class...Xs >
    void pretty_print_before( default_tag, std::basic_ostream<CharT, Traits>& s, std::basic_string<CharT, Xs...> const& ) {
      s << CharT('"');
    }
    template<class CharT, class Traits, class...Xs >
    void pretty_print_after( default_tag, std::basic_ostream<CharT, Traits>& s, std::basic_string<CharT, Xs...> const& ) {
      s << CharT('"');
    }
    // and pack the characters together:
    template<class CharT, class Traits, class...Xs >
    void pretty_print_between( default_tag, std::basic_ostream<CharT, Traits>&, std::basic_string<CharT, Xs...> const& ) {}

    // map magic. When iterating over the contents of a map, use the map_magic_tag:
    template<class...Xs>
    map_magic_tag<default_tag> pretty_print_descend( default_tag, std::map<Xs...> const& ) {
      return {};
    }
    template<class old_tag, class C>
    old_tag pretty_print_descend( map_magic_tag<old_tag>, C const& ) {
      return {};
    }

    // When printing a pair immediately within a map, use -> as a separator:
    template<class old_tag, class CharT, class Traits, class...Xs >
    void pretty_print_between( map_magic_tag<old_tag>, std::basic_ostream<CharT, Traits>& s, std::pair<Xs...> const& ) {
      s << CharT('-') << CharT('>');
    }
  }

  // default behavior:
  template<class CharT, class Traits, class Tag, class Container >
  void pretty_print_before( Tag const&, std::basic_ostream<CharT, Traits>& s, Container const& ) {
    s << CharT('[');
  }
  template<class CharT, class Traits, class Tag, class Container >
  void pretty_print_after( Tag const&, std::basic_ostream<CharT, Traits>& s, Container const& ) {
    s << CharT(']');
  }
  template<class CharT, class Traits, class Tag, class Container >
  void pretty_print_between( Tag const&, std::basic_ostream<CharT, Traits>& s, Container const& ) {
    s << CharT(',');
  }
  template<class Tag, class Container>
  Tag&& pretty_print_descend( Tag&& tag, Container const& ) {
    return std::forward<Tag>(tag);
  }

  // print things by default by using <<:
  template<class Tag=decorator::default_tag, class Scalar, class CharT, class Traits>
  std::enable_if_t<!is_visitable<Scalar>{}> print( std::basic_ostream<CharT, Traits>& os, Scalar&& scalar, Tag&&=Tag{} ) {
    os << std::forward<Scalar>(scalar);
  }
  // for anything visitable (see above), use the pretty print algorithm:
  template<class Tag=decorator::default_tag, class C, class CharT, class Traits>
  std::enable_if_t<is_visitable<C>{}> print( std::basic_ostream<CharT, Traits>& os, C&& c, Tag&& tag=Tag{} ) {
    pretty_print_before( std::forward<Tag>(tag), os, std::forward<C>(c) );
    visit_first( c, [&](auto&& elem) {
      print( os, std::forward<decltype(elem)>(elem), pretty_print_descend( std::forward<Tag>(tag), std::forward<C>(c) ) );
    });
    visit_all_but_first( c, [&](auto&& elem) {
      pretty_print_between( std::forward<Tag>(tag), os, std::forward<C>(c) );
      print( os, std::forward<decltype(elem)>(elem), pretty_print_descend( std::forward<Tag>(tag), std::forward<C>(c) ) );
    });
    pretty_print_after( std::forward<Tag>(tag), os, std::forward<C>(c) );
  }
}

测试代码:

int main() {
  std::vector<int> x = {1,2,3};

  pretty_print::print( std::cout, x );
  std::cout << "\n";

  std::map< std::string, int > m;
  m["hello"] = 3;
  m["world"] = 42;

  pretty_print::print( std::cout, m );
  std::cout << "\n";
}

现场示例

这确实使用了 C++14 功能(一些别名和 lambda),但没有一个是必需的。_tauto&&

评论

0赞 Yakk - Adam Nevraumont 11/25/2014
@KerrekSB工作版本,但有一些更改。在这一点上,大部分代码是通用的“访问元组/可迭代对象”,以及花哨的格式(包括 s of s 中的格式)。漂亮的打印库的核心又好又小,这很好。我试图让它易于扩展,不确定我是否成功了。->pairmap
7赞 stupidgoddamnjerk 3/12/2015 #14

在 C++ 11 中,基于范围的 for 循环可能是一个很好的解决方案:

vector<char> items = {'a','b','c'};
for (char n : items)
    cout << n << ' ';

输出:

a b c 
12赞 g24l 4/7/2015 #15

只需将容器复制到控制台即可。

std::vector<int> v{1,2,3,4};
std::copy(v.begin(),v.end(),std::ostream_iterator<int>(std::cout, " " ));

应输出:

1 2 3 4
13赞 Pixelchemist 9/27/2015 #16

现在,该代码在多个场合被证明是方便的,并且由于使用率非常低,因此我觉得进行自定义的费用非常低。因此,我决定在 MIT 许可下发布它,并提供一个 GitHub 存储库,可以在其中下载标题和一个小示例文件。

http://djmuw.github.io/prettycc

0. 前言和措辞

就此答案而言,“装饰”是一组前缀字符串、分隔符字符串和后缀字符串。 其中前缀字符串在容器值之前插入到流中,后缀字符串在容器值之后插入(请参阅 2。目标容器)。 分隔符字符串插入到相应容器的值之间。

注意:实际上,这个答案并没有 100% 解决这个问题,因为修饰不是严格编译的时间常数,因为需要运行时检查来检查是否已将自定义修饰应用于当前流。尽管如此,我认为它有一些不错的功能。

注意2:可能有一些小错误,因为它还没有经过很好的测试。

1. 总体思路/用法

使用时无需额外代码

它要保持尽可能简单

#include <vector>
#include "pretty.h"

int main()
{
  std::cout << std::vector<int>{1,2,3,4,5}; // prints 1, 2, 3, 4, 5
  return 0;
}

易于定制...

...对于特定流对象

#include <vector>
#include "pretty.h"

int main()
{
  // set decoration for std::vector<int> for cout object
  std::cout << pretty::decoration<std::vector<int>>("(", ",", ")");
  std::cout << std::vector<int>{1,2,3,4,5}; // prints (1,2,3,4,5)
  return 0;
}

或对于所有流:

#include <vector>
#include "pretty.h"

// set decoration for std::vector<int> for all ostream objects
PRETTY_DEFAULT_DECORATION(std::vector<int>, "{", ", ", "}")

int main()
{
  std::cout << std::vector<int>{1,2,3,4,5}; // prints {1, 2, 3, 4, 5}
  std::cout << pretty::decoration<std::vector<int>>("(", ",", ")");
  std::cout << std::vector<int>{1,2,3,4,5}; // prints (1,2,3,4,5)
  return 0;
}

粗略描述

  • 该代码包括一个类模板,该模板为任何类型提供默认修饰
  • 可以专门用于更改 (a) 某些类型的默认装饰,它是
  • 使用 / 提供的私有存储来保存指向特定流上特定类型的对象的指针。ios_basexallocpwordpretty::decor

如果未显式设置此流的对象,则调用该对象以获取给定类型的默认修饰。 该类将专门用于自定义默认装饰。pretty::decor<T>pretty::defaulted<T, charT, chartraitT>::decoration()pretty::defaulted

2. 目标对象/容器

此代码的“漂亮装饰”的目标对象是具有obj

  • 重载和定义(包括 C 样式数组),std::beginstd::end
  • 拥有并通过 ADL 提供,begin(obj)end(obj)
  • 属于类型std::tuple
  • 或类型 。std::pair

该代码包括一个特征,用于标识具有范围特征 (/) 的类。 (不过,不包括检查是否为有效表达式。beginendbegin(obj) == end(obj)

该代码在全局命名空间中提供 s,仅适用于没有更专用版本的可用类。 因此,例如,在此代码中使用运算符打印时,尽管具有有效的 / 对。operator<<operator<<std::stringbeginend

3. 利用和定制

可以为每种类型(不同的 s)和流(不是流类型! (即,对于不同的流对象,可以有不同的装饰。tuplestd::vector<int>

A) 默认装饰

默认前缀为 (nothing),默认后缀为 (nothing),而默认分隔符为 (comma+space)。""", "

B) 通过专业化类模板自定义类型的默认装饰pretty::defaulted

具有一个静态成员函数,该函数返回一个对象,该对象包含给定类型的默认值。struct defaulteddecoration()decor

使用数组的示例:

自定义默认数组打印:

namespace pretty
{
  template<class T, std::size_t N>
  struct defaulted<T[N]>
  {
    static decor<T[N]> decoration()
    {
      return{ { "(" }, { ":" }, { ")" } };
    }
  };
}

打印 arry 数组:

float e[5] = { 3.4f, 4.3f, 5.2f, 1.1f, 22.2f };
std::cout << e << '\n'; // prints (3.4:4.3:5.2:1.1:22.2)

将宏用于流PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...)char

宏扩展为

namespace pretty { 
  template< __VA_ARGS__ >
  struct defaulted< TYPE > {
    static decor< TYPE > decoration() {
      return { PREFIX, DELIM, POSTFIX };
    } 
  }; 
} 

启用上述部分专业化重写为

PRETTY_DEFAULT_DECORATION(T[N], "", ";", "", class T, std::size_t N)

或插入完整的专业化,例如

PRETTY_DEFAULT_DECORATION(std::vector<int>, "(", ", ", ")")

包含流的另一个宏:。wchar_tPRETTY_DEFAULT_WDECORATION

C) 对溪流进行装饰

该函数用于对某个流进行装饰。 有超载采取 - 一个字符串参数作为分隔符(采用默认类中的前缀和后缀) - 或三个字符串参数组合完整的装饰pretty::decoration

给定类型和流的完整装饰

float e[3] = { 3.4f, 4.3f, 5.2f };
std::stringstream u;
// add { ; } decoration to u
u << pretty::decoration<float[3]>("{", "; ", "}");

// use { ; } decoration
u << e << '\n'; // prints {3.4; 4.3; 5.2}

// uses decoration returned by defaulted<float[3]>::decoration()
std::cout << e; // prints 3.4, 4.3, 5.2

自定义给定流的分隔符

PRETTY_DEFAULT_DECORATION(float[3], "{{{", ",", "}}}")

std::stringstream v;
v << e; // prints {{{3.4,4.3,5.2}}}

v << pretty::decoration<float[3]>(":");
v << e; // prints {{{3.4:4.3:5.2}}}

v << pretty::decoration<float[3]>("((", "=", "))");
v << e; // prints ((3.4=4.3=5.2))

4.特殊处理std::tuple

此代码不允许对每个可能的元组类型进行专用化,而是将任何可用的修饰应用于所有类型的元组。std::tuple<void*>std::tuple<...>

5. 从流中删除自定义装饰

要返回到给定类型的默认修饰,请在流上使用函数模板。pretty::clears

s << pretty::clear<std::vector<int>>();

5. 更多例子

使用换行符分隔符打印“矩阵式”

std::vector<std::vector<int>> m{ {1,2,3}, {4,5,6}, {7,8,9} };
std::cout << pretty::decoration<std::vector<std::vector<int>>>("\n");
std::cout << m;

指纹

1, 2, 3
4, 5, 6
7, 8, 9

ideone/KKUebZ 上查看

6. 代码

#ifndef pretty_print_0x57547_sa4884X_0_1_h_guard_
#define pretty_print_0x57547_sa4884X_0_1_h_guard_

#include <string>
#include <iostream>
#include <type_traits>
#include <iterator>
#include <utility>

#define PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...) \
    namespace pretty { template< __VA_ARGS__ >\
    struct defaulted< TYPE > {\
    static decor< TYPE > decoration(){\
      return { PREFIX, DELIM, POSTFIX };\
    } /*decoration*/ }; /*defaulted*/} /*pretty*/

#define PRETTY_DEFAULT_WDECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...) \
    namespace pretty { template< __VA_ARGS__ >\
    struct defaulted< TYPE, wchar_t, std::char_traits<wchar_t> > {\
    static decor< TYPE, wchar_t, std::char_traits<wchar_t> > decoration(){\
      return { PREFIX, DELIM, POSTFIX };\
    } /*decoration*/ }; /*defaulted*/} /*pretty*/

namespace pretty
{

  namespace detail
  {
    // drag in begin and end overloads
    using std::begin;
    using std::end;
    // helper template
    template <int I> using _ol = std::integral_constant<int, I>*;
    // SFINAE check whether T is a range with begin/end
    template<class T>
    class is_range
    {
      // helper function declarations using expression sfinae
      template <class U, _ol<0> = nullptr>
      static std::false_type b(...);
      template <class U, _ol<1> = nullptr>
      static auto b(U &v) -> decltype(begin(v), std::true_type());
      template <class U, _ol<0> = nullptr>
      static std::false_type e(...);
      template <class U, _ol<1> = nullptr>
      static auto e(U &v) -> decltype(end(v), std::true_type());
      // return types
      using b_return = decltype(b<T>(std::declval<T&>()));
      using e_return = decltype(e<T>(std::declval<T&>()));
    public:
      static const bool value = b_return::value && e_return::value;
    };
  }

  // holder class for data
  template<class T, class CharT = char, class TraitT = std::char_traits<CharT>>
  struct decor
  {
    static const int xindex;
    std::basic_string<CharT, TraitT> prefix, delimiter, postfix;
    decor(std::basic_string<CharT, TraitT> const & pre = "",
      std::basic_string<CharT, TraitT> const & delim = "",
      std::basic_string<CharT, TraitT> const & post = "")
      : prefix(pre), delimiter(delim), postfix(post) {}
  };

  template<class T, class charT, class traits>
  int const decor<T, charT, traits>::xindex = std::ios_base::xalloc();

  namespace detail
  {

    template<class T, class CharT, class TraitT>
    void manage_decor(std::ios_base::event evt, std::ios_base &s, int const idx)
    {
      using deco_type = decor<T, CharT, TraitT>;
      if (evt == std::ios_base::erase_event)
      { // erase deco
        void const * const p = s.pword(idx);
        if (p)
        {
          delete static_cast<deco_type const * const>(p);
          s.pword(idx) = nullptr;
        }
      }
      else if (evt == std::ios_base::copyfmt_event)
      { // copy deco
        void const * const p = s.pword(idx);
        if (p)
        {
          auto np = new deco_type{ *static_cast<deco_type const * const>(p) };
          s.pword(idx) = static_cast<void*>(np);
        }
      }
    }

    template<class T> struct clearer {};

    template<class T, class CharT, class TraitT>
    std::basic_ostream<CharT, TraitT>& operator<< (
      std::basic_ostream<CharT, TraitT> &s, clearer<T> const &)
    {
      using deco_type = decor<T, CharT, TraitT>;
      void const * const p = s.pword(deco_type::xindex);
      if (p)
      { // delete if set
        delete static_cast<deco_type const *>(p);
        s.pword(deco_type::xindex) = nullptr;
      }
      return s;
    }

    template <class CharT> 
    struct default_data { static const CharT * decor[3]; };
    template <> 
    const char * default_data<char>::decor[3] = { "", ", ", "" };
    template <> 
    const wchar_t * default_data<wchar_t>::decor[3] = { L"", L", ", L"" };

  }

  // Clear decoration for T
  template<class T>
  detail::clearer<T> clear() { return{}; }
  template<class T, class CharT, class TraitT>
  void clear(std::basic_ostream<CharT, TraitT> &s) { s << detail::clearer<T>{}; }

  // impose decoration on ostream
  template<class T, class CharT, class TraitT>
  std::basic_ostream<CharT, TraitT>& operator<<(
    std::basic_ostream<CharT, TraitT> &s, decor<T, CharT, TraitT> && h)
  {
    using deco_type = decor<T, CharT, TraitT>;
    void const * const p = s.pword(deco_type::xindex);
    // delete if already set
    if (p) delete static_cast<deco_type const *>(p);
    s.pword(deco_type::xindex) = static_cast<void *>(new deco_type{ std::move(h) });
    // check whether we alread have a callback registered
    if (s.iword(deco_type::xindex) == 0)
    { // if this is not the case register callback and set iword
      s.register_callback(detail::manage_decor<T, CharT, TraitT>, deco_type::xindex);
      s.iword(deco_type::xindex) = 1;
    }
    return s;
  }

  template<class T, class CharT = char, class TraitT = std::char_traits<CharT>>
  struct defaulted
  {
    static inline decor<T, CharT, TraitT> decoration()
    {
      return{ detail::default_data<CharT>::decor[0],
        detail::default_data<CharT>::decor[1],
        detail::default_data<CharT>::decor[2] };
    }
  };

  template<class T, class CharT = char, class TraitT = std::char_traits<CharT>>
  decor<T, CharT, TraitT> decoration(
    std::basic_string<CharT, TraitT> const & prefix,
    std::basic_string<CharT, TraitT> const & delimiter,
    std::basic_string<CharT, TraitT> const & postfix)
  {
    return{ prefix, delimiter, postfix };
  }

  template<class T, class CharT = char,
  class TraitT = std::char_traits < CharT >>
    decor<T, CharT, TraitT> decoration(
      std::basic_string<CharT, TraitT> const & delimiter)
  {
    using str_type = std::basic_string<CharT, TraitT>;
    return{ defaulted<T, CharT, TraitT>::decoration().prefix,
      delimiter, defaulted<T, CharT, TraitT>::decoration().postfix };
  }

  template<class T, class CharT = char,
  class TraitT = std::char_traits < CharT >>
    decor<T, CharT, TraitT> decoration(CharT const * const prefix,
      CharT const * const delimiter, CharT const * const postfix)
  {
    using str_type = std::basic_string<CharT, TraitT>;
    return{ str_type{ prefix }, str_type{ delimiter }, str_type{ postfix } };
  }

  template<class T, class CharT = char,
  class TraitT = std::char_traits < CharT >>
    decor<T, CharT, TraitT> decoration(CharT const * const delimiter)
  {
    using str_type = std::basic_string<CharT, TraitT>;
    return{ defaulted<T, CharT, TraitT>::decoration().prefix,
      str_type{ delimiter }, defaulted<T, CharT, TraitT>::decoration().postfix };
  }

  template<typename T, std::size_t N, std::size_t L>
  struct tuple
  {
    template<class CharT, class TraitT>
    static void print(std::basic_ostream<CharT, TraitT>& s, T const & value,
      std::basic_string<CharT, TraitT> const &delimiter)
    {
      s << std::get<N>(value) << delimiter;
      tuple<T, N + 1, L>::print(s, value, delimiter);
    }
  };

  template<typename T, std::size_t N>
  struct tuple<T, N, N>
  {
    template<class CharT, class TraitT>
    static void print(std::basic_ostream<CharT, TraitT>& s, T const & value,
      std::basic_string<CharT, TraitT> const &) {
      s << std::get<N>(value);
    }
  };

}

template<class CharT, class TraitT>
std::basic_ostream<CharT, TraitT> & operator<< (
  std::basic_ostream<CharT, TraitT> &s, std::tuple<> const & v)
{
  using deco_type = pretty::decor<std::tuple<void*>, CharT, TraitT>;
  using defaulted_type = pretty::defaulted<std::tuple<void*>, CharT, TraitT>;
  void const * const p = s.pword(deco_type::xindex);
  auto const d = static_cast<deco_type const * const>(p);
  s << (d ? d->prefix : defaulted_type::decoration().prefix);
  s << (d ? d->postfix : defaulted_type::decoration().postfix);
  return s;
}

template<class CharT, class TraitT, class ... T>
std::basic_ostream<CharT, TraitT> & operator<< (
  std::basic_ostream<CharT, TraitT> &s, std::tuple<T...> const & v)
{
  using deco_type = pretty::decor<std::tuple<void*>, CharT, TraitT>;
  using defaulted_type = pretty::defaulted<std::tuple<void*>, CharT, TraitT>;
  using pretty_tuple = pretty::tuple<std::tuple<T...>, 0U, sizeof...(T)-1U>;
  void const * const p = s.pword(deco_type::xindex);
  auto const d = static_cast<deco_type const * const>(p);
  s << (d ? d->prefix : defaulted_type::decoration().prefix);
  pretty_tuple::print(s, v, d ? d->delimiter : 
    defaulted_type::decoration().delimiter);
  s << (d ? d->postfix : defaulted_type::decoration().postfix);
  return s;
}

template<class T, class U, class CharT, class TraitT>
std::basic_ostream<CharT, TraitT> & operator<< (
  std::basic_ostream<CharT, TraitT> &s, std::pair<T, U> const & v)
{
  using deco_type = pretty::decor<std::pair<T, U>, CharT, TraitT>;
  using defaulted_type = pretty::defaulted<std::pair<T, U>, CharT, TraitT>;
  void const * const p = s.pword(deco_type::xindex);
  auto const d = static_cast<deco_type const * const>(p);
  s << (d ? d->prefix : defaulted_type::decoration().prefix);
  s << v.first;
  s << (d ? d->delimiter : defaulted_type::decoration().delimiter);
  s << v.second;
  s << (d ? d->postfix : defaulted_type::decoration().postfix);
  return s;
}


template<class T, class CharT = char,
class TraitT = std::char_traits < CharT >>
  typename std::enable_if < pretty::detail::is_range<T>::value,
  std::basic_ostream < CharT, TraitT >> ::type & operator<< (
    std::basic_ostream<CharT, TraitT> &s, T const & v)
{
  bool first(true);
  using deco_type = pretty::decor<T, CharT, TraitT>;
  using default_type = pretty::defaulted<T, CharT, TraitT>;
  void const * const p = s.pword(deco_type::xindex);
  auto d = static_cast<pretty::decor<T, CharT, TraitT> const * const>(p);
  s << (d ? d->prefix : default_type::decoration().prefix);
  for (auto const & e : v)
  { // v is range thus range based for works
    if (!first) s << (d ? d->delimiter : default_type::decoration().delimiter);
    s << e;
    first = false;
  }
  s << (d ? d->postfix : default_type::decoration().postfix);
  return s;
}

#endif // pretty_print_0x57547_sa4884X_0_1_h_guard_
5赞 ivanmara 8/12/2016 #17

重载运算符<<:

template<typename OutStream, typename T>
OutStream& operator<< (OutStream& out, const vector<T>& v)
{
    for (auto const& tmp : v)
        out << tmp << " ";
    out << endl;
    return out;
}

用法:

vector <int> test {1,2,3};
wcout << test; // or any output stream
3赞 SketchyManDan 2/8/2017 #18

这个答案是基于 Zorawar 的答案,但我无法在那里发表评论。

您可以通过改用 和 来制作 (C++11)/ 版本autotypedefconstcbegincend

for (auto i = path.cbegin(); i != path.cend(); ++i)
    std::cout << *i << ' ';
10赞 dfrib 7/30/2017 #19

使用但不带额外的尾随分隔符std::copy

使用 std::copy 的替代/修改方法(最初用于@JoshuaKravtiz答案),但在最后一个元素后不包括额外的尾随分隔符:

#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>

template <typename T>
void print_contents(const std::vector<T>& v, const char * const separator = " ")
{
    if(!v.empty())
    {
        std::copy(v.begin(),
                  --v.end(),
                  std::ostream_iterator<T>(std::cout, separator));
        std::cout << v.back() << "\n";
    }
}

// example usage
int main() {
    std::vector<int> v{1, 2, 3, 4};
    print_contents(v);      // '1 2 3 4'
    print_contents(v, ":"); // '1:2:3:4'
    v = {};
    print_contents(v);      // ... no std::cout
    v = {1};
    print_contents(v);      // '1'
    return 0;
}

应用于自定义 POD 类型的容器的示例用法:

// includes and 'print_contents(...)' as above ...

class Foo
{
    int i;
    friend std::ostream& operator<<(std::ostream& out, const Foo& obj);
public:
    Foo(const int i) : i(i) {}
};

std::ostream& operator<<(std::ostream& out, const Foo& obj)
{
    return out << "foo_" << obj.i; 
}

int main() {
    std::vector<Foo> v{1, 2, 3, 4};
    print_contents(v);      // 'foo_1 foo_2 foo_3 foo_4'
    print_contents(v, ":"); // 'foo_1:foo_2:foo_3:foo_4'
    v = {};
    print_contents(v);      // ... no std::cout
    v = {1};
    print_contents(v);      // 'foo_1'
    return 0;
}
1赞 Akash Kandpal 11/24/2017 #20

在 C++ 中 11

for (auto i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';

for(int i=0; i<path.size(); ++i)
std::cout << path[i] << ' ';

评论

0赞 Yashas 11/24/2017
与现有答案相比,此答案没有提供任何其他信息。
79赞 vitaut 3/21/2019 #21

在 C++23 中,您将能够使用 std::p rint 打印大多数标准类型,包括 .例如:std::vector

import std;

int main() {
  auto v = std::vector{1, 2, 3};
  std::print("{}", v);
}

指纹

[1, 2, 3]

自。stdout

同时,您可以使用 {fmt} 库,它基于:std::print

#include <vector>
#include <fmt/ranges.h>

int main() {
  auto v = std::vector<int>{1, 2, 3};
  fmt::print("{}", v);
}

Godbolt:https://godbolt.org/z/xEdz15

我不建议对标准容器进行超载,因为这可能会带来ODR违规。operator<<

免责声明:我是 {fmt} 的作者,并且 .std::formatstd::print

评论

0赞 Karthik Nishanth 12/21/2020
std::map 呢?我在文档中找不到任何内容
2赞 vitaut 12/21/2020
支持所有容器的格式设置。
0赞 Karthik Nishanth 12/22/2020
你能给我一个起点吗?我很难找到 fmtlib 作为搜索词的用法。如果这算作菜鸟问题或类似 RTFM 的:),我深表歉意fmtlib print std::map
0赞 vitaut 12/22/2020
下面是 map 的示例:godbolt.org/z/EG7aoE。如您所见,使用方式没有区别。
3赞 Karthik Nishanth 12/24/2020
天哪!这真是太神奇了 godbolt.org/z/h7qxba
1赞 Jeffrey Faust 6/17/2019 #22

从最早的 BoostCon(现在称为 CppCon)中出来后,我和另外两个人合作开发了一个库来做到这一点。主要症结在于需要扩展。事实证明,对于提升库来说,这是不行的。namespace std

不幸的是,代码的链接不再起作用,但您可能会在讨论中找到一些有趣的花絮(至少那些没有谈论如何命名的花絮!

http://boost.2283326.n4.nabble.com/explore-Library-Proposal-Container-Streaming-td2619544.html

1赞 Skident 5/24/2020 #23

这是我在 2016 年完成的实施版本

所有内容都在一个标题中,因此易于使用 https://github.com/skident/eos/blob/master/include/eos/io/print.hpp

/*! \file       print.hpp
 *  \brief      Useful functions for work with STL containers. 
 *          
 *  Now it supports generic print for STL containers like: [elem1, elem2, elem3]
 *  Supported STL conrainers: vector, deque, list, set multiset, unordered_set,
 *  map, multimap, unordered_map, array
 *
 *  \author     Skident
 *  \date       02.09.2016
 *  \copyright  Skident Inc.
 */

#pragma once

// check is the C++11 or greater available (special hack for MSVC)
#if (defined(_MSC_VER) && __cplusplus >= 199711L) || __cplusplus >= 201103L
    #define MODERN_CPP_AVAILABLE 1
#endif


#include <iostream>
#include <sstream>
#include <vector>
#include <deque>
#include <set>
#include <list>
#include <map>
#include <cctype>

#ifdef MODERN_CPP_AVAILABLE
    #include <array>
    #include <unordered_set>
    #include <unordered_map>
    #include <forward_list>
#endif


#define dump(value) std::cout << (#value) << ": " << (value) << std::endl

#define BUILD_CONTENT                                                       \
        std::stringstream ss;                                               \
        for (; it != collection.end(); ++it)                                \
        {                                                                   \
            ss << *it << elem_separator;                                    \
        }                                                                   \


#define BUILD_MAP_CONTENT                                                   \
        std::stringstream ss;                                               \
        for (; it != collection.end(); ++it)                                \
        {                                                                   \
            ss  << it->first                                                \
                << keyval_separator                                         \
                << it->second                                               \
                << elem_separator;                                          \
        }                                                                   \


#define COMPILE_CONTENT                                                     \
        std::string data = ss.str();                                        \
        if (!data.empty() && !elem_separator.empty())                       \
            data = data.substr(0, data.rfind(elem_separator));              \
        std::string result = first_bracket + data + last_bracket;           \
        os << result;                                                       \
        if (needEndl)                                                       \
            os << std::endl;                                                \



////
///
///
/// Template definitions
///
///

//generic template for classes: deque, list, forward_list, vector
#define VECTOR_AND_CO_TEMPLATE                                          \
    template<                                                           \
        template<class T,                                               \
                 class Alloc = std::allocator<T> >                      \
        class Container, class Type, class Alloc>                       \

#define SET_TEMPLATE                                                    \
    template<                                                           \
        template<class T,                                               \
                 class Compare = std::less<T>,                          \
                 class Alloc = std::allocator<T> >                      \
            class Container, class T, class Compare, class Alloc>       \

#define USET_TEMPLATE                                                   \
    template<                                                           \
template < class Key,                                                   \
           class Hash = std::hash<Key>,                                 \
           class Pred = std::equal_to<Key>,                             \
           class Alloc = std::allocator<Key>                            \
           >                                                            \
    class Container, class Key, class Hash, class Pred, class Alloc     \
    >                                                                   \


#define MAP_TEMPLATE                                                    \
    template<                                                           \
        template<class Key,                                             \
                class T,                                                \
                class Compare = std::less<Key>,                         \
                class Alloc = std::allocator<std::pair<const Key,T> >   \
                >                                                       \
        class Container, class Key,                                     \
        class Value/*, class Compare, class Alloc*/>                    \


#define UMAP_TEMPLATE                                                   \
    template<                                                           \
        template<class Key,                                             \
                   class T,                                             \
                   class Hash = std::hash<Key>,                         \
                   class Pred = std::equal_to<Key>,                     \
                   class Alloc = std::allocator<std::pair<const Key,T> >\
                 >                                                      \
        class Container, class Key, class Value,                        \
        class Hash, class Pred, class Alloc                             \
                >                                                       \


#define ARRAY_TEMPLATE                                                  \
    template<                                                           \
        template<class T, std::size_t N>                                \
        class Array, class Type, std::size_t Size>                      \



namespace eos
{
    static const std::string default_elem_separator     = ", ";
    static const std::string default_keyval_separator   = " => ";
    static const std::string default_first_bracket      = "[";
    static const std::string default_last_bracket       = "]";


    //! Prints template Container<T> as in Python
    //! Supported containers: vector, deque, list, set, unordered_set(C++11), forward_list(C++11)
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    template<class Container>
    void print( const Container& collection
              , const std::string& elem_separator   = default_elem_separator
              , const std::string& first_bracket    = default_first_bracket
              , const std::string& last_bracket     = default_last_bracket
              , std::ostream& os = std::cout
              , bool needEndl = true
            )
    {
        typename Container::const_iterator it = collection.begin();
        BUILD_CONTENT
        COMPILE_CONTENT
    }


    //! Prints collections with one template argument and allocator as in Python.
    //! Supported standard collections: vector, deque, list, forward_list
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param keyval_separator separator between key and value of map. For default it is the '=>'
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    VECTOR_AND_CO_TEMPLATE
    void print( const Container<Type>& collection
              , const std::string& elem_separator   = default_elem_separator
              , const std::string& first_bracket    = default_first_bracket
              , const std::string& last_bracket     = default_last_bracket
              , std::ostream& os = std::cout
              , bool needEndl = true
            )
    {
        typename Container<Type>::const_iterator it = collection.begin();
        BUILD_CONTENT
        COMPILE_CONTENT
    }


    //! Prints collections like std:set<T, Compare, Alloc> as in Python
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param keyval_separator separator between key and value of map. For default it is the '=>'
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    SET_TEMPLATE
    void print( const Container<T, Compare, Alloc>& collection
              , const std::string& elem_separator   = default_elem_separator
              , const std::string& first_bracket    = default_first_bracket
              , const std::string& last_bracket     = default_last_bracket
              , std::ostream& os = std::cout
              , bool needEndl = true
            )
    {
        typename Container<T, Compare, Alloc>::const_iterator it = collection.begin();
        BUILD_CONTENT
        COMPILE_CONTENT
    }


    //! Prints collections like std:unordered_set<Key, Hash, Pred, Alloc> as in Python
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param keyval_separator separator between key and value of map. For default it is the '=>'
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    USET_TEMPLATE
    void print( const Container<Key, Hash, Pred, Alloc>& collection
              , const std::string& elem_separator   = default_elem_separator
              , const std::string& first_bracket    = default_first_bracket
              , const std::string& last_bracket     = default_last_bracket
              , std::ostream& os = std::cout
              , bool needEndl = true
            )
    {
        typename Container<Key, Hash, Pred, Alloc>::const_iterator it = collection.begin();
        BUILD_CONTENT
        COMPILE_CONTENT
    }

    //! Prints collections like std:map<T, U> as in Python
    //! supports generic objects of std: map, multimap
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param keyval_separator separator between key and value of map. For default it is the '=>'
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    MAP_TEMPLATE
    void print(   const Container<Key, Value>& collection
                , const std::string& elem_separator   = default_elem_separator
                , const std::string& keyval_separator = default_keyval_separator
                , const std::string& first_bracket    = default_first_bracket
                , const std::string& last_bracket     = default_last_bracket
                , std::ostream& os = std::cout
                , bool needEndl = true
        )
    {
        typename Container<Key, Value>::const_iterator it = collection.begin();
        BUILD_MAP_CONTENT
        COMPILE_CONTENT
    }

    //! Prints classes like std:unordered_map as in Python
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param keyval_separator separator between key and value of map. For default it is the '=>'
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    UMAP_TEMPLATE
    void print(   const Container<Key, Value, Hash, Pred, Alloc>& collection
                , const std::string& elem_separator   = default_elem_separator
                , const std::string& keyval_separator = default_keyval_separator
                , const std::string& first_bracket    = default_first_bracket
                , const std::string& last_bracket     = default_last_bracket
                , std::ostream& os = std::cout
                , bool needEndl = true
        )
    {
        typename Container<Key, Value, Hash, Pred, Alloc>::const_iterator it = collection.begin();
        BUILD_MAP_CONTENT
        COMPILE_CONTENT
    }

    //! Prints collections like std:array<T, Size> as in Python
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param keyval_separator separator between key and value of map. For default it is the '=>'
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    ARRAY_TEMPLATE
    void print(   const Array<Type, Size>& collection
                , const std::string& elem_separator   = default_elem_separator
                , const std::string& first_bracket    = default_first_bracket
                , const std::string& last_bracket     = default_last_bracket
                , std::ostream& os = std::cout
                , bool needEndl = true
            )
    {
        typename Array<Type, Size>::const_iterator it = collection.begin();
        BUILD_CONTENT
        COMPILE_CONTENT
    }

    //! Removes all whitespaces before data in string.
    //! \param str string with data
    //! \return string without whitespaces in left part
    std::string ltrim(const std::string& str);

    //! Removes all whitespaces after data in string
    //! \param str string with data
    //! \return string without whitespaces in right part
    std::string rtrim(const std::string& str);

    //! Removes all whitespaces before and after data in string
    //! \param str string with data
    //! \return string without whitespaces before and after data in string
    std::string trim(const std::string& str);



    ////////////////////////////////////////////////////////////
    ////////////////////////ostream logic//////////////////////
    /// Should be specified for concrete containers
    /// because of another types can be suitable
    /// for templates, for example templates break
    /// the code like this "cout << string("hello") << endl;"
    ////////////////////////////////////////////////////////////



#define PROCESS_VALUE_COLLECTION(os, collection)                            \
    print(  collection,                                                     \
            default_elem_separator,                                         \
            default_first_bracket,                                          \
            default_last_bracket,                                           \
            os,                                                             \
            false                                                           \
    );                                                                      \

#define PROCESS_KEY_VALUE_COLLECTION(os, collection)                        \
    print(  collection,                                                     \
            default_elem_separator,                                         \
            default_keyval_separator,                                       \
            default_first_bracket,                                          \
            default_last_bracket,                                           \
            os,                                                             \
            false                                                           \
    );                                                                      \

    ///< specialization for vector
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::vector<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for deque
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::deque<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for list
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::list<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for set
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::set<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for multiset
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::multiset<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

#ifdef MODERN_CPP_AVAILABLE
    ///< specialization for unordered_map
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::unordered_set<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for forward_list
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::forward_list<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for array
    template<class T, std::size_t N>
    std::ostream& operator<<(std::ostream& os, const std::array<T, N>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }
#endif

    ///< specialization for map, multimap
    MAP_TEMPLATE
    std::ostream& operator<<(std::ostream& os, const Container<Key, Value>& collection)
    {
        PROCESS_KEY_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for unordered_map
    UMAP_TEMPLATE
    std::ostream& operator<<(std::ostream& os, const Container<Key, Value, Hash, Pred, Alloc>& collection)
    {
        PROCESS_KEY_VALUE_COLLECTION(os, collection)
        return os;
    }
}
1赞 phuclv 3/6/2021 #24

如果 boost 是一个选项,那么你可以使用 boost::algorithm::join。例如,打印出以下向量:std::string

#include <boost/algorithm/string/join.hpp>

std::vector<std::string> vs { "some", "string", "vector" };
std::cout << boost::algorithm::join(vs, " | ") << '\n';

对于其他类型的向量,您需要先转换为字符串

#include <algorithm>
#include <iostream>
#include <numeric>
#include <vector>

#include <boost/algorithm/string/join.hpp>
#include <boost/range/adaptor/transformed.hpp>

int main()
{
    using boost::adaptors::transformed;
    using boost::algorithm::join;

    // Generate the vector
    std::vector<int> vi(10);
    std::iota(vi.begin(), vi.end(), -3);

    // Print out the vector
    std::cout << join(vi |
                 transformed(static_cast<std::string(*)(int)>(std::to_string)),
                 ", ")
              << '\n';
}

Godbolt 上的演示

评论

0赞 phuclv 6/9/2021
你们怎么了?没有人发布过使用boost::algorithm::join
1赞 phuclv 6/19/2021 #25

您可以使用 std::experimental::make_ostream_joiner

#include <algorithm>
#include <experimental/iterator>
#include <iostream>
#include <iterator>
#include <numeric>
#include <vector>
 
int main()
{
    std::vector<int> vi(12);
    std::iota(vi.begin(), vi.end(), -5);
    std::cout << "Int vector:\n";
    std::copy(std::begin(vi),
              std::end(vi),
              std::experimental::make_ostream_joiner(std::cout, ", "));

    std::cout <<"\nString vector:\n[";
    std::vector<std::string> vs { "some", "string", "vector" };
    std::copy(std::begin(vs),
              std::end(vs),
              std::experimental::make_ostream_joiner(std::cout, "] - ["));
    std::cout << "]\n";
}

Godbolt 上的演示

0赞 Captain Hatteras 10/21/2021 #26

我写了一个打印任何可迭代对象的 an,其中包括自定义容器、标准容器和具有已知边界的数组。需要 c++11:operator<<

template<typename Container, typename = 
    std::enable_if_t<std::is_same_v<std::void_t<
        decltype(static_cast<typename Container::const_iterator (*)(const Container&)>(&std::cbegin)),
        decltype(static_cast<typename Container::const_iterator (*)(const Container&)>(&std::cend))>, void>
        && !std::is_same_v<std::string, Container>>>
std::ostream& operator<<(std::ostream& out, const Container &vec)
{
    std::cout << "[ ";
    for(const auto& t: vec){
        std::cout << t << " ";
    }
    std::cout << "] ";
    return out;
}
1赞 baz 10/11/2022 #27
template <typename T>
std::ostream& operator<<( std::ostream& ostrm, const std::vector<T>& vec ){
    ostrm << "[";
    for( int j = 0, n = vec.size(); j < n; ++j ){
        ostrm << " " << vec[ j ] << " ,"[ j < n - 1 ];
    }
    return ostrm << "]";
}

[ 1, 2, 3, 4 ]

0赞 az5112 11/5/2022 #28

您可以使用以下作为起点:perr.h

#include <vector>
#include "perr.h"

int main() {
    std::vector< int > v = { 1, 2, 3 };
    perr << v;
}

您只需要从 GitHub (https://github.com/az5112/perr) 获取标头即可。

1赞 einpoklum 4/12/2023 #29

使用 C++23,您可以简单地编写:

std::print("{}", vec);

这很有效。使用 C++20,您可以使用 和 运算符:std::format<<

std::cout << std::format("{}", vec);

当然,您需要包含适当的标准库标头(或 )。#include <print>#include <format>

对于较旧的 C++ 版本 - 请参阅其他答案。

0赞 BaiJiFeiLong 8/8/2023 #30

一个解决方案使用了该库nlohmann::json

nlohmann::json Github 主页

// Create by [email protected] at 2023-08-08 10:14:27+0800
#include <nlohmann/json.hpp>
#include <iostream>

int main() {
    std::cout << nlohmann::json(std::vector<int>{1, 2, 3}) << std::endl;
    std::cout << nlohmann::json(std::vector<int>{1, 2, 3}).dump() << std::endl;
    std::cout << nlohmann::json(std::vector<std::string>{"hello", "world"}) << std::endl;
    std::cout << nlohmann::json(std::vector<std::string>{"hello", "world"}).dump() << std::endl;
}

控制台输出

[1,2,3]
[1,2,3]
["hello","world"]
["hello","world"]