为vector<T>重载输出流运算符

22

如何重载输出流运算符是一个推荐的方式?以下操作不能实现。如果类型T的运算符<<未定义,预计编译将失败。

template < class T >
inline std::ostream& operator << (std::ostream& os, const std::vector<T>& v) 
{
    os << "[";
    for (std::vector<T>::const_iterator ii = v.begin(); ii != v.end(); ++ii)
    {
        os << " " << *ii;
    }
    os << " ]";
    return os;
}

编辑:它确实编译了,问题与命名空间无关。感谢协助。


4
你能详细说明命名空间问题及其解决方案吗?当全局命名空间中存在一个重载函数,而其参数类型来自std时,ADL将无法找到该函数,而你也无法将其放置在std中。你是如何解决这个问题的? - Jeremy W. Murphy
2
C++11语法:for (auto &i : vec) {} 可以使代码更短。 - C.W.
1
@Charles 应该写成 for(const auto&i:vec) 才能通过编译。 - Walter
6个回答

15
这是您想要的内容:
template < class T >
std::ostream& operator << (std::ostream& os, const std::vector<T>& v) 
{
    os << "[";
    for (typename std::vector<T>::const_iterator ii = v.begin(); ii != v.end(); ++ii)
    {
        os << " " << *ii;
    }
    os << "]";
    return os;
}

你在第一个 ostream 前忘记了加上 std::。

os << "[" 中的 [ 后面多了一个空格。

std::vector<T>::const_iterator 前需要加上 typename


一种更现代的方法是使用 C++ 11 中的 for range loop,而不是使用 const_iterator - Tudax

7
你真的尝试过这段代码吗?在gcc上稍加修改后,它可以正常工作。需要将std::vector<T>::const_iterator声明为typename std::vector<T>::const_iterator
你最好使用std::copy和std::ostream_iterator。
编辑:类型、相关类型和typename 无法在评论中全部展开,所以我来解释一下(顺便说一句,这是我的理解,如果有偏差请指出!)...
我认为最好用一个简单的例子来解释。
假设你有一个函数foo。
template <typename T>
void foo()
{
  T::bob * instofbob; // this is a dependent name (i.e. bob depends on T)
};

看起来还不错,通常你可以这样做

class SimpleClass
{
  typedef int bob;
};

并调用

foo<SimpleClass>(); // now we know that foo::instofbob is "int"

再次强调,这似乎很容易理解,但是有些用户会做出以下操作:

class IdiotClass
{
  static int bob;
};

现在
foo<IdiotClass>(); // oops, 

现在您拥有的是一个表达式(乘法),因为IdiotClass :: bob解析为非类型!对于人类来说,这显然是愚蠢的,但编译器无法区分类型和非类型,默认情况下,在C++中(我认为这是编译器之间的区别),所有合格的依赖名称(即T :: bob)将被视为非类型。要明确地告诉编译器依赖名称是真实类型,您必须指定typename 关键字-
template <typename T>
void foo()
{
  typedef typename T::bob *instofbob; // now compiler is happy, it knows to interpret "bob" as a type (and will complain otherwise!)
};

即使是 typedef,也适用此规则。
template <typename T>
void foo()
{
  typedef typename T::bob local_bob;
};

这更清楚了吗?

如果是通用向量,为什么std::copy是流输出的更好方式?使用std::copy可以专门编写一条长行来流输出一个向量。但是使用输出流运算符更容易实现:std::cout << "Assignments = " << assignmentIds << std::endl;。我想始终在括号内打印向量 "[" + vector + "]",如果使用std::copy则需要再加上一行只有 "]" 的代码。 - Leonid
在解决原始问题后,它可以在没有typename的情况下编译。即使它能够编译,除了存在类型被错误解释的轻微风险之外,在这种情况下指定“typename”有什么用处吗? - Leonid
@Leonid,是的,如果有人能解释一下依赖名称的概念,那就太好了。 - Stephane Rolland
@Leonid,就刚才那个简单的向量示例而言,std::copy操作似乎有些啰嗦。但现在考虑一下,假设你决定更改格式,并且想要在某些情况下用“,”分隔,在其他情况下用“ ”分隔,那么你如何使用单个全局operator<<来实现?现在让我们想象一下,如果有人重复使用您的代码并想要做一些不同的事情呢?我对这种全局操作符持非常谨慎的态度... - Nim
@Leonid,关于你的第二点,如果你的编译器满意并且你很高兴你永远不需要在另一个编译器中重新编译你的代码,那么我就不会烦恼;但是为了一个关键字“typename”,如果你曾经处于在不同编译器之间移动的位置,你可以避免一些头疼的问题,因为你留下了它而导致的冗长的编译器错误信息!这是你的决定... - Nim
"<< 之所以好用,是因为它也适用于 vector<vector<vector<double>>>,而不需要编写两个 for_each/range 循环。" - kirill_igum

2
template<typename T>
std::ostream& operator<<(std::ostream& s, std::vector<T> t) { 
    s << "[";
    for (std::size_t i = 0; i < t.size(); i++) {
        s << t[i] << (i == t.size() - 1 ? "" : ",");
    }
    return s << "]" << std::endl;
}

有人能告诉我,为什么我不能将这段代码标记为<pre><code>吗? - smartnut007
因为SO使用了修改版的“markdown”。对于格式化代码,请缩进4个空格。 - Steve Jessop
1
警告:有符号整数与无符号整数进行比较 :( - Inverse

1

这段代码在Visual Studio 2003上编译通过。 建议在 const std::vector<T> 前使用关键字 typename。 我认为 inline 关键字没有意义,因为模板本身就非常接近于内联。

#include <ostream>
#include <vector>
#include <iostream>

template < class T >
std::ostream& operator << (std::ostream& os, typename const std::vector<T>& v) 
{
    os << "[ ";
    for (typename std::vector<T>::const_iterator ii = v.begin(); ii != v.end(); ++ii)
    {
        os << " " << *ii;
    }
    os << "]";
    return os;
}

void Test()
{
    std::vector<int> vect;
    vect.push_back(5);
    std::cerr << vect;
}

编辑:我已经按照Nim的建议在std::vector<T>::const_iterator之前添加了一个typename


2
内联与模板化是正交的。 - sbi
3
@Konrad inline 对于模板不是必需的。ODR 允许省略它。 - Johannes Schaub - litb
1
@Konrad,C++标准规定:“typename关键字只能应用于限定名,但这些名称不一定是相关的。”此外,C++03还强制规定:“typename关键字只能在模板声明和定义中使用”,但C++0x将要删除该规定。尽管如此,std::vector<T>肯定是一个相关的名称。但是在这里你不需要使用typename,因为编译器可以在解析时查找std::vector并知道它是一个类模板(因此知道std::vector<T>是一个类型)。 - Johannes Schaub - litb
1
他实际想写的是 const typename ...typename ... const 而不是 typename const ...,尽管最后一个在语法上是非法的。 - Johannes Schaub - litb
1
@Konrad vector<T> 没有被限定,但是 std::vector<T> 被限定了。关于内联的问题,请参见 3.2 最后的项目列表(我目前没有标准),其中提到了类内联函数以及函数/类模板等内容。 - Johannes Schaub - litb
显示剩余16条评论

0

对于在C++11之后进入此线程的人,请使用范围for循环使代码更易于阅读。

template <class T>
std::ostream &operator<<(std::ostream &os, const std::vector<T> &v) {
  for (const auto &x : v) {
    os << '[' << x << ']';
  }
  return os;
}

1
你需要在每个元素之间添加一个 ] - Tudax

0

这里是一个关于使用标准库算法(https://en.cppreference.com/w/cpp/algorithm/for_each)中的for_each,为std::vector提供operator<<重载示例的贡献。

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

template <typename T>
std::ostream& operator<<(std::ostream& os, std::vector<T> vec) {

    // printing the first element using at so it is bound checked
    // also, this helps to make the print prettier
    os << "{" << vec.at(0) << "}";

    // using for_each to go from second to last element of vec
    std::for_each(std::cbegin(vec)+1, std::cend(vec), [&](auto e){os << ", {" << e << "}";});

    return os;
}

int main() {
    std::vector<int> vec;

    vec.push_back(1);
    vec.push_back(2);

    std::cout << vec << std::endl;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接