所谓 stream 就是一条数据流,字符序列在其中“川流不息”。
c++ 中的 stream 是由某个 class 定义出来具有特定性质的对象。其中输出动作被抽象为数据流进 stream,输入动作被抽象为数据流出 stream。
注意:
一些概念:
stream 定义了 4 个类型为 istream 和 ostream 的全局标准 stream 对象,cin,cout,cerr,clog,分别对应于标准 I/O 通道 ;
流操作符:basic_ostream/basic_istream 将 >>/<< 定义为输入/输出操作符,
说明: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”。
标志位:标志位用来表示流的相关信息,标志位被实现为一种位掩码,可以同 | 来组合标志位,以及 & 来检测标志为。其中包括:
操控器:操控器是专门用来操控 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 通道” 的方式。
状态位 | 含义 | 函数 | 作用 |
---|---|---|---|
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);
格式标志 | 操控器 |
---|---|
boolalpha | boolalpha/unboolalpha |
说明:
成员函数 | 格式标志 | 操控器 |
---|---|---|
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 |
格式标志:
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 |
说明:
格式标志 | 操控器 |
---|---|
skipws | skipws/noskipws |
unitbuf | unitbuf/nounitbuf |
说明:
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*)
参数:
说明:通过指定 stream 的缓冲区,可以让多个 stream 写至相同的输出设备,或是读自和写至同一缓冲区,或是重定向;
注意:
功能:将输出流连接到流上,使两者的缓冲区保持同步,在流执行 IO 动作之前,输出流会先清空自己的缓冲区;
tie(ostream* strm)
参数:
说明:每个 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 的 scanf 与 printf 相比于 c++ 的 cin,cout,速度更快,其问题在于 c++ 为了保持对 c 的兼容,使得标准 stream 与 c 的标准 IO 保持同步, 然而同步导致了不必要的开销,同时限制了 stream 缓冲区的缓存行为。
解决:
int main(){
ios::sync_with_stdio(false); //取消同步,必须在 IO 操作前调用
cin.tie(0);
cout.tie(0);
}
取消同步导致的问题:
进一步的,为追求性能,应避免使用 flush,unitbuf,endl。
operator<>> 重载了以 stream 缓冲区指针为参数的版本;通过访问底层 streambuf,跳过上层的数据格式化操作,直接进行数据的输入/输出,以此来提高输入/输出效率。
int main(){
//copy all standard input to standard output
cout<< cin.rdbuf();
cin >>noskipws >>cout.rdbuf();
}
对文件进行读写,首先需要将文件与 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 | 打开一个文件时,如果文件不存在,创建该文件 |
说明:
设置读/写位置
函数 | 说明 |
---|---|
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 常量表示偏移基准;
注意:切换读写操作时,必须进行一个 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(){ }
class sstream 支持 class fstream 类似的初始化方式(流对象以及文件标志),使得对 string 像是一个 file 一样;额外的,sstream 支持 str() 用来将缓冲区内容作为一个 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;
输出:
先将一个十进制数和一个十六进制数写至 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++文件输入输出