本篇博客笔记顺序大体按照《C++标准程序库(第1版)》各章节顺序编排。
--------------------------------------------------------------------------------------------
13 以Stream Classes 完成输入和输出 13.1 String对象
(1)C++ I/O 由streams完成。所谓stream就是一条数据“流”。输出操作被解读为“数据流入stream”,输入操作则是“数据流出stream”。
(2)全局性的Stream对象
1. cin(隶属于istream), 标准输入通道;
2. cout(隶属于ostream),标准输出通道;
3. cerr(隶属于ostream),标准错误输出通道;
4. clog(隶属于ostream),标准日志通道;
(3)Stream操作符: operator >> 和 operator << 都被相应的stream classes 重载,分别用于输入和输出。因此,C++ 移位操作符摇身一变成了I/O操作符。
(4)操控器,表13.1 列出了IOStream 程序库中一些重要的操控器。
13.2 基本的Stream类别和 Stream对象
(1)IOStream程序库中的stream classes形成了图13.1所示的阶层体系。
解析如下:
1. 基类 ios_base 定义了stream classes 的所有“与字符型别及其相应之字符特性(traits)无关”的属性,主要包含状态和格式标志等组件和函数。
2. 由 ios_base 派生的 template class basic_ios<>,定义出“与字符型及其相关之字符特性(traits)相关”的 stream classes 共同属性,其中包括 stream 所用的缓冲器。缓冲器所属型别派生自 template class basic_streambuf<>,其具现参数和 basic_ios<> 一致。basic_streambuf<> 负责实际的读写操作。
3. template class basic_istream<> 和 basic_ostream<> 两者都虚拟继承自basic_ios<>,分别定义出用于读/写的对象。和 basic_ios<>一样,它们以字符型别及其特性(traits)作为参数。如果无关乎国际化议题,一般使用由字符型别char体现出来的istream和ostream就够了。
4. template class basic_iostream<> 派生(多重继承)自 basic_istream<> 和 basic_ostream<> ,用来定义既可读取亦可改写的对象。
5. template class basic_streambuf<> 是IOStream程序库的核心,定义出所有“可改写的stream”或“可读取的stream”的接口。其它stream classes 均利用它进行实际的字符读写操作。程序中为处理某些外部表达,必须从basic_streambuf<> 派生一些可用类别。
IOStream 程序库严格依照“职责分离”的原则来设计。basic_ios派生类别只处理数据的格式化,实际读写操作由basic_ios派生类别所维护的stream buffers完成。stream buffers 提供读写时所使用的字符缓冲区,并形成对外部表述(如文件和字符串)的一种抽象概念。
运用stream buffers ,我们可以轻松定义出对于新的外部表述(例如某种新的存储设备)的存取操作。我们需要做的仅仅是从basic_streambuf<>派生出一个新的stream buffer类别(或其适当特化版本),并定义该外部表述的字符读写函数即可。如果某个stream 对象初始化时候使用了该stream buffer,则所有I/O格式化操作的选项均可自动生效。
(2)具体的类别定义 和IOStream程序库的所有template classes一样,basic_ios<>有两个参数:
namespace std { template <class charT, class traits = char_traits<charT> > class basic_ios; }
这两个参数分别是1. stream classes所使用的字符型别,2. 前者的特性类别(traits class)
在特性类别中,enf-of-file值和复制/移动字符序列的各个指令,都属于特性(traits)的一部分。字符型别的特性(traits)和字符型别本身通常密不可分,因此定义一个template class并针对特定型别实施特化也就合情合理。针对字符型别charT,特性类别缺省为char_traits<charT>。关于char_traits<>,标准程序库提供了char和wchar_t两个特化版本。下面是两个最常使用的basic_ios<>具体实体:
namespace std { typedef basic_ios<char> ios; typedef basic_ios<wchar_t> wios; }
针对basic_streambuf<>, basic_istream<>, basic_ostream<>, basic_iostream<>,当然也以字符型别和特性类别(traits class)作为参数。
针对前面提到的全局性的stream对象,同样分别以char或对应的wchar_t作为字符型别,用来存取标准I/O通道。表13.2,缺省情况下,这些stream都和标准C stream同步。也就是说C++标准程序库确保在“混合使用C++ streams 和 C streams ”的情况下,顺序有保障。任何标准C++ stream 缓冲区在改写数据前,都会先刷新其所对应的C Stream缓冲区,反之亦然。当然,保持同步会占用一定时间。如果你不需要这样的功能,可以在输入或输出前调用sync_with_stdio(false),便可取消同步。
(3)各个stream classes 的定义分散于一下数个头文件:
1. <iosfwd>:内含stream classes 的前置声明。
2. <streambuf>:内含stream buffer基类(basic_streambuf<>)的定义。
3. <istream>:内含仅支持输入的类别(basic_istream<>)和同时支持输入输出的类别(basic_iostream<>)的定义。
4. <ostream>: 内含output stream(basic_ostream<>)的类别定义。
5. <iostream>:内含全局性的stream对象(例如cin和cout)的定义。
大部分头文件主要用于C++标准程序库的内部组织。IOStream程序库使用者只需含入拥有各个stream classes 声明式的<iosfwd>,并在运用输入或输出功能时分别含入<istream>或<ostream>即可。除非用到标准stream对象,否则不需要含入<iostream>。在某些实作版本中,每一个含入<iostream>的编译单元在启动(start-up)时都需要执行一段程序代码;虽说其执行负荷并不高,但却必须加载相应的执行分页,这项耗费可能不小。一般来说,有必要含入的头文件,我们才含入它。
(4)C++ 的操作符 << 和 >> 分别用于位左移和右移,然而basic_istream<> 和 basic_ostream<> 重载了它们,使之成为标准的I/O 操作符。使用这两个操作符,我们不再需要指定待打印的型别,只要针对不同型别进行重载,就可以保证编译器会自动推断出正确的打印函数。同时它们还具有可串接打印的特性(因为返回的依然是一个stream对象)。
标准 I/O 操作符还定义了bool,char* 和 void* 型别的输入/输出。此外我们也可以更加扩展,将<< 和 >> 运用在我们自己定义的型别身上。
13.4 Streams 的状态
(1)用来表示Streams状态的一些常数 表13.3 iostate型别的常量
注意,eofbit常常和failbit同时出现,因为在end-of-file之后再试图读取数据,就会检测出end-of-file状态。读取最后一个字符时,eofbit并未设立,但再一次试图读取字符时,就会导致eofbit和failbit同时被设立,因为读取操作也失败了。
(2)用来处理Streams状态的一些成员函数 表13.4 用于处理Streams状态的各个成员函数
如果某些标志被clear() 或 setstate() 设立,streams 便有可能抛出异常。如果对应的标志被设立起来,那么当那些标志的处置函数结束之际,stream会抛出异常。注意,我们必须明白地清除错误位。C 语言可以在“格式错误”发生之后仍然读入字符。例如虽然scanf()未能读入一个整数,我们仍能读入剩余字符,因此虽然读入操作失败,input stream 的状态依然ok。但C++不同:如果设置了failbit,除非显式予以清除,否则无法进行下一个操作。
请注意,被设立的位只是反映过去曾发生的事。如果某次操作后发现某个为被设立了,我们无法确定究竟是这一次或先前操作导致这个结果。因此如果想通过标志了解错误,操作前应先设立goodbit(如果尚未设立的话)。
如,下面这个例子检查failbit 是否设立。若是,则清除之:
// check whether failbit is set if (strm.rdstata() & std::ios::failbit) { std::cout << "failbit was set " << std::endl; // clear only failbit strm.clear(strm.rdstata() & ~std::ios::failbit); }
(3)Stream 状态与布尔条件测试 stream 定义了两个可用于布尔表达式的函数。如表13.5 可用于布尔表达式的Stream 操作符
以上技术的一个典型应用就是以循环读入对象并处理:
// as long as obj can be read while (std::cin >> obj) { // process obj (in this case, simply output it) std::cout << obj << std::endl; }
(4)Stream 的状态和异常 标准化之后的streams允许我们对任何一种状态标志进行定义:此一状态标志被设立时是否引发异常。这可由成员函数exceptions() 完成,如表13.6
如果调用带有唯一参数的exceptions(),那么一旦指定的那个标志被设立起来,立刻就会引发相应异常。如下:
// 下面这个例子要求stream对所有标志均抛出异常: // throw exceptions for all "errors" strm.exceptions(std::ios::eofbit | std::ios::failbit | std::ios::badbit);
13.5 标准I/O函数
(1)输入用的成员函数
如表13.7, Stream函数读取字符序列的性能
(2)输出用的成员函数
1. ostream& ostream::put(char c)
2. ostream& ostream::write(const char* str, streamsize count)
3. ostream& ostream::flush()
13.6 操控器(Manipulators)
如表13.8 定义于<istream> 或 <ostream> 中的操控器
操控器其实就是一个被I/O操作符调用的函数。如下:
ostream& ostream::operator << (ostream& (*op)(ostream&)) { // call the function passed as parameter with this stream as the argument return (*op)(*this); } std::cout << std::endl; // 这里的<< 分别以cout 和 end() 为操作数,从前述实作法可知,操作符 <<把它本身的 // 调用操作转换为一个以stream为参数的函数调用,直接使用下面表达式效果一样。 std::endl(std::cout)
13.7 格式化 如表13.9 所示 可以访问格式标志的成员函数
13.9 文件存取(File Access) (1)
stream 可用来存取文件。C++标志程序库提供了四个template classes,并定义了四个标准特化版本:
1. template class basic_ifstream<> 及其特化版本ifstream和wifstream,用来读取文件(是一种"input file stream")
2. template class basiic_ofstream<> 及其特化版本ofstream和wofstream,用来将数据写入文件(是一种"output file stream")
3. template class basic_fstream<> 及其特化版本fstream和wfstream,用于读写文件
4. template class basic_filebuf<> 及其特化版本filebuf和wfilebuf,被其它file stream classes 用来进行实际的字符读写工作。 这些classes和stream base classes的关系如图13.2
注意
1. file stream classes 都不以string 作为构造函数的参数型别。
2. 如果文件有可能在它被产生的范围(scope)之外被使用,我们应该从heap分配该文件对象,并且在不需要时删除之:
// 这时候就应当使用某种smart pointer class std::ofstream* filePtr = new std::ofstream("xyz") .... delete filePtr;
(2)文件标志 为了准确控制文件处理模式,class ios_base定义了一组标志,其型别都是openmode,这是类似fmtflags的一种位掩码型别(bit mask type),如表13.32 。
表13.33 展示"C++标志组合"和"C的文件开启函数fopen()所使用之接口字符串"间的关联。标志binary和ate的组合没有列出。设置binary相当于在接口字符串后加一个b,设置ate则相当于打开文件后立即跳至文件尾端。
表13.34 列出用来打开或关闭文件的一些成员函数
注意,处理过文件之后,必须调用clear() 以清除当时被设于文件尾端的状态标志。这是必要的,因为这个stream对象被多个文件共享。open()并不会清除任何状态标志,因此如果某个stream未处于良好状态,在关闭并重新打开之后,你还是必须调用clear()以取得一个良好状态。即使你透过它开启另一个文件,情况也一样。
// io/ cat1.cpp // header files for file I/O #include <fstream> #include <iostream> using namespace std; /* for all file names passed as command-line arguments * -open, print contents, and close file */ int main(int argc, char* argv[]) { ifstream file; // 注意,这个file stream 稍后将被多个文件共享 // for all command-line arguments for (int i = 1; i < argc; ++i) { // open file file.open(argv[i]); // write file contents to cout char c; while (file.get(c)) { cout.put(c); } // clear eofbit and failbit set due to end-of-file file.clear(); // close file file.close(); } }
(3)随机存取
表13.35 列出的成员函数,用来为C++ streams 确定读写位置。
表13.36 列出用于相对位置的常数
// seek to the beginning of the file file.seekg(0, std::ios::beg) ... // seek 20 character forward file.seekg(20, std::ios::cur); ... // seek 10 character before the end file.seekg(-10, std::ios::end); #include <iostream> #include <fstream> void printFileTwice(const char* filename) { // open file std::ifstream file(filename); // print contents the first time std::cout << file.rdbuf(); // seek to the beginning file.seekg(0); print contents the second time std::cout << file.rdbuf(); }
注意,file.rdbuf()被用来打印文件内容。此时是直接操作stream缓冲区,那并不会改变stream状态。如果透过stream接口函数(例如透过getline())打印file内容, 必须先调用clear()清除file的状态——在它能够被任何方式(任何函数)处理之前(包括改变读写位置),因为这些函数到达文件尾端时会设立ios::eofbit 和 ios::failbit。
(4)文件描述符(FIle Descriptors)
某些实作版本提供这种可能性:将一个stream附着到一个已开启的I/O通道。为了这么做,你必须以一个文件描述符将file stream 初始化。文件描述符是个整数,用来辨识某个开启的I/O通道。 有三个文件描述符是预先定义好的:
1. 0 代表标准输入通道
2. 1 代表标准输出通道
3. 2 代表标准错误信息通道
这些通道可能被连接至文件、控制台,其他行程(processes)或其他I/O设施。
13.10 连接Input Streams 和 Output Streams 常常会需要连接两个streams。例如你可能想在读取数据前确保屏幕上已经打印出提示文字;或者你可能希望对同一个stream读取和改写——这种情况主要发生在file stream身上。
(1)你可以把一个stream连接到一个output stream身上。这意味两者的缓冲区是同步的。 其具体作法是:output stream 将在另一个stream 执行输入或输出操作前先清空自己的缓冲区。也就是说对output stream 而言,其flush() 函数会被调用。表13.37 列出basic_ios定义的数个成员函数,它们用来将某个stream连接到另一个stream身上。每个stream 只能连接一个output stream,但你可以把一个output stream 连接到多个streams身上。
缺省情况下,标准input装置以下列方式连接到标准output装置上:
// predefined connections std::cin.tie(&std::cout); std::wcin.tie(&std::wcout);
这样就保证了在真正请求输入之前,一定会先清空output缓冲区。如下:
std::cout << "Please enter x :" ; std::cin >> x;
程序读取x之前会先隐式(implicitly)对cout调用函数flush()。
如果要删除两个stream间的连接,可传递0或NULL给tie()。例如:
// decouple cin from any output stream std::cin.tie(static_cast<std::ostream*>(0));
(2)以stream缓冲区完成"紧耦合"(Tight Coupling) 透过函数rdbuf(),可以使不同的streams共享一个缓冲区,从而实作streams的紧耦合。表13.38 所列函数使用与不同目的。 rdbuf()允许数个stream对象从同一个input通道读取信息,或者对同一个output通道写入信息,而不必困扰于I/O次序。由于I/O操作被施以缓冲措施,所以同时使用多个stream缓冲区是麻烦的。因为,对着同一个I/O通道使用不同的streams,而<font size="" color="">这些streams的缓冲区又各不相同</font>,意味I/O得传递给其它IO。basic_istream和basic_ostream各有构造函数接受一个stream缓冲区作为参数,以此将stream初始化。
#include <iostream> #include <fstream> using namespace std; int main() { // stream for hexadecimal standard output hexout.setf(ios::hex, ios::basefield); hexout.setf(ios::showbase); // switch between decimal and hexadecimal output hexout << "hexout :" << 177 << " "; // 输出hexout : 0xb1 cout << "cout :" << 177 << " "; // 输出cout : 177 hexout << "hexout :" << -49 << " "; // 输出hexout : 0xffffffcf cout << "cout :" << -49 << " "; // 输出cout : -49 hexout << endl; }
(3)注意,basic_stream和basic_ostream 的析构函数并不删除相应的stream缓冲区(毕竟该缓冲区并非由这些classes打开)。因此你可以传递一个指向stream缓冲区的指针,而不必使用stream reference。
#include <iostream> #include <fstream> void hexMultiplicationTable(std::streambuf* buffer, int num) { std::ostream hexout(buffer); hexout << std::hex << std::showbase; for (int i = 1; i <= num; ++i) { for (int j = 1; j <= 10; ++j) { hexout << i * j << ‘ ‘; } hexout << std::endl; } } int main() { using namespace std; int num = 5; cout << "We print " << num << " lines hexadecimal" << endl; hexMultiplicationTable(cout.rdbuf(), num); cout << "That was the output of " << num << "hexadecimal lines" << endl; }
这种方法的优点在于各式被修改后不必恢复其原先状态,因为格式乃是针对stream对象而不是针对stream缓冲区。缺点则是:stream对象的构造和析构会有更多额外开销。
(4)只有basic_istream和basic_ostream不销毁stream缓冲区。其它streams都会销毁它们最初分配的stream缓冲区,但它们不会销毁以rdbuf() 设置的缓冲区(stream对象--stream缓冲区)。
(5)将标准Streams重新定向(Redirectin) 只要透过"设置stream缓冲区"就可以重定向某个stream。如下:
// 使写入cout的信息不被送到标准output通道,而是被送到cout.txt std::ofstream file("cout.txt"); std::cout.rdbuf(file.rdbuf()); // 函数copyfmt() 可用来将某个stream的所有格式信息赋值给另一个stream对象 std::ofstream file("cout.txt") file.copyfmt(std::cout); std::cout.rdbuf(file.rdbuf());
注意,上述file是局部对象,将在上述程序区段结束时被销毁,相应的stream缓冲区也一并被销毁。这和标准的streams不同,因为通常file streams 在构造过程分配stream缓冲区,并于析构时销毁它们。所以本例中的cout不能再被用于写入(缓冲区被一并销毁了)。事实上它甚至无法在程序结束时被安全销毁。因此我们应该保留缓冲区并于事后恢复。如下面程序:
#include <iostream> #include <fstream> using namespace std; int main() { cout << " the first row " << endl; redirect(cout); cout << " the last row" << endl; } void redirect(ostream& strm) { ofstream file("redirect.txt"); // save output buffer of the stream streambuf* strm_buffer = strm.rdbuf(); // 保留旧缓冲区 file << "one row for the file " << endl; strm << "one row for the stream " << endl; // restore old output buffer strm.rdbuf(strm_buffer); } 程序输出: the first row the last row 文件redirect.txt的内容如下: one row for the file one row for the stream
(6)用于读写的Streams
运用同一个stream进行读写操作。如下:
std::fstream file("example.txt", std::ios::in | std::ios::out);
也可以采用两个不同的stream对象,一个用于读取,一个用于改写。如下:
std::ofstream out ("example.txt", ios::in | ios::out); std::istream in(out.rdbuf());
out的声明式会开启文件。in 的声明式使用out的stream缓冲区,从中读出数据。注意out必须同时允许读取和改写,如果仅能改写,则从它身上读取数据会导致未定义行为。另外还请注意,in并非隶属ifstream型别,只是隶属istream型别——文件被打开后,就有相应的stream缓冲区,此时唯一需要的只是另一个stream对象。
13.11 String Stream Classes
Stream classes机制也可以用来读取strings或将数据写入strings。String streams提供有缓冲区,但没有I/O通道;我们可以借着特殊函数来处理buffer/string。这项技术的一个主要用途就是以“独立于真实I/O装置以外”的方式来处理I/O。例如待输出文字的格式可以在string中设定,然后再将string发送到某个输出通道。
(1)针对string而定义的stream classes,与file stream 和 stream base classes 之间的关系一样。如图13.3描述了其间的继承体系。
表13.39 列出了String Streams的基本操作
一下程序说明如何使用string streams:
#include <iostream> #include <sstream> #include <bitset> using namespace std; int main() { ostringstream os; // decimal and hexadecimal value os << " dec :" << 15 << hex << " hex :" << 15 << endl; cout << os.str() << endl; // append floating value and bitset bitset<15> b(5789); os << " float :" << 4.67 << " bitset :" << b << endl; // overwrite with octal value os.seekp(0); os << " oct :" << oct << 15; cout << os.str() << endl; } 输出: dec : 15 hex : f oct : 17 hex : f float : 4.67 bitset : 001011010011101
处理string stream时,一个典型的错误是忘了使用str()提取字符串,而直接往stream输出。从编译器的角度看,这是合情合理的,因为此处的确存在一个转换操作可将参数转换为void*。于是stream的状态以地址形式被写入。
output string stream写入数据的一个典型应用就是,定义使用者自定义型别的output操作符。Input string stream主要用途是"按格式,从现有字符串中读取数据"。如:
int x; float f; std::string s = "3.7"; std::istringstream is(s); is >> x >> f;
注意:str(s) 返回的字符串是s的副本。
(2)char* Stream Classes
char* stream classes 只是为了向下兼容才被保留下来。这里只做一个简单介绍:
char* stream classes 是特别为字符型别char而定义的,包括:
class istrstream,class ostrstream,class strstream,class istrstreambuf,都在<strstream>头文件中被定义。
istrstream的初始化有两种做法,一是“以字符串终止符号‘\0’为结尾”的字符序列;一是将字符数量也当做参数传给构造函数。 使用char* stream作为字符串时必须十分小心,因为它和string stream不同,它并不负责存储字符序列所需的内存。利用成员函数str(),字符序列可以和其调用者一起共同管理内存。除非stream 被初始化为定长缓冲区(这么一来stream就不必负责)。
被冻结的char* stream可以恢复其正常状态。要做到这一点,必须调用成员函数freeze()并传入参数false。这么一来字符序列的所有权就转给了stream对象。这也是释放字符序列内存的唯一安全做法(调用者通过调用freeze(false)将所有权转给stream对象;stream对象通过调用str()将内存所有权转移给了调用者)。如下:
// 注意 // 1. 调用成员函数freeze()并传入参数false将内存所有权转给stream对象,是释放字符序列内存的唯一安全做法 // 2. 如果调用str(),stream便不能再修改字符序列。Stream会暗中调用成员函数freeze(),冻结字符序列 // 3. 成员函数str()不会附加字符终止符号(‘\0‘)。我们只有直接在stream内加上该特殊字符,才能结束字符序列。 float x; ... std::ostrstream buffer; foo(buffer.str()); buffer.freeze(false); buffer.seekp(0, ios::beg); buffer << "once more float x :" << x << std::endl; foo(buffer.str()); buffer.freeze(false);
(3)以辅助函数完成I/O
如果执行I/O操作符时需要存取对象的私有成员,标准操作符应该将实际任务委派给辅助的成员函数。这种技术允许具有多态性的读写函数,如下:
class Fraction { ... public: virtual void printOn(std::ostream& strm) const; // output virtual void scanFrom(std::istream& strm); // input ... }; std::ostream& operator<< (std::ostream& strm, const Fraction& f) { f.printOn(strm); return strm; } std::istream& operator>> (std::istream& strm, Fraction& f) { f.scanFrom(strm); return strm; }
如果你的classes并不打算被当做基类,那么你的I/O操作符可以设计为其friends。但是要注意,一旦用上了继承,这种方法(使用friends函数)就会有很大的局限性。friend函数不能成为虚函数,所以你的程序可能会调用错误的函数。例如,如果某个base class reference 实际指向一个derived class object,并被当做input操作符的参数,则被调用的将是base class的操作符。为了避免出现这种情况,derived classs不得不实作自己的I/O操作符。因此,先前的实作手法比friend函数手法通用得多。故此:
1. classes打算作为基类——>那么使用虚函数;
2. 不做基类——>friends函数;
3. 基类,使用friends函数——>有很大的局限性。
13.13 Stream Buffer Classes
一如前面所介绍的,stream并不负责实际读写操作,而是委托给stream buffers(缓冲区)完成。
(1)对于stream缓冲区的使用者来说,class basic_streambuf只是发送(sent)或提取(extracted)字符的地方。表13.41列出两个public函数,用来写入(到缓冲区)字符。
表13.42列出用来(从缓冲区)读取字符的public成员函数。
(2)Stream缓冲区迭代器(Buffer Iterators) 针对“无格式I/O”使用stream成员函数的另一种做法就是:采用stream缓冲区的迭代器类别(iterator classes)。这些类别所提供的迭代器1. 符合Input迭代器和Output迭代器的规格;2. 从stream缓冲区读取或写入单一字符。这么一来就能够将字符层级的I/O纳入C++标准程序库的算法管辖范围内了。 表13.44 列出Output Stream缓冲区迭代器的各项操作
例子如下:
// 以下程序片段使用ostreambuf_iterator 将字符串写入一个stream缓冲区 // create iterator for buffer of output stream cout std::ostreambuf_iterator<char> bufWriter(std::cout); std::string hello("hello, world\n"); std::copy(hello.begin(), hello.end(), bufWriter);
表13.45 列出 Input Stream 缓冲区迭代器的各项操作
注意:
1. 当两个stream缓冲区迭代器都是(或都不是)end-of-stream迭代器时,两者被视为相等(至于其output缓冲区是否相同,并不影响)。
2. 获得一个end-of-stream迭代器的可行方法是以default构造函数产生一个迭代器。此外,如果试图将迭代器移至stream尾部之外(也就是说如果sbumpc()返回traits_type::eof()),istreambuf_iterator就会成为一个end-of-stream迭代器。
// #include <iostream> #include <iterator> using namespace std; int main() { // input stream buffer iterator for in istreambuf_iterator<char> inpos(cin); // end-of-stream iterator, default 构造函数 istreambuf_iterator<char> endpos; // output stream buffer iterator for cout ostreambuf_iterator<char> outpos(cout); // while input iterator is valid while (inpos != endpos) { *outpos = *inpos; // assign its value to the output iterator ++inpos; ++outpos; } }