《C++标准程序库》笔记之四

本篇博客笔记顺序大体按照《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;
    }
}
时间: 2024-11-05 19:45:28

《C++标准程序库》笔记之四的相关文章

42. 蛤蟆的数据结构笔记之四十二图的遍历之广度优先

42. 蛤蟆的数据结构笔记之四十二图的遍历之广度优先 本篇名言:"生活真象这杯浓酒 ,不经三番五次的提炼呵 , 就不会这样一来可口 ! -- 郭小川" 继续看下广度优先的遍历,上篇我们看了深度遍历是每次一个节点的链表是走到底的. 欢迎转载,转载请标明出处:http://write.blog.csdn.net/postedit/47029275 1.  原理 首先,从图的某个顶点v0出发,访问了v0之后,依次访问与v0相邻的未被访问的顶点,然后分别从这些顶点出发,广度优先遍历,直至所有的

48. 蛤蟆的数据结构笔记之四十八的有向无环图的应用关键路径

48. 蛤蟆的数据结构笔记之四十八的有向无环图的应用关键路径 本篇名言:"富贵不淫贫贱乐 ,男儿到此是豪雄.-- 程颢" 这次来看下有向无环图的另一个应用关键路径. 欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/47135061 1.  关键路径 与AOV-网相对应的是AOE-网(Activity On Edge)即边表示活动的网.AOE-网是一个带权的有向无环图,其中,顶点表示事件(Event),弧表示活动,权表

49. 蛤蟆的数据结构笔记之四十九图的连通性问题

49. 蛤蟆的数据结构笔记之四十九图的连通性问题 本篇名言:"我们不得不饮食.睡眠.游惰.恋爱,也就是说,我们不得不接触生活中最甜蜜的事情:不过我们必须不屈服于这些事物 .....--约里奥?居里"     此篇就作为数据结构入门笔记的最后一篇吧. 欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/47135259 设图G=(V,E)是一个无向图,G的一个连通分支是一个最大的连通子图,即一个连通分支是不包含在任何更大的

46. 蛤蟆的数据结构笔记之四十六普里姆算法

46. 蛤蟆的数据结构笔记之四十六普里姆算法 本篇名言:"手莫伸 ,伸手必被捉.党与人民在监督 ,万目睽睽难逃脱.汝言惧捉手不伸 ,他道不伸能自觉 , 其实想伸不敢伸 ,人民咫尺手自缩.-- 陈毅" 连通图的生成树是一个极小的连通子图,它含有图中全部的顶点,但只有足以构成一棵树的n-1条边.所谓的最小成本,就是n个顶点,用n-1条边把一个连通图连接起来,并且使得权值的和最小.构造连通网的最小代价生成树,即最小生成树(Minimum Cost Spanning Tree). 找连通图的最

44. 蛤蟆的数据结构笔记之四十四弗洛伊德Floyd算法

44. 蛤蟆的数据结构笔记之四十四弗洛伊德Floyd算法 本篇名言:"希望是厄运的忠实的姐妹. --普希金" 我们继续来看下数据结构图中的一个算法,这个算法来自图灵奖得主. 1.  Floyd算法介绍 Floyd算法又称为插点法,是一种用于寻找给定的加权图中多源点之间最短路径的算法.该算法名称以创始人之一.1978年图灵奖获得者.斯坦福大学计算机科学系教授罗伯特·弗洛伊德命名.注意这个可不是心理学的那个弗洛伊德. 是解决任意两点间的最短路径的一种算法,可以正确处理有向图或负权的最短路径

41 蛤蟆的数据结构笔记之四十一图的遍历之深度优先

41  蛤蟆的数据结构笔记之四十一图的遍历之深度优先 本篇名言:"对于我来说 , 生命的意义在于设身处地替人着想 , 忧他人之忧 , 乐他人之乐. -- 爱因斯坦" 上篇我们实现了图的邻接多重表表示图,以及深度遍历和广度遍历的代码,这次我们先来看下图的深度遍历. 欢迎转载,转载请标明出处: 1.  原理 图遍历又称图的遍历,属于数据结构中的内容.指的是从图中的任一顶点出发,对图中的所有顶点访问一次且只访问一次.图的遍历操作和树的遍历操作功能相似.图的遍历是图的一种基本操作,图的许多其它

MapReduce剖析笔记之四:TaskTracker通过心跳机制获取任务的流程

上一节分析到了JobTracker把任务从队列里取出来并进行了初始化,所谓的初始化,主要是获取了Map.Reduce任务的数量,并统计了哪些DataNode所在的服务器可以处理哪些Split等等,将这些信息缓存起来,但还没有进行实质的分配.等待TaskTracker跟自己通信. TaskTracker一般运行于DataNode之上,下面是他的声明,可见,是一个线程类: /******************************************************* * TaskT

马哥Linux学习笔记之四——DNS

1.BIND:Berkeley Internet Name Domain DNS:Domian Name Service 域名解析 2. Http 3.PAM 插入式认证模块 4.SMTP/POP3/IMAP4:Mail Server 5.域名 www.baidu.com这是一个主机名(FQDN,Full Qualified Domain Name,完全限定域名),com是一个域名,baidu.com也是一个域名,域名是好多主机的集合. 域名解析起后面有一个数据库,解析就是一个查询的过程.域名解

Android笔记之四种launchMode

一.先来了解Task Task 顾名思义,任务,每一个Android应用在运行的时候,都会创建和维护一个属于自己的任务,而事实上,Task是一个包含栈结构的容器,该栈通常叫回退栈,用来保存当前所有Android应用中已经创建的窗口对象,通常我们看到的界面就是处于回退栈栈顶的窗口对象.当我们打开新的一个界面,那么之前的界面(窗口对象)就会压入栈内,让出栈顶位置给新来的界面(窗口对象):当关闭该窗口对象时,系统会首先将该对象弹出栈,并销毁该对象.当栈里最后一个窗口对象被弹出栈后,回退栈为空,这时候回

《Effective C++》 读书笔记之四 设计与申明

<Effective C++> 读书笔记之四 设计与申明 条款18:让接口容易被正确使用,不易被误用. 重点: 好的接口很容易被正确使用,不容易被误用.你应该在你的所有接口中努力达成这些性质. "促进正确使用"的办法包括接口的一致性,以及与内置类型的行为兼容. "阻止误用"的办法包括建立新类型.限制类型上的操作,束缚对象值,以及消除客户的资源管理责任. tr1::shared_ptr支持定制型删除器.这可防范DLL问题,可被用来自动解除互斥锁等等. 20