C++Stream Classes详解

东阳的学习记录

文章目录

    • 基本概念
      • Stream 类别
      • 全局性的 Stream 对象
      • 操作符
      • 操纵器
    • 基本的Stream 类别和对象
      • 相关类别和继承体系
    • 头文件
    • Stream Buffer(串流缓冲区)
    • Streams 的状态(state)
      • iostate 常数
      • 用来处理stream状态的一些成员函数
      • Stream 状态与布尔条件测试
      • Stream 的状态与异常
    • 文件存取
      • 文件标志(File Flags)
    • 随机存取
    • 连接 Input Streams 和 Output Streams
      • 以tie()完成`松耦合`
      • 以 stream 缓冲区完成`紧耦合`
    • String Stream Classes
      • 使用str()管理缓冲区
    • char* Stream Classes
    • 自定义 I/O操作符
      • 以辅助函数完成 I/O

基本概念

Stream 类别

  1. class istream ;用来读数据
  2. class ostream :用来写数据

全局性的 Stream 对象

IOStream定义了数个型别为 istream 和 ostream的全局对象,它们对应于标准的 I/O 通道(channels):

  1. cin:标准输入通道,对应C stdin。操作系统通常将它和键盘连接
  2. cout:标准输出通道,对应C stdout。操作系统通常将它和监视器连接
  3. cerr:标准错误输出通道,对应C stderr。无缓冲装置
  4. clog:标准日志通道,默认情况将其连接到 cerr 所连接的装置

正常输出错误信息的输出加以分离,可以让程序员以不同的方式对待两种输出,例如将正常输出重定向到某个文件,同时令错误输出依旧打印到控制台。

操作符

operator <<operator >>

操纵器

操纵器是专门用来操纵 stream 的对象,常常只会改变输入或格式化输出的解释方式。用于 ostreams的操作器不会凭空创造数据, 用于istreams 的操纵符也不会吃掉任何输入

  • std::cout << std::endl:输出’\n’,并刷新output缓冲区
  • std::cout << std::ends:输出’\0’
  • std::cout << std::flush:刷新output缓冲区
  • std::cin >> std::ws >> strTemp: 读入并忽略空格

基本的Stream 类别和对象

相关类别和继承体系

C++Stream Classes详解_第1张图片

  • 基类 ios_base 定义了 stream classes的所有与字符型别及其相应字符特性(traits)无关的属性,主要包括状态和格式标志等组件和函数。
  • 由 ios_base 派生的 template class basic_ios<>, 定义出与字符型别及其相应字符特性(traits)相关的 stream class共同属性,其中包括 stream 所用的缓冲器。缓冲器所属类别派生自 template class basic_streambuf<>,其具现参数和 basic_ios一样。basic_streambuf<> 负责实际的读写操作.
  • template class basic_istream<>basic_ostream<>两者都虚继承自 basic_ostream,分别定义出读/写的对象。和basic_ios<>一样 ,他们以字符型别及其特性(traits)作为参数就行了,iostream / ostream都是以 char 实现的
  • template class basic_iostream<>派生自 (多重继承) basic_istream<>basic_ostream<>,用来定义即可以读,又可以写的对象。
  • template class basic_streambuf<>是IOStream程序库的核心,定义出所有可改写的stream可读取的 stream的接口。其他的stream class 均利用它进行实际的字符读写操作程序中为处理某些外部表达,必须从 basic_streambuf<>派生一些类别。

头文件

各个 stream class的定义分散于以下数个头文件中:

  • :内含 stream classes 的前置声明。这个文件是必要的,因为前置声明不能只是简单地写一句诸如 class ostream这样的声明就行
  • :内含 stream buffer基类(basic_streambuf<>)的定义。
  • : 内含仅支持输入的类别(basic_istream)和同时支持输入输出的类别(basic_iostream<>)的定义
  • :内含 basic_ostream<>的类别定义
  • :内含全局性的 stream对象(例如cin, cout)的定义

Stream Buffer(串流缓冲区)

操作符 <<>>可以直接用于读取或改写 stream buffer,这恐怕是运用 C++ I/O streams 来拷贝文件的最快方法

Streams 的状态(state)

Stream 维护着一种状态,标志 I/O 是否成功,并且能够指出不成功的原因。

iostate 常数

  • goodbit:值被定义为0,因此goodbit的设立意味着其它位均被清为0
  • failbit:如果某项操作未能完成,但 stream 仍大体 OK, 那么就设立这个位。这通常是因为读入格式错误
  • badbit:如果 stream 因不明原因而损坏或丢失数据,那就设立这个位。
  • eofbit:遇到 end-of-file,注意:eofbit 常常和 failbit同时被设立

用来处理stream状态的一些成员函数

  • god(): 若stream正确无误,返回 true
  • eof():若遭遇 end-of-file, 返回 ture
  • fail():若发生错误,返回 true
  • bad():若发生毁灭性错误,返回 true
  • rdstate():返回当前已设立的所有标志
  • clear():清除所有标志
  • clear(state):清除所有标志,然后设立 state 标志
  • setstate(state):加设 state 标志

在C++中,如果设立了 failbit,除非显示予以清除,否则无法进行下一个操作。请注意:被设立的位只是反映过去曾发生的事。如果某次操作后发现某个位被设立了,我们无法确定到底是这一次还是先前动作导致了这个结果。因此如果想要通过标志了解错误,操作前应先设立goodbit(如果尚未设立的话)。此外,清除标志之后各项操作可能得到不同的结果

Stream 状态与布尔条件测试

streams 定义了两个可用于布尔表达式的函数。

  1. operator void* ():转化成 void*常常是为了在表达式中读入对象并测试是否成功。
  2. operator !()
while (std::cin) { /.../ }
if (std::cin >> x) {
	// reading x was successful
}

Stream 的状态与异常

标准化之后的 stream 允许我们对任何一种状态标志进行定义:此一状态标志被设立时是否引发异常

  • exceptions(flags):设定会引发异常的标志
  • exceptions():返回引发异常的标志
// throw exceptions for all "errors"
strm.exceptions(std::ios::eofbit | std::ios::failbit | 
				std::ios::badbit);

文件存取

stream 可用来存取文件。C++标准程序库提供了四个 template class,并定义了四个标准特化版本:

  1. template class basic_ifstream<> 及其特化版本 ifstream 和 wifstream,用来读取文件;
  2. tenplate class basic_ofstream<> 及其特化版本 ofstream 和 wofstream,用来将数据写入文件
  3. tenplate class basic_fstream<> 及其特化版本 fstream 和 wfstream,用来读写文件
  4. template class basic_filebuf<> 及其特化版本 filebuf 和 wfilebuf,被其他的 file stream classes 用来进行实际的字符读写操作

继承关系如下图:
C++Stream Classes详解_第2张图片

文件标志(File Flags)

为了准确控制文件处理模式,class ios_base定义了一组标志,其型别是 openmode,这是类似 fmtflags 的一种位掩码型别(bit mask type)

  • in:打开,用于读取。文件必须存在
  • out:打开,用于改写。注意:清空而后改写
  • app:写入时始终添加于尾端。
  • ate:打开文件之后令读写位置移至文件尾端
  • trunc:将之前的文件内容移除
  • binary:不要替换特殊字符

请注意:若要打开一个写文件而不清除文件内容的话, 必须以 out | app 的模式打开

随机存取

  1. basic_istream<>:
    i. tellg():返回读取位置
    ii. seekg(pos) 设置绝对读取位置
    iii. seekg(offset, rpos) 设置相对读取位置
  2. basic_ostream<>:
    i. tellp():返回写入位置
    ii. seekp(pos):设置绝对写入位置
    iii. seekp(offset, rpos):设置相对写入位置

连接 Input Streams 和 Output Streams

以tie()完成松耦合

你可以把一个 stream 连接到一个 output stream 身上。这意味着两者的缓冲区是同步的。

  • tie():返回一个指向当前所连接的 output stream的指针
  • tie(ostream *strm):将 strm 所指的 output stream 连接到当前 stream 身上,并返回一个指针指向先前所连接的 output streams 上(如果有的话)
// predefined connections
std::cin.tie(&std::cout);
std::wcin.tie(&std::wcout);

// 程序在真正请求输入之前,一定会先清空 output 缓冲区
std::cout << "please enter x:" ;
std::cin >> x;

// 删除上面的连接
std::cin.tie(static_cast<std::ostream*>(0))

你也可以将一个output stream 连接到另一个 output stream 上。下面语句就保证在对着 error stream 写东西之前,先清空正常的 output 缓冲区

// tie cout to cerr
std::cerr.tie(&std::cout);

以 stream 缓冲区完成紧耦合

透过函数 rdbuf(),可以使不同的streams共享同一个缓冲区,从而实现紧耦合

  • rdbuf(): 返回一个指针,指向 stream 缓冲区
  • rdbuf(streambuf*):将参数所指的 stream 缓冲区安装到当前 stream 身上,并返回一个指针,指向先前安装的 stream 缓冲区
/*
 * rdbuf1.cpp
 *
 *  Created on: 2021年1月12日
 *      Author: san
 */

#include 
#include 
using namespace std;

int main()
{
	// stream for hexadecimal standard output
	ostream hexout(cout.rdbuf());
	hexout.setf(ios::hex, ios::basefield);
	hexout.setf(ios::showbase);

	// switch between decimal and hexadecimal output
	hexout << "hexout: " << 177 << " ";  // 输出 hexout: 0xb1
	cout << "cout: " << 177 << " ";
	hexout << endl;
}

String Stream Classes

  1. string 提供有缓冲区,但没有 I/O通道
  2. 独立于直接I/O以外的方式来处理I/O

使用str()管理缓冲区

  • str():将缓冲区内容当作一个string返回
  • str(string):将 string 设为缓冲区内容

char* Stream Classes

利用函数str(),字符序列可以和其调用者一起共同管理内存,除非 stream 被初始化为定长缓冲区,否则,必须遵守下面三个原则:

  1. 由于内存的拥有权转移给了调用者,所以如果 stream 没有被初始化为 定长缓冲区,那么字符序列将被释放。但我们无法确定内存是如何被分配的,在此情况下使用delete[]来释放字符序列并不安全。最安全的做法是调用成员函数 freeze()并给予参数 false,将内存传会stream
  2. 如果调用str(),stream 便不能再修改字符序列
  3. 成员函数 str()不会添加字符串终止符 \0。我们只有直接在 stream内加上该特殊符号,才能结束字符序列。这可以通过操纵器 ends完成。
float x;
...
std::ostrstream buffer;  //dynamic char* stream

// fill char* stream
buffer << "float x: << x << std::ends;

/* pass resulting C-string to foo()
- freezes the char* stream
*/
foo(buffer.str());

// unfreeze the char* stream
// 将内存传回 stream
buffer,freeze(false);

自定义 I/O操作符

自定义一个读取分数的 >>

// io/fraclin.hpp
#include 

inline std::istream &operator >> (std::istream &strm, Fraction &f)
{
	int n, d;
	strm >> n;
	strm.ignore();
	strm >> d;
	
	f = Frantion(n, d);
	
	return strm;

}

以辅助函数完成 I/O

就是传递一个函数指针作为 I/O 操作符的参数

还有缓存区 Classes等内容下一篇再讲

你可能感兴趣的:(C++)