Cout的扩展
写这个笔记完全是由一道题起的,题目就是如何用iostream迭代器实现类及容器的输出(想必大家早已厌烦了写一个while或for循环来做这件事吧,而且这样做函数基本都是专用,不能实现通用)。(结合C++Primer 第11章)
最初尝试代码如下:
#include<iostream>
#include<fstream>
#include<vector>
#include<algorithm>
#include<iterator>
#include<string>
using namespace std;
template<class T,classbaseType>
void Show(T &v,baseType r)
{
//第二个参数仅仅是为了传递类型
ostream_iterator<baseType>out(cout," ");
copy(v.begin(),v.end(),out);
cout<<endl;
}
template<class T>
void Show(T &v)
{
Show(v,v[0]); //注意重载函数时,常量引用和非常量引用是不同函数
}
int main()
{
vector<string> vec(10,"Bosch");
Show(vec,string(""));
Show(vec);
return 0;
}
|
1) ostream_iterator<baseType> out(cout," ")是声明一个ostream迭代器,其声明将baseType类型的对象写到输出流cout(此处为标准输出,也可以是其它输出流)的ostream_iterator对象,在写入过程中,使用delim作为元素的分隔。Delim是以空字符结束字符数组。
2) copy(v.begin(),v.end(),out),则是将类型T中的在迭代器对范围内的元素拷贝到out所迭代的输出流中。
3) 这个函数实现了输出任何一维容器的功能(当然容器中必须存储基础类型)。
输出一维成功后,我尝试了一下二维容器,结果不甚理想,会在编译期报出类似找不到匹配的“<<“操作符的错误,先看程序:
#include<iostream>
#include<fstream>
#include<vector>
#include<algorithm>
#include<iterator>
#include<string>
using namespace std;
template<class T,class baseType>
void Show(T &v,baseType r)
{
ostream_iterator<baseType> out(cout," ");
copy(v.begin(),v.end(),out);
cout<<endl;
}
template<class T>
void Show(T &v)
{
Show(v,v[0]);
}
int main()
{
vector<string> vec(10,"Bosch");
Show(vec,string(""));
Show(vec);
vector<vector<string> > dvec(2,vec);
Show(dvec[0],string(""));
Show(*dvec.begin(),string(""));
Show(dvec,vec);
ostream_iterator< vector<string> > out(cout," ");
copy(dvec.begin(),dvec.end(),out);
}
|
Show(dvec,vec);
copy(dvec.begin(),dvec.end(),out);
上面两句话编译不通过,而且出来很大篇幅错误,真是无语,我是看不懂了。后经有经验的程序员高人指点,说是“<<“无法找到匹配的,我开始有了概念,大概是没有输出vector<string>类型的操作符重载,那么很自然的想到重载cout。说做就做,见下面程序。
#include<iostream>
#include<fstream>
#include<vector>
#include<algorithm>
#include<iterator>
#include<string>
using namespace std;
ostream& operator << (ostream& os,vector<string>&vec)
{
vector<string>::iterator iter = vec.begin();
while (iter != vec.end())
{
os<<*iter++<<" 0 ";
}
os<<endl;
return os;
}
template<class T,class baseType>
void Show(T &v,baseType r)
{
ostream_iterator<baseType> out(cout," ");
copy(v.begin(),v.end(),out);
cout<<endl;
}
template<class T>
void Show(T &v)
{
Show(v,v[0]);
}
int main()
{
vector<string> vec(10,"Bosch");
Show(vec,string(""));
Show(vec);
cout<<vec;
vector<vector<string> > dvec(2,vec);
Show(dvec[0],string(""));
Show(*dvec.begin(),string(""));
ostream_iterator< vector<string> > out(cout," ");
copy(dvec.begin(),dvec.end(),out);
Show(dvec,vec);
}
|
copy(dvec.begin(),dvec.end(),out);
Show(dvec,vec);
这两行仍然编译不通过,提示错误相同,还是找不到匹配的“<<“操作符,这样我着实郁闷。现在只能是copy内部有问题了,在stackoverflow网站找到了代码:
template<class InputIterator, class OutputIterator>
OutputIterator mycopy (InputIterator first, InputIterator last, OutputIterator result)
{
while (first != last) *result++ = *first++;
return result;
}
|
看完有点小惊讶,copy函数居然这么简单,看来我们真的不要把标准库看的太难啊,可是就这么简单的一行代码,怎么会,我只能猜测vector<string>不支持赋值操作,结果简单的测试一下马上推翻了这个假设。
这下这是黔驴技穷了,没办法硬着头皮找一下ostream_iterator的实现,同时命名为myostream_iterator(不重命名会重定义):
template <class T, class charT=char, class traits=char_traits<charT> >
class myostream_iterator :public iterator<output_iterator_tag, void, void, void, void>
{
basic_ostream<charT,traits>* out_stream;
const charT* delim;
public:
typedef charT char_type;
typedef traits traits_type;
typedef basic_ostream<charT,traits> ostream_type;
myostream_iterator(ostream_type& s) : out_stream(&s), delim(0) {}
myostream_iterator(ostream_type& s, const charT* delimiter)
: out_stream(&s), delim(delimiter) { }
myostream_iterator(const myostream_iterator<T,charT,traits>& x)
: out_stream(x.out_stream), delim(x.delim) {}
~myostream_iterator() {}
//关键函数,也是“<<“找不到匹配的地方。
myostream_iterator<T,charT,traits>& operator= (const T& value) {
*out_stream << value;
if (delim!=0) *out_stream << delim;
return *this;
}
myostream_iterator<T,charT,traits>& operator*() { return *this; }
myostream_iterator<T,charT,traits>& operator++() { return *this; }
myostream_iterator<T,charT,traits>& operator++(int) { return *this; }
};
|
耐心研究一下,这个类并不复杂,除了一个较为底层的basic_ostream<charT,traits>类型没怎么见过外,其他的都很容易理解。而且不难发现错误就处在函数:myostream_iterator<T,charT,traits>&
operator= (constT& value)中的
*out_stream << value;这句话,所以重载还是必要的,那么为什么重载了不好使呢,我又尝试了重载的另一个版本:
typedef basic_ostream<char,char_traits<char> > ostream_Type;
///you can see the ostream_Type as ostream.
ostream_Type& operator << (ostream_Type& os,vector<string>& vec)
{
vector<string>::iterator iter = vec.begin();
while (iter != vec.end())
{
os<<(*iter++)<<" ";
}
os<<endl;
return os;
}
|
开始把这个函数放在类里面结果不好使,提示函数调用少一个参数(至于怎么改进目前还不知道)后来把这个函数放在外面,结果仍然出错。结果发现:
myostream_iterator<T,charT,traits>&operator= (const T& value)
的第二个参数是常量类型,所以我们重载函数也必须是const类型的引用参数,否则不会匹配,也就不会调用。
注意:在C++中,重载函数时,编译器会识别常引用参数和非常引用参数,所以会产生不同的函数版本,忽略这个特性会产生很隐蔽的错误。因为非常引用的形参是不会接受常量引用作为实参的。
上述事实也是重载后仍然无效的错觉,改进后我们仍然发现编译不通过,这是因为:
vector<string>::iterator iter = vec.begin();
这条语句的原因,我们必须给它声明常量迭代器。修改后的代码如下:
typedef basic_ostream<char,char_traits<char> > ostream_Type;
///you can see the ostream_Type as ostream.
ostream_Type& operator << (ostream_Type& os,const vector<string>& vec)
{
vector<string>::const_iterator iter = vec.begin();
while (iter != vec.end())
{
os<<(*iter++)<<" ";
}
os<<endl;
return os;
}
|
至此我们需要的二维容器也可以正常输出了,最终代码如下:
#include<iostream>
#include<fstream>
#include<vector>
#include<algorithm>
#include<iterator>
#include<string>
using namespace std;
typedef basic_ostream<char,char_traits<char> > ostream_Type;
///you can see the ostream_Type as ostream.
ostream_Type& operator << (ostream_Type& os,const vector<string>& vec)
{
vector<string>::const_iterator iter = vec.begin();
while (iter != vec.end())
{
os<<(*iter++)<<" ";
}
os<<endl;
return os;
}
template <class T, class charT=char, class traits=char_traits<charT> >
class myostream_iterator :public iterator<output_iterator_tag, void, void, void, void>
{
basic_ostream<charT,traits>* out_stream;
//ostream* out_stream;
const charT* delim;
public:
typedef charT char_type;
typedef traits traits_type;
typedef basic_ostream<charT,traits> ostream_type;
//typedef ostream ostream_type;
myostream_iterator(ostream_type& s) : out_stream(&s), delim(0) {}
myostream_iterator(ostream_type& s, const charT* delimiter)
: out_stream(&s), delim(delimiter) { }
myostream_iterator(const myostream_iterator<T,charT,traits>& x)
: out_stream(x.out_stream), delim(x.delim) {}
~myostream_iterator() {}
myostream_iterator<T,charT,traits>& operator= (const T& value) {
*out_stream << value;
if (delim!=0) *out_stream << delim;
return *this;
}
myostream_iterator<T,charT,traits>& operator*() { return *this; }
myostream_iterator<T,charT,traits>& operator++() { return *this; }
myostream_iterator<T,charT,traits>& operator++(int) { return *this; }
};
template<class T,class baseType>
void Show(T &v,baseType r)
{
myostream_iterator<baseType> out(cout," ");
copy(v.begin(),v.end(),out);
cout<<endl;
}
/*ostream& operator << (ostream& os,const vector<string>&vec)
{
vector<string>::const_iterator iter = vec.begin();
while (iter != vec.end())
{
os<<*iter++<<" 0 ";
}
os<<endl;
return os;
}*/
template<class InputIterator, class OutputIterator>
OutputIterator mycopy (InputIterator first, InputIterator last, OutputIterator result)
{
while (first != last) *result++ = *first++;
return result;
}
template<class T>
void Show(T &v)
{
Show(v,v[0]);
}
int main()
{
vector<string> vec(10,"Bosch");
Show(vec);
Show(vec,string(""));
cout<<vec;
vector<vector<string> > dvec(2,vec);
Show(dvec[0],string(""));
Show(*dvec.begin(),string(""));
myostream_iterator< vector<string> > out(cout," ");
vector<string> tes = vec;
//*out<<*dvec.begin();
mycopy(dvec.begin(),dvec.end(),out);
cout<<"*******************Success*********************"<<endl;
Show(dvec,vec);
}
|
ostream& operator << (ostream& os,const vector<string>&vec)
ostream_Type& operator << (ostream_Type& os,constvector<string>& vec)
两个版本在这个程序中都可以用。加重的部分几乎等价,至于差别以后在探究吧。
上面使用的是我们自己的代码下面除了重载cout完全用库函数:即使用库中的ostream_iterator仍然报错,无语,几经尝试我仍旧束手无策,那么没办法,看linux下标准库源码吧,毕竟网上找的ostream_iterator源代码也许不够权威。
度娘真是个好东西,经过一番搜索得知iostream_iterator包含在头文件<iterator>中,然后进入/usr/include/c++下找到该头文件,打开之后如下所示:
从中无法得到直接定义,但是我们稍加观察会发现有个stream_iterator.h那么我们可以猜测,在该文件中,打开文件果真是有的(此处只贴出ostream_iterator代码,其他完整代码见博客源码目录下):
/**
* @brief Provides output iterator semantics for streams.
*
* This class provides an iterator to write to an ostream. The type Tp is
* the only type written by this iterator and there must be an
* operator<<(Tp) defined.
*
* @param Tp The type to write to the ostream.
* @param CharT The ostream char_type.
* @param Traits The ostream char_traits.
*/
template<typename _Tp, typename _CharT = char,
typename _Traits = char_traits<_CharT> >
class ostream_iterator
: public iterator<output_iterator_tag, void, void, void, void>
{
public:
//@{
/// Public typedef
typedef _CharT char_type;
typedef _Traits traits_type;
typedef basic_ostream<_CharT, _Traits> ostream_type;
//@}
private:
ostream_type* _M_stream;
const _CharT* _M_string;
public:
/// Construct from an ostream.
ostream_iterator(ostream_type& __s) : _M_stream(&__s), _M_string(0) {}
/**
* Construct from an ostream.
*
* The delimiter string @a c is written to the stream after every Tp
* written to the stream. The delimiter is not copied, and thus must
* not be destroyed while this iterator is in use.
*
* @param s Underlying ostream to write to.
* @param c CharT delimiter string to insert.
*/
ostream_iterator(ostream_type& __s, const _CharT* __c)
: _M_stream(&__s), _M_string(__c) { }
/// Copy constructor.
ostream_iterator(const ostream_iterator& __obj)
: _M_stream(__obj._M_stream), _M_string(__obj._M_string) { }
/// Writes @a value to underlying ostream using operator<<. If
/// constructed with delimiter string, writes delimiter to ostream.
ostream_iterator&
operator=(const _Tp& __value)
{
__glibcxx_requires_cond(_M_stream != 0,
_M_message(__gnu_debug::__msg_output_ostream)
._M_iterator(*this));
*_M_stream << __value;
if (_M_string) *_M_stream << _M_string;
return *this;
}
ostream_iterator&
operator*()
{ return *this; }
ostream_iterator&
operator++()
{ return *this; }
ostream_iterator&
operator++(int)
{ return *this; }
};
|
看完源码,就更加使我们困惑了,为什么会这样,这个版本和我们上面的版本几乎相同,于是我想到将标准ostream_iterator替换上面的myostream_iterator(当然此时不需要包含<iterator>),结果出乎意料,结果一切正常。仔细捉摸一下发现可能是标准库无法找到我重载的函数,随即,我便想到把上面的库文件stream_iterator复制一份命名为iostream_iterator.h放到当前工作目录中,然后用include<iostream_iterator.h>包含,果不出所料,这样就会报错,然后我将重载的"<<"函数放入该头文件中,编译通过。至此我们算是找到了问题的根源。
最终源码:
(1)iostream_iterator.h
// Stream iterators
// Copyright (C) 2001, 2004, 2005, 2009 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library. This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3, or (at your option)
// any later version.
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// Under Section 7 of GPL version 3, you are granted additional
// permissions described in the GCC Runtime Library Exception, version
// 3.1, as published by the Free Software Foundation.
// You should have received a copy of the GNU General Public License and
// a copy of the GCC Runtime Library Exception along with this program;
// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
// <http://www.gnu.org/licenses/>.
/** @file stream_iterator.h
* This is an internal header file, included by other library headers.
* You should not attempt to use it directly.
*/
#ifndef _STREAM_ITERATOR_H
#define _STREAM_ITERATOR_H 1
#pragma GCC system_header
#include <debug/debug.h>
_GLIBCXX_BEGIN_NAMESPACE(std)
/// Provides input iterator semantics for streams.
template<typename _Tp, typename _CharT = char,
typename _Traits = char_traits<_CharT>, typename _Dist = ptrdiff_t>
class istream_iterator
: public iterator<input_iterator_tag, _Tp, _Dist, const _Tp*, const _Tp&>
{
public:
typedef _CharT char_type;
typedef _Traits traits_type;
typedef basic_istream<_CharT, _Traits> istream_type;
private:
istream_type* _M_stream;
_Tp _M_value;
bool _M_ok;
public:
/// Construct end of input stream iterator.
istream_iterator()
: _M_stream(0), _M_value(), _M_ok(false) {}
/// Construct start of input stream iterator.
istream_iterator(istream_type& __s)
: _M_stream(&__s)
{ _M_read(); }
istream_iterator(const istream_iterator& __obj)
: _M_stream(__obj._M_stream), _M_value(__obj._M_value),
_M_ok(__obj._M_ok)
{ }
const _Tp&
operator*() const
{
__glibcxx_requires_cond(_M_ok,
_M_message(__gnu_debug::__msg_deref_istream)
._M_iterator(*this));
return _M_value;
}
const _Tp*
operator->() const { return &(operator*()); }
istream_iterator&
operator++()
{
__glibcxx_requires_cond(_M_ok,
_M_message(__gnu_debug::__msg_inc_istream)
._M_iterator(*this));
_M_read();
return *this;
}
istream_iterator
operator++(int)
{
__glibcxx_requires_cond(_M_ok,
_M_message(__gnu_debug::__msg_inc_istream)
._M_iterator(*this));
istream_iterator __tmp = *this;
_M_read();
return __tmp;
}
bool
_M_equal(const istream_iterator& __x) const
{ return (_M_ok == __x._M_ok) && (!_M_ok || _M_stream == __x._M_stream); }
private:
void
_M_read()
{
_M_ok = (_M_stream && *_M_stream) ? true : false;
if (_M_ok)
{
*_M_stream >> _M_value;
_M_ok = *_M_stream ? true : false;
}
}
};
/// Return true if x and y are both end or not end, or x and y are the same.
template<typename _Tp, typename _CharT, typename _Traits, typename _Dist>
inline bool
operator==(const istream_iterator<_Tp, _CharT, _Traits, _Dist>& __x,
const istream_iterator<_Tp, _CharT, _Traits, _Dist>& __y)
{ return __x._M_equal(__y); }
/// Return false if x and y are both end or not end, or x and y are the same.
template <class _Tp, class _CharT, class _Traits, class _Dist>
inline bool
operator!=(const istream_iterator<_Tp, _CharT, _Traits, _Dist>& __x,
const istream_iterator<_Tp, _CharT, _Traits, _Dist>& __y)
{ return !__x._M_equal(__y); }
/**
* @brief Provides output iterator semantics for streams.
*
* This class provides an iterator to write to an ostream. The type Tp is
* the only type written by this iterator and there must be an
* operator<<(Tp) defined.
*
* @param Tp The type to write to the ostream.
* @param CharT The ostream char_type.
* @param Traits The ostream char_traits.
*/
template<typename _Tp, typename _CharT = char,
typename _Traits = char_traits<_CharT> >
class ostream_iterator
: public iterator<output_iterator_tag, void, void, void, void>
{
public:
//@{
/// Public typedef
typedef _CharT char_type;
typedef _Traits traits_type;
typedef basic_ostream<_CharT, _Traits> ostream_type;
//@}
private:
ostream_type* _M_stream;
const _CharT* _M_string;
public:
/// Construct from an ostream.
ostream_iterator(ostream_type& __s) : _M_stream(&__s), _M_string(0) {}
/**
* Construct from an ostream.
*
* The delimiter string @a c is written to the stream after every Tp
* written to the stream. The delimiter is not copied, and thus must
* not be destroyed while this iterator is in use.
*
* @param s Underlying ostream to write to.
* @param c CharT delimiter string to insert.
*/
ostream_iterator(ostream_type& __s, const _CharT* __c)
: _M_stream(&__s), _M_string(__c) { }
/// Copy constructor.
ostream_iterator(const ostream_iterator& __obj)
: _M_stream(__obj._M_stream), _M_string(__obj._M_string) { }
/// Writes @a value to underlying ostream using operator<<. If
/// constructed with delimiter string, writes delimiter to ostream.
ostream_iterator&
operator=(const _Tp& __value)
{
__glibcxx_requires_cond(_M_stream != 0,
_M_message(__gnu_debug::__msg_output_ostream)
._M_iterator(*this));
*_M_stream << __value;
if (_M_string) *_M_stream << _M_string;
return *this;
}
ostream_iterator&
operator*()
{ return *this; }
ostream_iterator&
operator++()
{ return *this; }
ostream_iterator&
operator++(int)
{ return *this; }
};
typedef basic_ostream<char,char_traits<char> > ostream_Type;
ostream_Type& operator << (ostream_Type& os,const vector<string>& vec)
{
vector<string>::const_iterator iter = vec.begin();
while (iter != vec.end())
{
os<<(*iter++)<<" ";
}
os<<endl;
return os;
}
ostream_Type& operator << (ostream_Type& os,vector<string>& vec)
{
vector<string>::iterator iter = vec.begin();
while (iter != vec.end())
{
os<<(*iter++)<<" ";
}
os<<endl;
return os;
}
_GLIBCXX_END_NAMESPACE
#endif
|
(1)main.c
#include<iostream>
#include<fstream>
#include<vector>
#include<algorithm>
#include<string>
#include"iostream_iterator.h"
using namespace std;
//typedef basic_ostream<char,char_traits<char> > ostream_Type;
///you can see the ostream_Type as ostream.
template<class T,class baseType>
void Show(T &v,baseType r)
{
ostream_iterator<baseType> out(cout," ");
copy(v.begin(),v.end(),out);
cout<<endl;
}
/*ostream& operator << (ostream& os,const vector<string>&vec)
{
vector<string>::const_iterator iter = vec.begin();
while (iter != vec.end())
{
os<<*iter++<<" 0 ";
}
os<<endl;
return os;
}*/
template<class InputIterator, class OutputIterator>
OutputIterator mycopy (InputIterator first, InputIterator last, OutputIterator result)
{
while (first != last) *result++ = *first++;
return result;
}
template<class T>
void Show(T &v)
{
Show(v,v[0]);
}
int main()
{
vector<string> vec(10,"Bosch");
Show(vec);
Show(vec,string(""));
cout<<vec;
vector<vector<string> > dvec(2,vec);
Show(dvec[0],string(""));
Show(*dvec.begin(),string(""));
ostream_iterator< vector<string> > out(cout," ");
vector<string> tes = vec;
//*out<<*dvec.begin();
copy(dvec.begin(),dvec.end(),out);
cout<<"*******************Success*********************"<<endl;
Show(dvec,vec);
}
|
尽管解决了这个问题,又引出了一个值得思考的问题,当把代码直接贴入main.c 时,是可以找到重载的"<<"的,为什么include不可以呢,以前我们不是可以把include的动作理解为"抄写"吗?
通过耐心测试发现,根源不在于我们对include的理解错误,而在于,命名空间的问题,iostream_iterator.h 所使用的命名空间和我们主文件不同,所以找不到函数。在iostream_iterator.h前面加一条using namespace std;问题圆满解决;
看来命名空间是个值得探讨的东西,我们需要好好研究一下。
优快云高人指点:
这是是c++名字查找规则引起的。要想找到自定义的operator<<(xxx, vector<xxx>)函数,有以下几种办法:
① 将该函数的定义放在namespace std中(一般名字查找规则);
② 像版主那样,在与该函数相同的命名空间内定义自己的ostream_iterator。(将标准库的相应版本拷贝出来改个类名即可)(一般名字查找规则)
③ 提供vector<xxx>的一个隐式类型转换类vector_wrapper,并未该类提供operator<<(xxx, vector_wrapper)(Koenig名字查找)
所以目前一个较为理想的解决方案是,将重载的"<<"放入std中,如:
namespace std{
ostream& operator << (ostream& os,const vector<string>&vec)
{
vector<string>::const_iterator iter = vec.begin();
while (iter != vec.end())
{
os<<*iter++<<" 0 ";
}
os<<endl;
return os;
}
}
参考:
http://bytes.com/topic/c/answers/716006-ostream_iterator-vector-pair-int-string
http://www.cnblogs.com/zhenjing/archive/2010/10/20/1856309.html