zoukankan      html  css  js  c++  java
  • 《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的初始化有两种做法,一是“以字符串终止符号‘’为结尾”的字符序列;一是将字符数量也当做参数传给构造函数。 使用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()不会附加字符终止符号('')。我们只有直接在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
    ");
    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;
        }
    }
  • 相关阅读:
    利用正則表達式排除特定字符串
    js面向对象编程:this究竟代表什么?
    js调用父级frame中的方法
    Cocos2d-x动画工具类
    BZOJ 2466 中山市选2009 树 高斯消元+暴力
    Android Intent Scheme URLs攻击
    XML基础(一)
    【Life】 Never Too Late, Just Do it Better!
    代理模式
    HDU--Elevator(水题)
  • 原文地址:https://www.cnblogs.com/yyxt/p/5060993.html
Copyright © 2011-2022 走看看