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


实现以下操作符<<的解决方案也很好:

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

当前回答

这里是一个工作库,作为一个完整的工作程序,我刚刚把它组合在一起:

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

它目前只适用于vector和set,但通过扩展IsContainer专门化,可以使其适用于大多数容器。我没有过多地考虑这些代码是否最少,但我不能立即想到任何可以去掉的冗余代码。

编辑:只是为了好玩,我包含了一个处理数组的版本。我不得不排除字符数组,以避免进一步的歧义;使用wchar_t[]仍然会遇到麻烦。

其他回答

我的解决方案是simple.h,它是scc包的一部分。所有std容器,map, set, c-arrays都是可打印的。

我认为最好的方法是通过在程序中添加这个函数来重载操作符<<:

#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&运算符<<:

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}

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

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

输出:

a b c 

这个解决方案的灵感来自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类型的特征,必须为所有要支持的容器特殊化。也许可以使用trait来检查value_type、const_iterator、begin()/end(),但我不确定我是否会推荐这样做,因为它可能匹配那些符合这些标准但实际上不是容器的东西,比如std::basic_string。同样像Marcelo的版本一样,它使用可以专门化的模板来指定要使用的分隔符。

主要的区别是,我围绕pretty_ostream_iterator构建了我的版本,它的工作原理类似于std::ostream_iterator,但没有在最后一项后面打印分隔符。容器的格式化是由print_container_helper完成的,它可以直接用于打印不带is_container特征的容器,或指定不同的分隔符类型。

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

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

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

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

在c++ 11(以及以后的版本)中,你可以使用新的基于范围的for循环,它看起来像这样:

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

for-loop语句中的type char应该是vector路径元素的类型,而不是整数索引类型。换句话说,因为path的类型是std::vector<char>,所以应该出现在基于范围的for循环中的类型是char。然而,你可能会经常看到显式类型被auto占位符类型取代:

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

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

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循环中更改i的复制值,你可以强制i的类型为const char,如下所示:

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

如果你想修改path中的项,以便这些更改在for循环之外的path中持续存在,那么你可以像这样使用引用:

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

即使你不想修改path,如果对象的复制是昂贵的,你应该使用const引用,而不是按值复制:

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循环中修改vector的内容,那么使用iterator而不是const_iterator。

补充:typedef / type alias (c++ 11) / auto (c++ 11)

这不是另一个解决方案,而是对上述迭代器解决方案的补充。如果你正在使用c++ 11标准(或更高版本),那么你可以使用auto关键字来帮助提高可读性:

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

这里i的类型将是非const(即,编译器将使用std::vector<char>::iterator作为i的类型)。这是因为我们调用了begin方法,所以编译器由此推导出i的类型。如果我们改为调用cbegin方法(“c”表示const),则i将是std::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++ 11编译器(或者出于某种原因不喜欢类型别名语法),那么你可以使用更传统的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 << ' ';

注:

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

It is convenient to have an object (the iterator) that does the operation you want (like sequential access) rather than you writing the details yourself (the "details" being the code that does the actual accessing of the elements of the vector). You should notice that in the for-loop you are only ever asking the iterator to return you a value (*i, where i is the iterator) -- you are never interacting with path directly itself. The logic goes like this: you create an iterator and give it the object you want to loop over (iterator i = path.begin()), and then all you do is ask the iterator to get the next value for you (*i); you never had to worry exactly how the iterator did that -- that's its business, not yours.

OK, but what's the point? Well, imagine if getting a value wasn't simple. What if it involves a bit of work? You don't need to worry, because the iterator has handled that for you -- it sorts out the details, all you need to do is ask it for a value. Additionally, what if you change the container from std::vector to something else? In theory, your code doesn't change even if the details of how accessing elements in the new container does: remember, the iterator sorts all the details out for you behind the scenes, so you don't need to change your code at all -- you just ask the iterator for the next value in the container, same as before.

所以,虽然这看起来像是对vector循环的过度使用,但迭代器的概念背后有很好的理由,所以你最好习惯使用它们。

索引

你也可以使用整数类型显式地在for循环中为vector元素建立索引:

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

如果要这样做,最好使用容器的成员类型,如果它们可用且合适的话。vector有一个名为size_type的成员类型:它是size方法返回的类型。

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] << ' ';

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

std::复制(C + + 11)

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

std::范围:复制(C + + 20)

为了完整起见,c++ 20引入了range,它可以作用于std::vector的整个范围,因此不需要begin和end:

#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::操作符< <

下面是克里斯的回答。这更像是对其他答案的补充,因为您仍然需要在重载中实现上面的解决方案之一,但好处是代码更简洁。这是你如何使用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对象传递到输出流。使用上述任何其他解决方案也应该同样简单。

结论

这里提供的任何解决方案都可以工作。这取决于你(环境或你的编码标准),哪一个是“最好的”。任何比这更详细的问题可能最好留给另一个问题,在那里可以正确地评估利弊,但一如既往,用户偏好总是起作用的:所提出的解决方案在客观上都是错误的,但有些对每个程序员来说都更好。

齿顶高

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