c++ 专题 1:IO stream


文章目录

  • 前言
  • 流的状态
  • 操控器的实现
  • 格式化
    • bool 的 IO 格式
    • 栏位宽度,填充字符,对齐
    • 正号和大写
    • 数值基数
    • 浮点数表示
    • 其他
  • 缓冲区
  • 性能
    • c 与 c++ 的流同步
    • 直接使用 stream 缓冲区
  • fstream
  • sstream
  • 参考


前言

所谓 stream 就是一条数据流,字符序列在其中“川流不息”。

c++ 中的 stream 是由某个 class 定义出来具有特定性质的对象。其中输出动作被抽象为数据流进 stream,输入动作被抽象为数据流出 stream。

c++ IO stream 的基本结构如下:
c++ 专题 1:IO stream_第1张图片

注意:

  • 不能拷贝或对 stream 对象赋值,因此 stream 作为形参/返回类型时要用引用的形式;
  • stream 不能声明 const(读写一个 IO 对象会改变其状态);

一些概念:

  • stream 定义了 4 个类型为 istream 和 ostream 的全局标准 stream 对象,cin,cout,cerr,clog,分别对应于标准 I/O 通道 ;

  • 流操作符:basic_ostream/basic_istream 将 >>/<< 定义为输入/输出操作符,

    • >>:按箭头方向将 stream 存储于第二实参;
    • <<:把第二实参按箭头方向发送到相应的 stream 去;
    • 两者都返回第一个实参,即输入输出 stream ;

    说明:stream 对所有基础类型均重载了输入/输出操作符,不包括 void 和 nullptr_t。可以通过重载类类型的 operator<<,扩展 stream 操作的类型;这也使得 c++ 的 IO 操作相对于 c 的 printf I/O 机制,程序不再需要指定输出的数据类型,只要针对不同类型进行重载,就可以保证编译器自动推断出正确的打印函数。

    示例:考虑如下表达式:

    cout<<x<<"times"<<y<<"is "<<x*y<< endl;
    

    operator<< 从左向右执行,因此先执行 cout<< x;此表达式返回第一个实参 cout 的引用,因此接下来执行 cout<<“times”。

  • 标志位:标志位用来表示流的相关信息,标志位被实现为一种位掩码,可以同 | 来组合标志位,以及 & 来检测标志为。其中包括:

    • 状态标志:ios_base 包含 4 个 iostate 类型的静态常量数据成员用于描述流的状态 badbit,failbit,eofbit 和 goodbit。当事件发生,相应的位被置位;相反,状态位都被清除时,说明 stream 一切正常。同时,ios_base 提供了一组成员函数用于检查,清除或设置相应的状态位;
    • 格式标志:ios_base 定义了 fmtflags 类型用来存储数据输入/输出时的格式;
    • 文件标志:ios_base 定义了 openmode 类型用于控制文件打开模式;
  • 操控器:操控器是专门用来操控 stream 的一种对象,用于刷新缓存或格式化输入/输出数据;如下一组基本操控器定义于 或 用于刷新缓存;其余的操控器用于格式化数据的输入/输出,定义于 ;

    操控器 说明
    endl 输出一个换行符,并清空缓存
    ends 输出一个终止符,并清空缓存
    flush 清空缓存
    ws 读入时忽略空格
  • 格式化:设置 stream 格式的方式有 2 种;

    成员函数 说明 操控器 说明
    setf(flags) 添加 flags,返回之前的 flags setiosflags(flags) 调用 etf(flags)
    setf(flags, grp) 添加 flags,并清除本组其余 flags,返回之前的 flags resetiosflags(grp) 调用 setf(0,grp)
    unsetf(flags) 只清除 flags
    flags() 返回 flags
    flags(flags) 将当前 flags 设置为参数指定的 flags,返回所有之前的 flags
    copyfmt(stream) 从 stream 中复制所有的格式定义

    额外的,对于某些格式标志还提供了专门的控制器;

    示例:

    cout.setf(ios::showpos | ios::uppercase); //添加 showpos,uppercase
    cout.setf(ios::hex, ios::basefield);      //保证仅设置 basefield 组中的 hex 位
    cout.unsetf(ios::uppercase);              //清除 uppercase
    
    cout << resetiosflags(ios::adjustfield)     //清除 adjustm 
    << setiosflags(ios::left);             //添加 left 
    

额外的:C++ 标准库并未提供 “运用文件描述符将一个 stream 关联到某个 I/O 通道” 的方式。

流的状态

  • goodbit 被定义为 0,设置 goodbit 并不是将哪个位置位,而是意味着将其他位被清除了;
  • 由于其静态属性,这些 flag 都只能反映最后一次操作的 stream 状态(甚至无法确定究竟是这一次或先前操作导致这个结果);
  • 同样由于其由于其静态属性,可以通过实例化对象访问 cin.goodbit 或直接访问 ios_base::badbit;
状态位 含义 函数 作用
ios_base::badbit 指出流已崩溃,流无法在使用 s.bad() badbit 置位,返回 true
ios_base::failbit 指出 io 操作失败,置位后,流还可以继续使用; s.fail() failbit 或 badbit 置位,返回 true
ios_base::eofbit 文件末尾,同时设置 failbit s.eof() eofbit 置位,返回 true
ios_base::goodbit 指出流未处于错误状态,此值保持为 0; s.good() 所有错误位均未被置位的情况下返回 true
s.clear(iostate) 清除所有状态位,设置状态位 flags
s.clear() 清除所有状态位
s.setstate(iostate) 设置状态位 flags
s.rdstate() 返回流的当前状态

示例:检查 failbit 是否设置,若设置则清除:

if(strm.rdstate() & ios::failbit){
cout <<"failbit was set"<< endl;
	strm.clear(strm.rdstate() & ~ios::failbit);  //仅清除 failbit 
}

进一步的,ios_base 重载了 operator bool() 用于在控制结构中反映流的状态:

成员函数 意义
operator bool() Stream 是否未出错(相当于 !fail() )
operator !() Stream 是否已出错(相当于 fail() )

示例:考虑表达式

if(std::cin>>x){ }  

其中表达式 std::cin>>x 会返回 cin,所以读入 x 后上述语句变为:

if(std::cin){ }  

此时的 cin 被用于条件判断,接着 cin 调用 operator bool,根据 stream 的状态返回 stream 是否发生错误。

操控器的实现

IO 操作符重载了以函数指针为参数的版本,

ostream& ostream::operator<<(ostream& (*op)(ostream&)){
  return(*op)(*this);
}

将操控器与 IO 操作符连用时,实际上就是将操控器作为实参传递给 IO 操作符,即操控器其实就是一个被 I/O 操作符调用的函数,该函数以流的引用作为形参并返回该流的引用;

template<typename charT, typename traits>
std::basic_ostream<charT, traits>&
std::endl(std::basic_ostream<charT,traits>& strm){  //endl 的实现
    strm.put(strm.widen('\n'));
	strm.flush();
	return strm;	
}

因此对于如下调用:

cout << endl;  //<< 分别以 cout 和 endl 为实参,
//等效为:
endl(cout)

格式化

bool 的 IO 格式

格式标志 操控器
boolalpha boolalpha/unboolalpha

说明:

  • 对于写:将 bool 类型以文本输出,否则以数字输出(默认);
  • 对于读:读入的字符串须为 true 或 false,否则读入必须是 1 或 0(默认);

栏位宽度,填充字符,对齐

成员函数 格式标志 操控器
width() / width(val) setw(val)
fill() / fill© setfill©
adjustfield:left/right(默认)/internal left/right/internal

说明:

  • width(val) :设置 val 为当前栏位宽度,并返回先前的栏位宽度;

  • fill©:定义 c 为当前的填充字符,并返回先前的填充字符;

  • left,right,internal 指定对齐方式;

  • 设置栏位宽度和填充字符,执行了任何格式化 IO 操作后,栏位宽度都会恢复为默认值;填充字符和位置调整方式不变;

正号和大写

格式标志 操控器
showpos showpos/noshowpos
uppercase uppercase/nouppercase

格式标志:

  • showpos:在正数前加正号;
  • uppercase:在数值中使用大写字母;
cout<<12345678.9<<endl;              //1.23457e+07
cout.setf(ios::showpos|ios::uppercase);
cout<<12345678.9<<endl;              //+1.23457E+07

数值基数

格式标志 操控器
basefield:oct/dec(默认)/hex oct/dec/hex
showbase showbase/noshowbase

说明:

  • basefield 用于设置进制读/写

  • 如果该组的多个标志位被设定,则采用十进制读写,因此设置某个 flag 时,先清除另一个;

  • 如果未设置任何标志位,读取的数值依据起始字符确定进制:以 0x 或 0X 起始的是十六进制,以 0 起始的是八进制,其余被视为十进制数字。

cout.unsetf(ios::dec);   //清除某个 flag,然后设置另一个:
cout.setf(ios::hex);

cout.setf(ios::hex,ios::basefield);  //设置某个flag,同时自动清理同组的其他flag:

cout <<127<<' '<<255<< endl;       //127 255
cout << hex<<127<<' '<<255<< endl; //7f ff

cout.setf(ios::showbase);
cout <<127<<' '<<255<< endl;     //0x7f 0xff

浮点数表示

成员函数 格式标志 grp:flags 操控器
precision()/precision(val) setprecision(val)
floatfield:fixed/scientific/None(默认) fixed/scientific/defaultfloat
showpoint showpoint/noshowpoint

说明:

  • precision(val):令 val 为新的浮点数精度,并返回原值;
  • floatfield 的默认 None 自动使用小数计数和科学计数重最合适的;
  • showpoint 总是显示小数点,并以 0 填充尾部;

c++ 专题 1:IO stream_第2张图片

其他

格式标志 操控器
skipws skipws/noskipws
unitbuf unitbuf/nounitbuf

说明:

  • skipws:调用>>读取数值时,自动跳过起始的空白字符;
  • unitbuf:每次输出后,刷清output缓冲区;

缓冲区

stream 按照职责分离的原则设计,basic_ios 派生类只处理数据的格式化,实际读/写由 basic_ios 派生类所维护 streambuf 完成。streambuf 被抽象为读/写时所使用的字符缓冲区,作为 stream 和物理设备的媒介,如将数据打印到控制台,首先数据复制到 stream 关联的缓冲区中,当缓冲区满时或刷新(flush)时再将将数据写入控制台。

streambuf 是一个虚基类,其子类 stringbuf 和 filebuf 可用于实例化,sstream 和 fstream 内部缓冲区的实例化对象就是这两个类型。

指定 stream 的缓冲区:

  • basic_istream 和 basic_ostream 重载了一个以 stream 缓冲区为参数的构造函数,可以在初始化 stream 时指定缓存区

    stringbuf sbuf;
    istream is(&sbuf);
    
  • stream 成员函数 rdbuf() 指定 stream 的缓冲区

    rdbuf(streambuf*)
    

    参数:

    • 无参数:返回指针,指向流缓冲区;
    • 指定 streambuf*:将 streambuf* 所指的缓冲区绑定到流上,并返回指针,指向之前的流缓冲区;

说明:通过指定 stream 的缓冲区,可以让多个 stream 写至相同的输出设备,或是读自和写至同一缓冲区,或是重定向;

注意:

  • basic_istream 和 basic_ostream 的析构函数不销毁 stream 缓冲区;
  • 其他 stream 的析构函数都会销毁它们最初分配的 stream 缓冲区,但它们不会销毁以 rdbuf() 设置的缓冲区。

功能:将输出流连接到流上,使两者的缓冲区保持同步,在流执行 IO 动作之前,输出流会先清空自己的缓冲区;

tie(ostream* strm)

参数:

  • 不指定参数:返回指针,指向流上连接的输出流;
  • 指定 ostream*:将 ostream* 指向的输出流连接到当前流,并返回指针,指向流之前连接的输出流;
  • 0/nullptr:用于解除连接;

说明:每个 stream 只能连接一个 output stream,但你可以把一个 output stream 连接到多个 stream 身上。

例子:将 cout 连接到 cin

cin.tie(&cout);
cout << "Please enter x:"; 
cin >> x;   //在读取 x 之前,对 cout 调用 flush()
cin.tie(nullptr);

cerr.tie(&cout); //也可以将一个输出流连接到另一个输出流,在对 cerr 写之前,先清空 cout 缓冲区:

性能

c 与 c++ 的流同步

c 的 scanf 与 printf 相比于 c++ 的 cin,cout,速度更快,其问题在于 c++ 为了保持对 c 的兼容,使得标准 stream 与 c 的标准 IO 保持同步, 然而同步导致了不必要的开销,同时限制了 stream 缓冲区的缓存行为。

解决:

  • 关闭同步(关闭同步之后,cin 与 scanf 的执行效率相差无几);
  • 默认情况下 cin 绑定的是 cout,即每次执行 << 的时候都要调用flush,进而增加 IO 负担,因此可以通过 tie(0) 解绑。
int main(){
    ios::sync_with_stdio(false);   //取消同步,必须在 IO 操作前调用
    cin.tie(0);
    cout.tie(0);
}

取消同步导致的问题:

  • 应避免同时使有 c++ 的 cin,cout 与 c 的 scanf 与 printf 。在同步条件下混合使用 c++ stream 与 c stream,IO 执行的顺序将得到保证。任何 c++ stream 缓冲区在写数据前,都会先刷新 c stream 缓冲区,反之亦然。但是取消同步后,混合使用 c++ stream 与 c stream 将得不到保证。
  • 取消同步意味着将“并发支持”取消(并发能力允许你在多线程中使用标准 stream 对象);

进一步的,为追求性能,应避免使用 flush,unitbuf,endl。

直接使用 stream 缓冲区

operator<> 重载了以 stream 缓冲区指针为参数的版本;通过访问底层 streambuf,跳过上层的数据格式化操作,直接进行数据的输入/输出,以此来提高输入/输出效率。

  • 将指向 stream 缓冲区的 pointer 传给 operator<<,便可以输出该 stream 内的所有输入;
  • 将指向 stream 缓冲区的 pointer 传给 operator>>,便可以将数据直接读进该 stream 缓冲区内。
int main(){
    //copy all standard input to standard output
	cout<< cin.rdbuf();
    cin >>noskipws >>cout.rdbuf();
}

fstream

定义了三个类型来支持文件 IO:

  • ifstream:从文件读数据;
  • ofstream:从文件写数据;
  • fstream:读写给定文件;

对文件进行读写,首先需要将文件与 fstream 对象进行绑定。在后续过程中,被打开的文件由一个 fstream 对象来表示 ,而对这个 fstream 对象所做的任何输入输出操作实际就是对该文件所做的操作。

fstream 支持文件名和文件标志作为参数初始化 class fstream 对象;用文件名初始化 class fstream 对象时,open() 会自动被调用(打开文件);当 fstream 对象被销毁时,close() 会被自动调用(关闭文件);

fstream fstrm;
fstream fstrm("file.txt");   
fstream fstrm("file.txt",ios::app|ios::binary); //创建文件流 fstrm,绑定 file.txt,指定文件处理模式,并打开文件

fstream 还提供了一组成员函数用于在后续过程中绑定文件或指定文件打开模式

函数 功能
fstrm.open(s)/fstrm.open(s,flag) 打开名名为 s 的文件,将文件与 fstrm 绑定
fstrm.close() 关闭 fstrm 绑定的文件
fstrm.is_open() fstrm 绑定的文件是否成功打开

注意:处理过文件之后(eofbit 被设置),必须调用 clear() 清除当时被设于文件尾端的状态标志。因为当 stream 对象绑定多个文件时,调用 open() 重新打开关闭的文件或是打开另一个文件并不会清除任何状态标志,而此时的 eofbit 可能已经被设置了;

int main(int argc, char* argv[]){
	ifstream file;
    for(int i=1;i<argc;++i){  //对所有文件都使用 file 打开
        file.open(argv[i]); 
        char c;
        while(file.get(c)){
        	cout.put(c);
        }
        file.clear();   //清除 eofbit 和 failbit
        file.close();    //关闭文件会将缓冲区中的数据刷新    
    }
}

文件标志

模式 功能 模式 功能
ios::in 以读方式打开 ios::out 以写方式打开,默认截断
ios::app 以 out 方式打开,并定位到文件末尾 ios::ate 打开文件后定位到文件末尾
ios::trunc 将先前的文件内容移除(需要指定 out) ios::binary 以二进制进行 IO
ios::nocreate 打开一个文件时,如果文件不存在,不创建文件 ios::noreplace 打开一个文件时,如果文件不存在,创建该文件

说明:

  • ifstream 默认以 in 打开,ofstream 默认以 out 打开;fstream 默认以 In 和 out 打开;默认值仅在文件标志参数为空的清空下有效,当制定了任何文件标志时,其不会与默认值进行组合。
  • binary flag 使得 stream 在读写文件时按照二进制文件的读写方式(忽略特殊字符的含义);

设置读/写位置

函数 说明
tellg()\tellp() 返回读/写位置
seekg(pos)、seekp(pos) 设置读/写的绝对位置
seekg(offset, rpos)、seekp(offset, rpos) 设置读/写的相对位置

说明:

  • tellg 返回的文件位置是 pos_type 或 streampos 类型的,但无论哪种类型都不是整数类型,因此无法使用整数作为位置;

  • 偏移值 offset 属于 off_type 或 streamoff 类型,而该类型是一个有符号的整数类型,因此对于偏移值可以使用整数;

  • 基准 rpos 属于 seekdir 类型,ios_base 中定义了 3 个 seekdir 常量表示偏移基准;

    c++ 专题 1:IO stream_第3张图片

注意:切换读写操作时,必须进行一个 seek 动作到达当前位置再接着执行 ;

ios::postype pos=file.tellg();   //读取当前读位置,等价的 streampos pos=file.tellg();
file.seekg(pos);                 //设置读位置

file.seekg(0, ios::beg);    //定位至文件开头  
file.seekg(20, ios::cur);   //定位至当前位置后 20 个字符
file.seekg(-10, ios::end);  //定位至文件末尾前 10 个字符

例子:读写文件

#include 
//写文件
ofstream outfile("io.txt",ios_base::app);   //第一步:定义 ofstream 对象
if(!outfile)                			//第二步:判断文件是否打开,或 if(!object.is_open())
	cerr <<"unable to save data!";
else
outfile << .....<<endl;                //第三步:写文件

//读文件
string word;
ifstream infile("io.txt");      //第一步:定义 ifstream 对象
if(!infile)                      //第二步:判断文件是否打开
	cerr <<"unable to save data!";
while(infile>>word)              //第三步:循环判断文件是否读到末尾,或 while(!object.eof())
	cout<<word<<'\n';            //第四步:读文件

//读写文件  
fstream iofile("io.txt",ios_base::app);  //读写同一个文件
if(!iofile)
	cerr...
else{
	iofile.seekg(0);   //将iofile重新定位到文件的起始处,追加模式打开,文件位置位于末尾,不重新定位,会直接结束 
while(){                                 
}

sstream

定义了三个类型来支持 string IO:

  • isstream:从 string 读数据;
  • osstream:从 string 写数据;
  • sstream:从 string 读写数据

class sstream 支持 class fstream 类似的初始化方式(流对象以及文件标志),使得对 string 像是一个 file 一样;额外的,sstream 支持 str() 用来将缓冲区内容作为一个 string 返回;

在这里插入图片描述

用途:

  • 格式化 string 中的数据;
  • 将数据转化为 string;

说明:

  • sstream 实际上是在底层维护了一个 string 类型的对象用来保存结果,s.str() 返回的正是底层 string 对象。可以使用 s.str(" “) 方法将底层 string 对象设置为空字符串(”");
  • 相较于 c 的格式转换函数(sprintf, itoa 等),sstream 采用 string 对象代替字符数组,使得在转换过程中避免了缓冲区溢出的风险以及格式转换失败的风险。因此使用更方便,更安全;

例子 1:sstream 的使用方法

ostringstream os;
os <<"dec:"<< 15 << hex << " hex:"<< 15 << endl;
cout << os.str() << endl;

os<<"float:"<<4.67<< endl;

os.seekp(0);             //覆写
os <<"oct:"<< oct<< 15;
cout <<os.str()<<endl;

输出:

c++ 专题 1:IO stream_第4张图片

先将一个十进制数和一个十六进制数写至 os,接下来增加一个浮点数;然后运用 seekp() 设置写的位置,覆写 os 起始处的内容,未被覆盖的字符依然有效。如果你要删除 stream 的现有内容,可利用函数 str(“”) 将内容赋予缓冲区

例子 2:以下例子表明对 sstream 绑定的 string 进行格式化,并不会影响原本的 string;

string s("value:");
ostringstream os(s, ios::out | ios::ate);    //关联一个 string s
os << 77 <<" "<< hex <<77<< endl;
cout << os.str();      //writes: value:77 4d
cout << s;             //writes: value:

参考

(1条消息) ❥关于C++之流状态(stream state)_itzyjr的博客-CSDN博客_c++流的状态

(1条消息) C++ 流(stream)总结_浩世轩宇的博客-CSDN博客_c++ stream

(1条消息) C++IO流详解_DR5200的博客-CSDN博客_c++ io流

(2条消息) C++文件输入输出流及标准I/O流类知识总结_不想不努力的菜菜的博客-CSDN博客_c++文件输入输出

你可能感兴趣的:(c++,c++,ios,开发语言)