C/C++开发,无可避免的IO输入/输出(篇一).设备层流IO处理

目录

一、从std::ostream说起

        1.1 自定义类类型的operator<<

        1.2 std::ostream具体是什么

        1.3 IO流的数据成员--std::streambuf

        1.4 std::ostream全局对象

二、C/C++标准流IO

        2.1 IO流的使用

        2.2 流IO继承体系

        2.3 流IO状态

        2.4 流IO的缓冲区刷新

扫描二维码关注公众号,回复: 14797108 查看本文章

        2.5 std::ostream类功能

三、演示源码补充


一、从std::ostream说起

        1.1 自定义类类型的operator<<

        在很多自定义类型中,都会涉及到针对该类型的输入输出函数实现,正如我们前一篇博文中定义的MyString自定义类型一样:

//test1.h
#include <ostream>
class MyString
{
public:
	//...其他函数
    friend std::ostream& operator<<(std::ostream& output,const MyString& obj//输出屏幕
private:
	char *m_data;
};
//test1.cpp
std::ostream& operator<<(std::ostream& output,const MyString& obj)
{
	output << std::string(obj.m_data);
	return output;
};
//main.cpp
#include "test1.h"
#include <iostream>

int main(int argc, char* argv[])
{
    MyString s1("hello world!");
    std::cout << s1 << "\n";
    std::clog << s1 << std::ends;
    std::cerr << s1 << std::endl;
    return 0;
}
//out log
hello world!
hello world! hello world!

        上述代码中,通过简单的operator<<重载定义,向std::ostream通过位移操作符输入内容,就能调用std::cout等直接将MyString对象输出显示到屏幕上,这里面都有那些门道呢。

        1.2 std::ostream具体是什么

        std::ostream本质上是标准库类模板std::basic_ostream的特化类型,即:

namespace std {
  template<class CharT, class Traits = char_traits<CharT>>
    class basic_ostream;
 
  using ostream  = basic_ostream<char>;
};

        这个前一篇博文讲述std::string等同于std::basic_string<char>是一样的风格,这是c/c++标准库实现标准类的常用套路。显然,MyString类的operator<<重载定义,就是通过位移操作符向std::ostream输入内容,就是相当于调用了以下函数:

std::basic_ostream<char>::operator<<(形参列表)

        std::basic_ostream类模板和std::basic_string类定义是类似的,只是没有后者那样需要支持内存分配器(Allocator):

//std::basic_ostream类定义
template< class CharT, class Traits = std::char_traits<CharT> > 
    class basic_ostream : virtual public std::basic_ios<CharT, Traits>

//std::basic_ostream<CharT,Traits>::basic_ostream类构造函数

/*构造 basic_ostream 对象,通过调用 basic_ios::init(sb) 赋初值给基类*/
explicit basic_ostream( std::basic_streambuf<CharT, Traits>* sb );

/*复制构造函数被删除。输出流不可复制*/
protected:
 basic_ostream( const basic_ostream& rhs ) = delete;//(C++11 起) 
/*
*移动构造函数用 basic_ios<CharT, Traits>::move(rhs) 从 rhs 移动所有 basic_ios ,
*除了 rdbuf() 到 *this 中。此移动构造函数受保护:它为可移动输出流类 std::basic_ofstream 
*和 std::basic_ostringstream 的移动构造函数所调用,它们知道如何正确地移动关联的流缓冲。
*/
protected:
 basic_ostream( basic_ostream&& rhs );//(C++11 起) 

        类模板 basic_ostream 提供字符流上的高层输出操作。受支持操作包含有格式输出(例如整数值)和无格式输出(例如生字符和字符数组)。此功能以 basic_streambuf 类所提供的接口实现,通过 basic_ios 基类访问。典型的实现中, basic_ostream 无非继承的数据成员。

        std::basic_ostream类模板虚继承了std::basic_ios类模板,类 std::basic_ios 提供设施,以对拥有 std::basic_streambuf 接口的对象赋予接口。数个 std::basic_ios 对象能指涉一个实际的 std::basic_streambuf 对象。

//定义于头文件 <ios>
template< class CharT, class Traits = std::char_traits<CharT> > 
    class basic_ios : public std::ios_base 

//构造函数,构造新的 basic_ios 对象。
/*默认构造函数。不初始化内部状态。必须在首次使用对象或析构函数前调用 init() ,否则行为未定义*/
protected:
 basic_ios();  

/*以调用init(sb) 初始化内部状态。设置关联流缓冲为 sb ,参数sb - 要关联的流缓冲 */
public:
explicit basic_ios( std::basic_streambuf<CharT,Traits>* sb );

/*
*C++11 前复制构造函数声明为 private: 且不定义,而在 C++11 中声明为被删除: 
*I/O 流非可复制构造 (CopyConstructible)
*/
private:
 basic_ios(const basic_ios&);//(C++11 前) 

public:
 basic_ios(const basic_ios& ) = delete;//(C++11 起) 

         std::basic_ios 类模板进一步继承了std::ios_base类,类 ios_base 是作为所有 I/O 流类的基类工作的多用途类。它维护数种数据:

  1. 状态信息:流状态标志;
  2. 控制信息:控制输入和输出序列格式化和感染的本地环境的标志;
  3. 私有存储:允许 long 和 void* 成员的有下标可扩展数据结构,它可以实现为二个任意长度的数组,或二元素结构体的单个数组,或另一容器;
  4. 回调:从 imbue() 、 copyfmt() 和 ~ios_base() 调用的任意数量用户定义函数。

        典型实现保有对应下列 fmtflags(格式化标志类型) 、 iostate(流状态类型) 、 openmode(流打开模式类型) 及 seekdir(寻位方向类型) 所有值的成员常量,维护当前精度、宽度、格式化标志、异常掩码、缓冲区错误状态、保有回调的可调大小容器、当前感染的 locale 、私有存储的成员变量及 xalloc() 所用的静态整数变量。

        大家是否觉得类 ios_base的表现和文件操作很近似呢。是的,std::fstream等文件流缓冲类型也是遥继承该类(后面章节再展开), I/O 流的管理本质上就是对文件的管理,只是这个文件是管道文件,与屏幕输出关联。类 ios_base实现了格式化、本地环境配置、可扩展数值管理、注册事件回调、流异常监测等功能。

        std::basic_ios 的直接实现仅储存以下成员(它们完全取决于模板形参,从而不能为 std::ios_base 的一部分):

  • 用于填充的字符
  • 绑定的流指针
  • 关联的流缓冲区指针

        因此,需要继承类自行定义存储数据成员。

        1.3 IO流的数据成员--std::streambuf

        std::basic_ios 类模本和其派生类类模板 basic_ostream 都通过内部构造或外部传入std::streambuf作为其成员,用于进行数据存储。std::streambuf也一样是特化类:

using std::streambuf = std::basic_streambuf<char>

        类 basic_streambuf 控制字符序列的输入与输出。它包含下列内容并提供到它们的访问:

-----get area(获取区)-----         -----put area(放置区)-----
          ↑↓                                  ↑↓
------------------stream(关联序列,设备实体)-------------------
  1. 受控制字符序列,又称为缓冲区,它可含有为输入操作缓冲的输入序列(又称为获取区),和/或为输出操作缓冲的输出序列(又称为放置区)。
  2. 关联字符序列,又称作源(对于输入)或池(对于输出)。它可以是通过 OS API 访问的实体(文件、 TCP 接头、串行端口、其他字符设备),或者可以是能转译成字符源或池的对象( std::vector 、数组、字符串字面量)。
//定义于头文件 <streambuf>
template< class CharT, class Traits = std::char_traits<CharT> > 
    class basic_streambuf; 

/*
*构造 basic_streambuf 对象,初始化六个成员( eback() 、 gptr() 、 egptr() 、 pbase() 、 
*pptr() 和 epptr() )为空指针值, locale 成员为 std::locale() ,
*构造时的全局 C++ 本地环境的副本。
*/
protected:  basic_streambuf();  

/*
*从 rhs 复制构造,初始化六个指针和 locale 对象为 rhs 所保有值的副本。
*注意这是浅复制:新构造的 basic_streambuf 的指针指向的字符数组与 rhs 的相同。
*/
protected:  basic_streambuf(const basic_streambuf& rhs);//(C++11 起) 

        从std::basic_istream定义可以看出, std::basic_ios 类模板和其派生类类模板 basic_ostream 的模板参数完全就是为其成员std::basic_istream准备的,关于流的显示输出处理也是std::basic_istream为主,但std::basic_istream是通过成员为输出流提供功能实现,这有点装饰设计模式的韵味。

        PS:两个构造函数均为受保护,而且仅为具体的 streambuf 类,如继承std::basic_streambuf 类的std::basic_filebuf 、 std::basic_stringbuf 或 std::strstreambuf (C++98 中弃用) 调用

        I/O 流对象 std::basic_istream 及 std::basic_ostream ,还有所有派生自它们的对象( std::ofstream 、 std::stringstream 等),都完全以 std::basic_streambuf 实现。

        受控制字符序列是 CharT 的数组,它在所有时候都表示子序列,或对着关联字符序列的“窗”。其状态以三个指针描述:

  1. 起始指针 ,始终指向缓冲的最低元素
  2. 下一位置指针 ,指向读或写的下个候选元素
  3. 终止指针 ,指向缓冲区尾后一个位置。

        basic_streambuf 对象可支持输入(该情况下起始、下一位置和终止指针所描述的区域被称为获取区)、输出(放置区),或同时输入与输出。在最后一种情况下,跟踪六个指针,它们可能全部指向同一数组的元素,或指向二个单独数组的元素。

  • 若放置区中下一位置指针小于终止指针,则写位置可用。下一位置指针可被解引用和赋值。
  • 若获取区中下一位置指针小于终止指针,则读位置可用。下一位置指针可被解引用和读取。
  • 若获取区中下一位置指针大于起始指针,则回放位置可用,而下一位置指针可以被自减并赋值,以将字符放回到获取区。

        受控制序列中的字符表示和编码可以异于关联序列中的字符表示,该情况下典型地用 std::codecvt 本地环境进行转换。常见的例子是通过 std::wfstream 对象访问 UTF-8 (或其他多字节编码)文件:受控制字符序列由 wchar_t 字符组成,但关联序列由字节组成。

        std::basic_streambuf 基类的典型实现只保有六个 CharT* 指针和一个 std::locale 副本作为数据成员。另外,实现可以保持缓存的 locale 平面,凡在调用 imbue() 时非法化它。具体的缓冲类,如 std::basic_filebuf 或 std::basic_stringbuf 派生自 std::basic_streambuf 。

        关于std::basic_streambuf 的成员变量及函数就不深入探讨了,实际项目开发中也不会深究这个,只知道它是std::ostream 将数据输出到屏幕的内部实现,将std::ostream 输入的数据放置放置区,然后内部自行将数据推送至管理的关联序列(通常是FILE句柄,IO管道)输出到命令窗口上。

        1.4 std::ostream全局对象

        标准库给std::ostream(std::basic_ostream<char>)实现类各个内置基本类型通过位移操作符输入到缓冲区的功能,用户自定义类型就需要自行重载operator<<来实现。

//std::basic_ostream<CharT,Traits>::operator<<
basic_ostream& operator<<( short value );
basic_ostream& operator<<( unsigned short value );
basic_ostream& operator<<( int value );
basic_ostream& operator<<( unsigned int value );
basic_ostream& operator<<( long value );
basic_ostream& operator<<( unsigned long value );
basic_ostream& operator<<( long long value );
basic_ostream& operator<<( unsigned long long value );// (C++11 起) 
basic_ostream& operator<<( float value ); 
basic_ostream& operator<<( double value );
basic_ostream& operator<<( long double value ); 
basic_ostream& operator<<( bool value );

basic_ostream& operator<<( const void* value );
basic_ostream& operator<<( const volatile void* value );//(C++23 起) 
basic_ostream& operator<<( std::nullptr_t );//(C++17 起) 

basic_ostream& operator<<( std::basic_streambuf<CharT, Traits>* sb );
basic_ostream& operator<<( std::ios_base& (*func)(std::ios_base&) );
basic_ostream& operator<<(
     std::basic_ios<CharT,Traits>& (*func)(std::basic_ios<CharT,Traits>&) );
basic_ostream& operator<<(std::basic_ostream<CharT
    ,Traits>& (*func)(std::basic_ostream<CharT,Traits>&) );

        标准库提供六个全局 basic_ostream 对象。

//定义于头文件 <iostream>
/*
*全局对象 std::cout 和 std::wcout 控制到实现定义类型流缓冲
*(导出自 std::streambuf )的输出,它与标准 C 输出流 stdout 关联。
*/
extern std::ostream cout;
extern std::wostream wcout; 

/*
*全局对象 std::cerr 和 std::wcerr 控制到实现定义类型
*(分别导出自 std::streambuf 和 std::wstreambuf )的流缓冲,与标准C错误输出流 stderr 关联。
*/
extern std::ostream cerr;
extern std::wostream wcerr;  
   
/*
*全局对象 std::clog 和 std::wclog 控制实现定义类型(导出自 std::streambuf )的流缓冲,
*与标准 C 输出流 stderr 关联,但不同于 std::cerr/std::wcerr ,不自动冲入这些流,
*且不自动与 cout tie() 。
*/
extern std::ostream clog;
extern std::wostream wclog;

        这六个全局basic_ostream 对象与标准 C 输出流是类似的

//(宏常量) ,定义于头文件 <cstdio>
stdout     与输出流关联的 FILE* 类型表达式
stderr     与错误输出流关联的 FILE* 类型表达式
//main.cpp
#include <cstdio>
#include <iostream>

int main(int argc, char* argv[])
{
    fprintf(stdout,"hello world!\n");
    std::cout << "hello world!\n";
    //与 cout 共享缓冲
    std::ostream local_os(std::cout.rdbuf());
    local_os <<  "hello world!\n";
    return 0;
}
//out log
hello world!
hello world!
hello world!

二、C/C++标准流IO

        2.1 IO流的使用

        在实践项目中,我们会涉及到多种 IO 标准库工具的使用,实现用户控制窗口的交互:

• istream(输入流)类型,提供输入操作。
• ostream(输出流)类型,提供输出操作。
• cin(发音为 see-in):读入标准输入的 istream 对象。
• cout(发音为 see-out):写到标准输出的 ostream 对象。
• cerr(发音为 see-err):输出标准错误的 ostream 对象。cerr 常用于程序错误信息。
• >> 操作符,用于从 istream 对象中读入输入。
• << 操作符,用于把输出写到 ostream 对象中。
• getline 函数,需要分别取 istream 类型和 string 类型的两个引用形参,其功能是从 istream 对象读取一个单词,然后写入 string 对象中。
......

        出于某些原因,标准库类型不允许做复制或赋值操作。

// std::ostream  cpy_os = local_os;    //error,不能赋值构造
// std::ostream  cpy_os(local_os);     //error,不能拷贝构造

        形参或返回类型也不能为流类型。如果需要传递或返回 IO对象,则必须传递或返回指向该对象的指针或引用:

std::ostream& print(std::ostream& os)
{
    //code
    return os;
};

        2.2 流IO继承体系

        基于流的输入/输出库围绕抽象的输入/输出设备组织。这些抽象设备允许相同代码处理对文件、内存流或随即进行任意操作(例如压缩)的自定义适配器设备的输入/输出。大多数已经被类模板化,故它们能被适配到任何标准字符类型。为最常用的基本字符类型( char 和 wchar_t )提供分离的 typedef 。以下列层次将类组织:

         整个流IO体系的基类是std::ios_base,前面也提到std::ios_base类负责维护当前精度、宽度、格式化标志、异常掩码、缓冲区错误状态、保有回调的可调大小容器、当前感染的 locale 、私有存储的成员变量及 xalloc() 所用的静态整数变量。所有派生类型无论是直接继承还是重载,都会实现这些功能。

        2.3 流IO状态

        IO 标准库管理一系列条件状态(std::ios_base::iostate)成员,用来标记给定的 IO 对象是否处于可用状态,或者碰到了哪种特定的错误。std::ios_base::iostate指定流状态标志。它是位掩码类型 (BitmaskType) ,定义下列常量:

goodbit     无错误 
badbit      不可恢复的流错误 
failbit     输入/输出操作失败(格式化或提取错误) 
eofbit      关联的输出序列已抵达文件尾 

//static constexpr iostate goodbit = 0;

        例如,现需要从屏幕读取一个整型数据:

int ival;
std::cin >> ival;

        如果在标准输入设备输入 字段,则 std::cin 在尝试将输入的字符串读为 int型数据失败后,会生成一个错误状态。类似地,如果输入文件结束符(end-of-file),std::cin 也会进入错误状态。而如果输入数值例如10,则成功读取,std::cin将处于正确的无错误状态。流必须处于无错误状态(goodbit==0),才能用于输入或输出。检测流是否用的最简单的方法是检查其真值:

    int ival = 0;
    if (std::cin)// ok to use cin, it is in a valid state
    {
        if(std::cin >> ival)
            std::cout << "ival =" << ival << "\n";
        else
           std::cout << "input is not int:" << std::cin.rdstate() << "\n"; 
    }
    char c = '\0';
    while (std::cin >> c)// ok: read operation successful ...
    {
        if(c=='q') break;
    }

        if 语句直接检查流的状态,而 while 语句则检测条件表达式返回的流,从而间接地检查了流的状态。如果成功输入,则条件检测为 true。所有流对象都包含一个条件状态成员,该成员由 setstate 和 clear 操作管理。这个状态成员为 iostate 类型,这是由各个 iostream 类分别定义的机器相关的整型。该状态成员以二进制位(bit)的形式使用。

ios_base::iostate             标志 basic_ios 访问器 
eofbit failbit badbit   good()    fail()    bad()     eof()     operator  bool operator! 
false  false   false    true      false     false     false     true      false 
false  false   true     false     true      true      false     false     true 
false  true    false    false     true      false     false     false     true 
false  true    true     false     true      true      false     false     true 
true   false   false    false     false     false     true      true      false 
true   false   true     false     true      true      true      false     true 
true   true    false    false     true      false     true      false     true 
true   true    true     false     true      true      true      false     true 

        badbit 标志着系统级的故障,如无法恢复的读写错误。如果出现了这类错误,则该流通常就不能再继续使用了。如果出现的是可恢复的错误,如在希望获得数值型数据时输入了字符,此时则设置 failbit 标志,这种导致设置 failbit的问题通常是可以修正的。eofbit 是在遇到文件结束符时设置的,此时同时还设置了 failbit。

        流的状态由 bad、fail、eof 和 good 操作函数提示。如果 bad、fail 或者 eof中的任意一个为 true,则检查流本身将显示该流处于错误状态。类似地,如果这三个条件没有一个为 true,则 good 操作将返回 true。 

        clear 和 setstate 操作用于改变条件成员的状态。clear 操作将条件重设为有效状态。在流的使用出现了问题并做出补救后,如果我们希望把流重设为有效状态,则可以调用 clear 操作。使用 setstate 操作可打开某个指定的条件,用于表示某个问题的发生。除了添加的标记状态,setstate 将保留其他已存在的状态变量不变。

void cin_test2()
{
    int ival;
    // read cin and test only for EOF; loop is executed even if there are other IO failures
    while (std::cin >> ival,!std::cin.eof()) {
        //输入流处理在任何错误上停止。然后能用 eof() 和 fail() 区别不同的错误条件。
        if (std::cin.bad())     // input stream is corrupted; bail out 
            throw std::runtime_error("IO stream corrupted");
        if (std::cin.fail()) {  // bad input
            std::cerr<< "bad data";                 // warn the user
            std::cin.clear(std::istream::failbit);  // reset the stream
            break;           // 
        }
        // ok to process ival        
    }
}

        这个循环不断读入 cin,直到到达文件结束符或者发生不可恢复的读取错误为止。在循环中,首先检查流是否已破坏。如果是的放,抛出异常并退出循环。如果输入无效,则输出警告并清除 failbit 状态。

        rdstate 成员函数返回一个 iostate 类型值,该值对应于流当前的整个条件状态:

istream::iostate old_state = std::cin.rdstate();

        由于iostate是采用多个状态二进制位标识,可以通过使用按位或(OR)操作符在一次调用中生成“传递两个或更多状态位”的值。按位或操作使用其操作数的二进制位模式产生一个整型数值。对于结果中的每一个二进制位,如果其值为 1,则该操作的两个操作数中至少有一个的对应二进制位是 1。例如:

//std::istream is
is.setstate(istream ::badbit | istream ::failbit);

        将对象 is 的 failbit 和 badbit 位同时打开。实参:is.badbit | is.failbit生成了一个值,其对应于 badbit 和 failbit 的位都打开了,也就是将这两个位都设置为 1,该值的其他位则都为 0。在调用 setstate 时,使用这个值来开启流条件状态成员中对应的 badbit 和 failbit 位。

        2.4 流IO的缓冲区刷新

        每个 IO 对象管理一个缓冲区,用于存储程序读写的数据。如有下面语句:

//std::ostream os
os << "please enter a value: ";

        系统将字符串字面值存储在与流 os 关联的缓冲区中。下面几种情况将导致缓冲区的内容被刷新,即写入到真实的输出设备或者文件:

  1. 程序正常结束。作为 main 返回工作的一部分,将清空所有输出缓冲区。
  2. 在一些不确定的时候,缓冲区可能已经满了,在这种情况下,缓冲区将会在写下一个值之前刷新。
  3. 用操纵符显式地刷新缓冲区,例如行结束符 endl。
  4. 在每次输出操作执行完后,用 unitbuf 操作符设置流的内部状态,从而清空缓冲区。
  5. 可将输出流与输入流关联(tie)起来。在这种情况下,在读输入流时将刷新其关联的输出缓冲区。

        这些用于流IO缓冲区刷新的输入/输出操纵符定义在<istream>、<ostream>中。

//定义于头文件 <ios>
unitbuf            控制是否每次操作后冲洗输出(函数) 
nounitbuf          控制是否每次操作后冲洗输出(函数) 
  
//定义于头文件 <istream>
ws      消耗空白符 (函数模板) 
  
//定义于头文件 <ostream>
ends              输出 '\0' (函数模板) 
flush             冲洗输出流 (函数模板) 
endl              输出 '\n' 并冲洗输出流 (函数模板) 
emit_on_flush     (C++20)  控制流的 basic_syncbuf 是否在冲入时发射(函数模板) 
noemit_on_flush   (C++20)  控制流的 basic_syncbuf 是否在冲入时发射(函数模板) 
flush_emit        (C++20)  冲入流,而若它使用 basic_syncbuf 则发射其内容 (函数模板) 

        常用的std::endl 操纵符,用于输出一个换行符并刷新缓冲区。std:: flush,用于刷新流,但不在输出中添加任何字符。比较少用的 std::ends,这个操纵符在缓冲区中插入空字符 null,然后后刷新它,记住它是有刷新功能的。

//main.cpp
    std::cout << "hello" << std::ends << "world!" << std::flush;//std::flush只冲刷不换行
    std::cout << "hello" << std::ends << "world!" << std::endl; //std::endl冲刷并换行
//out log
hello world!hello world!

如果需要刷新所有输出,最好使用 std::unitbuf 操纵符。这个操纵符在每次执行完写操作后都刷新流:

std::cout << std::unitbuf << "first" << " second" << std::nounitbuf;

        等价于:

std::cout << "first" << std::flush << " second" << std::flush;

        std::nounitbuf 操纵符将流恢复为使用正常的、由系统管理的缓冲区刷新方式。

        如果程序不正常结束,输出缓冲区将不会刷新。在尝试调试已崩溃的程序时,通常会根据最后的输出找出程序发生错误的区域。如果崩溃出现在某个特定的输出语句后面,则可知是在程序的这个位置之后出错。为了确保用户看到程序实际上处理的所有输出,最好的方法是保证所有的输出操作都显式地使用刷新操纵符(flush 或 endl),而非 '\n'。使用endl 则不必担心程序崩溃时输出是否悬而未决(即还留在缓冲区,未输出到设备中)。

        2.5 std::ostream类功能

        标准流IO分为划分为两大块,高层设备接口流IO和文件流IO,前者指的是std::ostream、std::istream、std::iostream,后者是std::fstream、std::ifstream、std::ofstream。文件流IO将在下一篇博文讲述,现在主要阐述设备接口流IO,三个IO类较常用的就是 std::ostream类,这在c++代码中随处可见其应用,尤其是在c++11标准后的程序代码中。

         std::ostream类定义于头文件 <ostream>,包含格式化、状态、寻位等功能函数。

成员类型          定义 
char_type        CharT 
traits_type      Traits ;若 Traits::char_type 不是 CharT 则程序非良构。 
int_type         Traits::int_type 
pos_type         Traits::pos_type 
off_type         Traits::off_type 

成员函数
(构造函数)        构造对象(公开成员函数) 
(析构函数)        [虚] 析构对象(虚公开成员函数) 
operator=        (C++11) 从另一 basic_ostream 移动赋值(受保护成员函数) 

有格式输出
operator<<       插入带格式数据(公开成员函数) 

无格式输出
put              插入字符(公开成员函数) 
write            插入字符块(公开成员函数) 

寻位
tellp            返回输出位置指示器(公开成员函数)
seekp            设置输出位置指示器(公开成员函数) 

杂项
flush            与底层存储设备同步(公开成员函数) 
swap            (C++11) 交换流对象,除了关联缓冲区(受保护成员函数) 

成员类
sentry          为输出操作实现流准备的基本逻辑(公开成员类) 

非成员函数        
operator<<(std::basic_ostream)  插入字符数据(函数) 

**继承自 std::basic_ios
成员类型         定义 
char_type       CharT 
traits_type     Traits 
int_type        Traits::int_type 
pos_type        Traits::pos_type 
off_type        Traits::off_type 

成员函数-状态函数
good  检查是否没有发生错误,例如是否可执行I/O操作(std::basic_ios<CharT,Traits>的公开成员函数) 
eof   检查是否到达了文件末尾(std::basic_ios<CharT,Traits> 的公开成员函数) 
fail  检查是否发生了可恢复的错误(std::basic_ios<CharT,Traits> 的公开成员函数) 
bad   检查是否已发生不可恢复的错误(std::basic_ios<CharT,Traits> 的公开成员函数) 
operator! 检查是否有错误发生(fail() 的同义词)(std::basic_ios<CharT,Traits> 的公开成员函数) 
operator void*   (C++11 前)检查是否没有发生错误(!fail()的同义词)std::basic_ios<CharT,Traits> 的公开成员函数) 
operator bool    (C++11 起)
rdstate      返回状态标志(std::basic_ios<CharT,Traits> 的公开成员函数) 
setstate     设置状态标志(std::basic_ios<CharT,Traits> 的公开成员函数) 
clear        修改状态标志(std::basic_ios<CharT,Traits> 的公开成员函数) 

格式化
copyfmt    复制格式化信息(std::basic_ios<CharT,Traits> 的公开成员函数) 
fill       管理填充字符(std::basic_ios<CharT,Traits> 的公开成员函数) 

杂项
exceptions    管理异常掩码(std::basic_ios<CharT,Traits> 的公开成员函数) 
imbue         设置本地环境(std::basic_ios<CharT,Traits> 的公开成员函数) 
rdbuf         管理相关的流缓冲区(std::basic_ios<CharT,Traits> 的公开成员函数) 
tie           管理绑定的流(std::basic_ios<CharT,Traits> 的公开成员函数) 
narrow        窄化字符(std::basic_ios<CharT,Traits> 的公开成员函数) 
widen         拓宽字符(std::basic_ios<CharT,Traits> 的公开成员函数) 

***继承自 std::ios_base
成员函数-格式化
flags      管理格式标志(std::ios_base 的公开成员函数) 
setf       设置特定格式标志(std::ios_base 的公开成员函数) 
unsetf     清除特定格式的标志(std::ios_base 的公开成员函数) 
precision  管理浮点操作的精度(std::ios_base 的公开成员函数) 
width      管理域的宽度(std::ios_base 的公开成员函数) 

本地环境
imbue      设置本地环境(std::ios_base 的公开成员函数) 
getloc     返回当前本地环境(std::ios_base 的公开成员函数) 

内部可扩展数组
xalloc     [静态]返回能安全用作 pword() 和 iword() 下标的程序范围内独有的整数(std::ios_base 的公开静态成员函数) 
iword      如果有必要的话,调整私有存储的大小,并且访问位于提供的下标的long元素(std::ios_base 的公开成员函数) 
pword      若需要则重置私有存储的大小,并访问位于指定下标的 void* 元素(std::ios_base 的公开成员函数) 

杂项
register_callback  注册事件回调函数(std::ios_base 的公开成员函数) 
sync_with_stdio    [静态]设置C++和C的IO库是否可以互操作(std::ios_base 的公开静态成员函数) 

成员类
failure      流异常(std::ios_base 的公开成员类) 
Init         初始化标准流对象(std::ios_base 的公开成员类) 

成员类型和常量
 
类型              解释 
openmode          流打开模式类型 
                  亦定义下列常量(typedef) :
                  app     每次写入前寻位到流结尾 
                  binary  以二进制模式打开 
                  in      为读打开 
                  out     为写打开 
                  trunc   在打开时舍弃流的内容 
                  ate     打开后立即寻位到流结尾 

fmtflags          格式化标志类型 
                  亦定义下列常量(typedef) :
                  dec         为整数 I/O 使用十进制底:见 std::dec 
                  oct         为整数 I/O 使用八进制底:见 std::oct 
                  hex         为整数 I/O 使用十六进制底:见 std::hex 
                  basefield dec|oct|hex 。适用于掩码运算 
                  left        左校正(添加填充字符到右):见 std::left 
                  right       右校正(添加填充字符到左):见 std::right 
                  internal    内部校正(添加填充字符到内部选定点):见 std::internal 
                  adjustfield left|right|internal 。适用于掩码运算 
                  scientific 用科学记数法生成浮点类型,或若与 fixed 组合则用十六进制记法:见 std::scientific 
                  fixed      用定点记法生成浮点类型,或若与 scientific 组合则用十六进制记法:见 std::fixed 
                  floatfield scientific|fixed 。适用于掩码运算 
                  boolalpha 以字母数字格式插入并释出 bool 类型:见 std::boolalpha 
                  showbase  生成为整数输出指示数字基底的前缀,货币 I/O 中要求现金指示器:见 std::showbase 
                  showpoint 无条件为浮点数输出生成小数点字符:见 std::showpoint 
                  showpos   为非负数值输出生成 + 字符,见 std::showpos 
                  skipws    在具体输入操作前跳过前导空白符:见 std::skipws 
                  unitbuf   在每次输出操作后冲入输出:见 std::unitbuf 
                  uppercase 在具体输出的输出操作中以大写等价替换小写字符:见 std::uppercase 

iostate           流状态类型 
                  亦定义下列常量(typedef) :
                  goodbit 无错误 
                  badbit  不可恢复的流错误 
                  failbit 输入/输出操作失败(格式化或提取错误) 
                  eofbit  关联的输出序列已抵达文件尾 

seekdir          寻位方向类型 
                 亦定义下列常量(typedef) : 
                 beg     流的开始 
                 end     流的结尾 
                 cur     流位置指示器的当前位置 

event           指定事件类型(枚举) 
event_callback  回调函数类型(typedef) 

        流ostream 只定义了最基本的操作:解引用和赋值,此外,不提供比较运算。

#include <sstream>
#include <fstream>
void add_words(std::streambuf* p)
{
    std::ostream buf(p); // buf 与 s 共享缓冲
    buf << " is the answer";
} // 调用 buf 的析构函数。 p 保持不受影响。

void ostream_test(void)
{
    // 错误:复制构造函数被删除
    //  std::ostream myout(std::cout);
 
    // OK :与 cout 共享缓冲
    std::ostream myout(std::cout.rdbuf());
 
    // 错误:移动构造函数受保护
    //  std::ostream s1(std::move(std::ostringstream() << 7.1));    
 
    // OK :通过导出类调用移动构造函数
    std::ostringstream s1(std::move(std::ostringstream() << 7.1)); 
    myout << s1.str() << '\n';
    //
    std::ostringstream s2;
    //  std::cout = s2;                             // 错误:复制赋值运算符被删除
    //  std::cout = std::move(s2);                  // 错误:移动赋值运算符为受保护
    s2 = std::move(std::ostringstream() << 42); // OK :通过导出类移动
    std::cout << s2.str() << '\n';
    //
    std::cout.put('a'); // 正常用法
    std::cout.put('\n');
 
    std::ofstream s3("/does/not/exist/");
    s3.clear(); // 假装流是好的
    std::cout << "Unformatted output: ";
    s3.put('c'); // 这将设置 badbit ,但非 failbit
    std::cout << " fail=" << bool(s3.rdstate() & s3.failbit);
    std::cout << " bad=" << s3.bad() << '\n';
    s3.clear();
    std::cout << "Formatted output:   ";
    s3 << 'c'; // 这将设置 badbit 和 failbit
    std::cout << " fail=" << bool(s3.rdstate() & s3.failbit);
    std::cout << " bad=" << s3.bad() << '\n';
    //
    int n = 0x41424344;
    std::cout.write(reinterpret_cast<char*>(&n), sizeof n) << '\n';
 
    char c[]="This is sample text.";
    std::cout.write(c, 4).write("!\n", 2);
    //
    std::ostringstream s4;
    s4 << 42;
    add_words(s4.rdbuf());
    s4 << ".";
    std::cout << s4.str() << '\n';
}
//out log
7.1
42
a
Unformatted output:  fail=0 bad=1
Formatted output:    fail=0 bad=1
DCBA
This!
42 is the answer.

        设备层的流IO介绍结束!

        感谢读友能耐心看到最后,本人文章都很长,知识点很细,可能会有所遗漏和失误,如果有不妥之处,烦请指出。如果内容对您有所触动,请点赞关注一下防止不迷路。

三、演示源码补充

        编译指令g++ main.cpp test*.cpp -o test.exe -std=c++11

        main.cpp

#include "test1.h"
#include <iostream>
#include <cstdio>

std::ostream& print(std::ostream& os)
{
    //code
    return os;
};

void cin_test1()
{
    int ival = 0;
    if (std::cin)// ok to use cin, it is in a valid state
    {
        if(std::cin >> ival)
            std::cout << "ival =" << ival << "\n";
        else
           std::cout << "input is not int:" << std::cin.rdstate() << "\n"; 
    }
    char c = '\0';
    while (std::cin >> c)// ok: read operation successful ...
    {
        if(c=='q') break;
    }
}

void cin_test2()
{
    int ival;
    // read cin and test only for EOF; loop is executed even if there are other IO failures
    while (std::cin >> ival,!std::cin.eof()) {
        //输入流处理在任何错误上停止。然后能用 eof() 和 fail() 区别不同的错误条件。
        if (std::cin.bad())     // input stream is corrupted; bail out 
            throw std::runtime_error("IO stream corrupted");
        if (std::cin.fail()) {  // bad input
            std::cerr<< "bad data\n";                 // warn the user
            std::cin.clear(std::istream::failbit);  // reset the stream
            break;           // 
        }
        // ok to process ival        
    }
}

#include <sstream>
#include <fstream>
void add_words(std::streambuf* p)
{
    std::ostream buf(p); // buf 与 s 共享缓冲
    buf << " is the answer";
} // 调用 buf 的析构函数。 p 保持不受影响。

void ostream_test(void)
{
    // 错误:复制构造函数被删除
    //  std::ostream myout(std::cout);
 
    // OK :与 cout 共享缓冲
    std::ostream myout(std::cout.rdbuf());
 
    // 错误:移动构造函数受保护
    //  std::ostream s1(std::move(std::ostringstream() << 7.1));    
 
    // OK :通过导出类调用移动构造函数
    std::ostringstream s1(std::move(std::ostringstream() << 7.1)); 
    myout << s1.str() << '\n';
    //
    std::ostringstream s2;
    //  std::cout = s2;                             // 错误:复制赋值运算符被删除
    //  std::cout = std::move(s2);                  // 错误:移动赋值运算符为受保护
    s2 = std::move(std::ostringstream() << 42); // OK :通过导出类移动
    std::cout << s2.str() << '\n';
    //
    std::cout.put('a'); // 正常用法
    std::cout.put('\n');
 
    std::ofstream s3("/does/not/exist/");
    s3.clear(); // 假装流是好的
    std::cout << "Unformatted output: ";
    s3.put('c'); // 这将设置 badbit ,但非 failbit
    std::cout << " fail=" << bool(s3.rdstate() & s3.failbit);
    std::cout << " bad=" << s3.bad() << '\n';
    s3.clear();
    std::cout << "Formatted output:   ";
    s3 << 'c'; // 这将设置 badbit 和 failbit
    std::cout << " fail=" << bool(s3.rdstate() & s3.failbit);
    std::cout << " bad=" << s3.bad() << '\n';
    //
    int n = 0x41424344;
    std::cout.write(reinterpret_cast<char*>(&n), sizeof n) << '\n';
 
    char c[]="This is sample text.";
    std::cout.write(c, 4).write("!\n", 2);
    //
    std::ostringstream s4;
    s4 << 42;
    add_words(s4.rdbuf());
    s4 << ".";
    std::cout << s4.str() << '\n';
}

int main(int argc, char* argv[])
{
    MyString s1("hello world!");
    std::cout << s1 << "\n";
    std::clog << s1 << std::ends;
    std::cerr << s1 << std::endl;
    //
    fprintf(stdout,"hello world!\n");
    std::cout << "hello world!\n";
    //与 cout 共享缓冲
    std::ostream local_os(std::cout.rdbuf());
    local_os <<  "hello world!\n";
    // std::ostream  cpy_os = local_os;    //error,不能赋值构造
    // std::ostream  cpy_os(local_os);     //error,不能拷贝构造
    //
    cin_test1();
    cin_test2();
    //
    std::cout << "hello" << std::ends << "world!" << std::flush;
    std::cout << "hello" << std::ends << "world!" << std::endl;
    //
    ostream_test();
    return 0;
}

        test1.h

#ifndef _TEST_1_H_
#define _TEST_1_H_

#include <ostream>
class MyString
{
public:
	MyString( const char *str );	        //普通构造函数
	virtual ~MyString( void );				//析构函数
	MyString& operator=(const MyString &other);	//赋值函数
	char* c_str(void) const;					//取值(取值)
    //输出屏幕
    friend std::ostream& operator<<(std::ostream& output,const MyString& obj);
private:
    void init(const char *str);
    void copy(const char *str);
private:
	char *m_data;
};

#endif //_TEST_1_H_

        test1.cpp

#include "test1.h"

#include <iostream>
#include <cstring>
//普通构造函数
MyString::MyString(const char *str)
{
    init(str);
}
// MyString 的析构函数
MyString::~MyString(void)
{
	delete [] m_data; // 或 delete m_data;
}

void MyString::init(const char *str)
{
	if(nullptr==str)
	{
		m_data = new char[1];	//对空字符串自动申请存放结束标志'\0'的空
		*m_data = '\0';
	}else{
		int length = strlen(str);
		m_data = new char[length+1];   // 分配内存
		// strcpy(m_data, str);		   //调用标准库的字符串拷贝函数
		std::memcpy(m_data,str,length);//调用标准库的字符数组拷贝函数
	}
};

void MyString::copy(const char *str)
{
    if(nullptr!=str)
    {
        delete [] m_data;				//释放原有的内存资源
        int length = strlen( str );
        m_data = new char[length+1];	//重新分配内存
        // strcpy( m_data, str);			//调用标准库的字符串拷贝函数
		std::memcpy(m_data,str,length); //调用标准库的字符数组拷贝函数
    }
};

//赋值函数
MyString &MyString::operator =( const MyString &other )//输入参数为const型
{
	if(this == &other)				//检查自赋值
		return *this;	
    copy(other.m_data);	
	return *this;					//返回本对象的引用
}

char* MyString::c_str(void) const
{
	return (char*)m_data;
};

std::ostream& operator<<(std::ostream& output,const MyString& obj)
{
	output << std::string(obj.m_data);
	return output;
};

猜你喜欢

转载自blog.csdn.net/py8105/article/details/129795505