1、流的概念
"流"就是"流动",是物质从一处向还有一处流动的过程,比方我们能感知到的水流。C++的流是指信息从外部输入设备(如键盘和磁盘)向计算机内部(即内存)输入和从内存向外部输出设备(如显示器和磁盘)输出的过程,这样的输入输出过程被形象地比喻为"流"。
为了实现信息的内外流动,C++系统定义了I/O类库。当中的每个类都称作对应的流或流类,用以完毕某一方面的功能。依据一个流类定义的对象也时常被称为流。
通常标准输入或标准输出设备显示器称为标准流;外存磁盘上文件的输入输出称为文件流;对于内存中指定的字符串存储空间称为字符串流。
那么流的内容一般是什么呢?
流里的基本单位是字节,所以又称为字节流。字节流能够是ASCII字符、二进制数据、图形图像、音频视频等信息。文件和字符串也能够看成是有序的字节流,又称为文件流和字符串流。
2、IO类
C++的IO类库属于STL的一部分,在STL中定义了一个庞大的类库,它们的继承关系为下图:
管理标准的输入/输出流的类为:istream(输入)、ostream(输出)、iostream(输入输出),当中istream和ostream直接从ios中继承,iostream多重继承了istream和otream。
而cin是STL中定位的一个用于输入的istream对象,cout、cerr、clog是三个用于输出的ostream对象。当中cout对象也被称为标准输出。用于正常的输出。cerr用来输出警告和错误信息,由于被称为标准错误。而clog用来输出程序执行时的一般性信息。cerr和clog之间的不同之处在于cerr是不经过缓冲区直接向显示器输出有关信息。而clog则是先把信息放在缓冲区,缓冲区满后或遇上endl时向显示器输出。
管理文件流的类为:ifstream(文件输入)、ofstream(文件输出)和fstream(文件的输入/输出)。当中ifstream是从istream中继承的类,ofstream是从ostream中继承的类,fstream是从iostream继承的类。
管理字符串流的类为:istringstream(字符串输入)、ostringstream(字符串输出)和stringstream(字符串的输入/输出)。当中istringstream是从istream中继承的类,ostringstream是从ostream中继承的类,stringstream是从iostream继承的类。
3。<<和>>操作符
3.1 <<的使用方法
在istream输入流类中定义有对右移操作符>>重载的一组公用成员函数,函数的详细声明格式为:
istream& operator>> (istream& is, char& c);
istream& operator>> (istream& is, signed char& c);
istream& operator>> (istream& is, unsigned char& c);
istream& operator>> (istream& is, char* s);
istream& operator>> (istream& is, signed char* s);
istream& operator>> (istream& is, unsigned char* s);
因为右移操作符重载用于给变量输入数据的操作,所以又称为提取操作符,即从流中提取出数据赋给变量。
当系统运行cin>>variable操作时,将依据实參x的类型调用相应的提取操作符重载函数。把variable引用传送给相应的形參,接着从键盘的输入中读入一个值并赋给variable后。返回cin流,以便继续使用提取操作符为下一个变量输入数据。
当从键盘上输入数据时。仅仅有当输入完数据并按下回车键后,系统才把该行数据存入到键盘缓冲区。供cin流顺序读取给变量。还有,从键盘上输入的每一个数据之间必须用空格或回车符分开。由于cin为一个变量读入数据时是以空格或回车符作为其结束标志的。
当cin>>str_ptr操作中的str_ptr为字符指针类型时,则要求从键盘的输入中读取一个字符串,并把它赋值给str_ptr所指向的存储空间中,若str_ptr没有事先指向一个同意写入信息的存储空间,则无法完毕输入操作。另外从键盘上输入的字符串。其两边不能带有双引號定界符。若带有仅仅作为双引號字符看待。
对于输入的字符也是如此。不能带有单引號定界符。
3.2 >>的使用方法
在ostream输出流类中定义有对左移操作符<<重载的一组公用成员函数,函数的详细声明格式为:
istream& operator>> (bool& val);
istream& operator>> (short& val);
istream& operator>> (unsigned short& val);
istream& operator>> (int& val);
istream& operator>> (unsigned int& val);
istream& operator>> (long& val);
istream& operator>> (unsigned long& val);
istream& operator>> (long long& val);
istream& operator>> (unsigned long long& val);
istream& operator>> (float& val);
istream& operator>> (double& val);
istream& operator>> (long double& val);
istream& operator>> (void*& val);
除了与在istream流类中声明右移操作符重载函数给出的全部内置类型以外,还添加一个void* 类型,用于输出不论什么指针(但不能是字符指针。由于它将被作为字符串处理,即输出所指向存储空间中保存的一个字符串)的值。
因为左移操作符重载用于向流中输出表达式的值,所以又称为插入操作符。如当输出流是cout时。则就把表达式的值插入到显示器上。即输出到显示器显示出来。
当系统运行cout<<variable操作时,首先依据x值的类型调用相应的插入操作符重载函数。把variable的值按值传送给相应的形參,接着运行函数体,把variable的值(亦即形參的值)输出到显示器屏幕上,从当前屏幕光标位置起显示出来,然后返回cout流,以便继续使用插入操作符输出下一个表达式的值。
4,IO条件状态
4.1 查询流的状态
IO操作都有可能发生错误,一些错误是可恢复的。而其它发生错误在系统深处。已经超出了应用程序能够修正的范围。
IO类定义了一些函数和标志,能够帮助我们訪问和操纵流的条件状态。
首先表示一个流当前的状态的变量的类型为strm::iostate。当中strm是一种流类型,能够是iostream、fstream等。
比方,我们定义一个标准IO流状态:
iostream::iostate strm_state=iostream::goodbit;
IO库存定义了4个iostate类型的contexpr值,表示特定的位模式。这些值用来表示特定类型的IO条件,能够与位运算一起使用来一次性检測或设置多个标志位。
1)strm::badbit用来指定流已崩溃。
它表示系统级的错误。如不可恢复的读写错误。
通常情况下,一旦badbit被置位,流就无法再使用了。
2)strm::failbit用来指出一个IO操作失败了。
3)strm::eofbit用来指出流达了文件的结束。
在发生可恢复错误后,failbit被置位,如期望读取数值却读出一个字符错误。这样的问题通常能够修正,流还能够继续使用。假设到达文件结束位置。eofbit和failbit都会被置位。
4)strm::goodbit用来指出流未处于错误状态。
此值保证为零。
goodbit的值为0,表示流未错误发生。假设badbit、failbit和eofbit任一个置位,则检測流状态的条件会失败。
标准库还定义了一组函数来查询这些标志位的状态,假如s是一个流,那么:
s.eof() // 若流s的eofbit置位,则返回true
s.fail() // 若流s的failbit或badbit置位,则返回true
s.bad() // 若流s的badbit被置位。则返回true
s.good() // 若流s处于有效状态,则返回true
在实际我们在循环中推断流的状态是否有效时。都直接使用流对象本身。比方:while(cin>>variable){cout<<variable},在实际中都转换为了while((cin>>variable).good()){cout<<variable}。
4.2 管理条件状态
IO类库提供了3个函数来管理和设置流的状态:
s.clear(); // 将流s中全部条件状态复位。将流的状态设置为有效。调用good会返回true
s.clear(flags); // 依据给定的flags标志位,将流s中相应的条件状态复位
s.setstate(flags); // 依据给定的flags标志位。将流s中相应的条件状态置位。
s.rdstate(); // 返回一个iostate值,相应流当前的状态。
我们能够这样使用上面的这些成员函数。
iostream::iostate old_state = cin.rdstate(); // 记住cin当前的状态
cin.clear(); // 使用cin有效
process_input(cin); // 使用cin
cin.setstate(old_state); // 将cin置为原有状态
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit); // 下failbit和badbit复位,保持eofbit不变。
5。IO缓冲区
5.1 输入缓冲
我们先看一个简单的输入输出程序:
int main()
{
char ch;
while (cin >> ch && ch!='#')
{
cout << ch;
}
return 0;
}
程序的功能是。循环输入字符。然后把输入的字符显式出来,遇到#或cin流失败时结束,依照程序的表面来看。我们想要的效果是输入一个。显示一个,像这样rroonnyy#,红色代表的是显示的结果。而实际中我们的输出与输出却是这种:
ronny#abc [Enter]
ronny
输入字符马上回显是非缓冲或直接输入的一个形式。它表示你所键入的字符对正在等待的程序马上变为可用。相反。延迟回显是缓冲输入的样例,这样的情况下你所键入的字符块被收集并存储在一个被称为缓冲区的暂时存储区域中。
按下回车键可使你输入的字符段对程序起作用。
缓冲输入一般经常使用在文本程序内,当你输入有错误时,就能够使用你的键盘更正修正错误。当终于按下回车键时,你就能够发送正确的输入。
而在一些交互性的游戏里须要非缓冲输入,如:游戏里你按下一个键时就要运行某个命令。
缓冲分为两类:
1)全然缓冲:缓冲区被充满时被清空(内容发送到其目的地)。这样的类型的缓冲通常出如今文件输入中。
2)行缓冲:遇到一个换行字符时被清空缓冲区。
键盘的输入是标准的行缓冲。因此按下回车键将清空缓冲区。
5.2 输出缓冲
上面讲的是输入的缓冲,而C++中的输出也是存在缓冲的。
每一个输出流都管理一个缓冲区,用来保存程序读写的数据。比如。假设运行以下的代码
os<<”please enter a value:”;
文本串可能马上打印出来。但也有可能被操作系统保存在缓冲区中。随后再打印。有了缓冲机制。操作系统就能够将程序的多个输出操作组合成单一的系统级写操作。
因为设备的写操作可能非常耗时。同意操作系统将多个输出操作组合为单一的设备写操作能够带来非常大的性能提升。
导致缓冲刷新(即,数据真正写到输出设备或文件)的原因有非常多:
<1> 程序正常结束。作为main函数的return操作的一部分,缓冲刷新被运行。
<2> 缓冲区满时,须要刷新缓冲,而后新的数据才干继续写入缓冲区。
<3> 我们能够使用操纵符endl来显式刷新缓冲区。
<4> 在每一个输出之后,我们能够用操纵符unitbuf设置流的内部状态,来清空缓冲区。默认情况下,对cerr是设置unitbuf的。因此写到cerr的内容都是马上刷新的。
<5> 一个输出流被关联到还有一个流。
在这样的情况下,当读写被关联的流时。关联到的流的缓冲区会被刷新。cin和cerr都关联到cout。因此读cin或写cerr会导致cout的缓冲区被刷新。
除了endl能够完毕换行并刷新缓冲区外,IO库中还有两个类似的操纵符:flush和ends。flush刷新缓冲区。但不输出。不论什么额外的字符;ends向缓冲区插入一个空字符,然后刷新缓冲区。
cout << "hi!" << endl; // 输出 hi 和一个换行符,然后刷新缓冲区
cout << "hi!" << flush; // 输出hi,然后刷新缓冲区,不附加不论什么额外字符
cout << "hi!" << ends; // 输出hi和一个空字符。
然后刷新缓冲区
假设想每次输出操作后都刷新缓冲区,我们能够使用unitbuf操纵符。它告诉流在接下来的每次写操作后都进行一次flush操作。而nounitbuf操作符则重置流使其恢复使用正常的系统管理的缓冲区刷新机制。
cout << unitbuf; // 全部输出操作后都马上刷新缓冲区
// 不论什么输出都马上刷新。无缓冲
cout << nounitbuf; // 回到正常的缓冲方式
注意:如要程序异常终止。输出缓冲区是不会被刷新的。当一个程序崩溃后,它所输出的数据非常可能停留在输出缓冲区中等待打印。
我们能够将一个istream流关联到还有一个ostream,也能够将一个ostream流关联到还有一个ostream。
cin.tie(&cout); // 标准库已经将cin与cout关联在一起
// s.tie假设s关联到一个输出流,则返回指向这个流的指针。假设对象未关联到流,则返回空指针
ostream *old_tie = cin.tie(nullptr); // 将cin不再与其它流关联,同一时候old_tie指向cout
cin.tie(&cerr); // 读取cin会刷新cerr而不是cout
cin.tie(old_tie); // 重建cin和cout的正常关联
6,使用文件流
6.1 使用文件流对象
创建一个文件流对象时,我们能够提供文件名称,也可不提供文件名称,后面用open成员函数来打开文件。
string infile="../input.txt";
string outfile = "../output.txt";
ifstream in(infile); // 定义时打开文件
ofstream out;
out.open(outfile); // 用open打开文件
假设调用open失败,failbit会被置位。所以调用open时进行检測一般是一个好习惯。
假设用一个读文件流ifstream去打开一个不存在的文件,将导致读取失败,而假设用一写文件流ofstream去打开一个文件,假设文件不存在,则会创建这个文件。
一旦一个文件流已经被打开,它就保持与相应文件的关联。实际上。对一个已经打开的文件流调用open会失败,并会导致failbit被置位,随后的试图使用文件流的操作都会失败。
为了将文件流关联到另外一个文件,必须首先关闭已经关联的文件。
关闭一个流的关联文件能够用close成员函数来完毕。
6.2 文件模式
每一个流都有一个关联的文件模式,用来指出怎样使用文件,以下列出了文件模式和它们的含义:
in | 以读方式打开 |
out | 以写方式打开 |
app | 每次写操作均定位到文件末尾 |
ate | 打开文件后马上定位到文件末尾 |
trunc | 截断文件 |
binary | 以二进制方式进行IO |
用文件名称初始化一个流时或用open打开文件时都能够指定文件模式,但要注意以下几种限制:
<1>仅仅能够对ofstream或fstream对象设定out模式。
<2>仅仅能够对ifstream或fstream对象设定in模式。
<3>仅仅有out设定时才干够设定trunc模式。
<4>仅仅要trunc没有被设定,就能够设定app模式。在app模式下,即使没有显式指定out模式 ,文件也总是以输出方式被打开。
<5>默认情况下。即使我们没有指定trunc,以out模式打开的文件也会被截断。为了保留以out模式打开的文件的内容,我们必须同一时候指定app模式,这样仅仅会将数据追加写到文件末尾。或者同一时候指定in模式,即打开文件同一时候进行读写操作。
<6>ate和binary模式能够用于不论什么类型的文件流对象,且能够与其它不论什么文件模式组合使用。
以out模式打开文件会丢弃已有数据。所以阻止一个ofstream清空给定文件内容的方法是同一时候指定app模式。
// 在这几条语句中。file1都被截断
ofstream out("file1"); // 隐含以输出模式打开文件并截断文件
ofstream out2("file1", ofstream::out); // 隐含地截断文件
ofstream out3("file1", ofstream::out | ofstream::trunc); // 显式截断文件
// 为了保留文件内容,我们必须显式指定app
ofstream app("file2", ofstream::app); // 隐含为输出模式
ofstream app("file2", ofstream::app | ofstream::out);
7,使用字符流
sstream头文件定义了三个类型来支持内存IO,这些类型能够向string写入数据,从string读取数据。就像string是一个IO流一样。
7.1 使用istringstream
非常多时候我们须要逐行处理文本。并且须要对行内的单词进行单独分析。这时候使用istringstream是非常方便的。
比方。我们程序须要一次读取一行文本,然后将当中的单词分别取出保存在一个vector中。
string line,word;
while (getline(cin, line))
{
vector<string> wordList;
istringstream lineText(line);
while (lineText >> word)
{
wordList.push_back(word);
}
}
7.2 使用ostringstream
当我们逐步构造输出时。希望最后一起打印时,ostringstream是非常实用的,它能够帮我们完毕类似于itoa,ftoa这样的数字转字符串的功能。
int num1 = 42;
double pi = 3.1415926;
string str = "some numbers";
ostringstream formatted;
formatted << str << pi << num1;
cout << formatted.str() << endl;
当中str成员函数是stringstream有几个特有操作之中的一个。
string s;
stringstream strm(s);// 保存s的一个拷贝,此构造函数是explicit的。
strm.str(); // 返回strm所保存的string对象的拷贝。
strm.str(s); // 将s复制到strm中。返回void。
8。格式化输入输出
8.1 操纵符
标准库定义了一组操纵符用来改动流的状态,一个操纵符是一个函数或是一个对象,会影响流的状态。并能用作输入或输出运算符的运算对象。比方我们熟悉的endl,就是一个操纵符。
操纵符用于两大类输出控制:控制数值的输出形式以及控制补白的数量和位置,大多数改变格式状态的操纵符都是设置/复原成对的:一个操纵符用来将格式状态设置为一个新值,而还有一个用来将其复原,恢复为正常默认格式。
8.2 控制布尔值的格式
通过设置boolalpha能够将bool型变量的true输出为true或将false输出为false。能够设置noboolalpha来将内部状态恢复为默认格式。
// modify boolalpha flag
#include <iostream> // std::cout, std::boolalpha, std::noboolalpha
int main () {
bool b = true;
std::cout << std::boolalpha << b << '\n';
std::cout << std::noboolalpha << b << '\n';
return 0;
}
8.3 指定整形值的进制
默认情况是以十进制格式输出。我们能够设置不同的格式操纵符来改变输出整型值的进制。
oct:以八进制显示
hex:以十六进制显示
dec:以十进制显示
另外能够使用showbase操纵符来显式格式的前缀,8进制前有前导0,十六进制有前导0x。操纵符noshowbase恢复cout的状态,从而不再显示整型值的进制。
有时候我们须要将16进制输出为大写如0X FF。能够用操纵符uppercase和nouppercase来控制流输出的大写和小写状态。
cout << uppercase << showbase << hex << 20 << 1024
<< nouppercase << noshowbase << dec << endl;
8.4 控制浮点数格式
打印精度是通过precision成员或使用setprecision操纵符来改变。当中precision是一个重载函数,一个版本号接受int參数,将精度设置为此值,并返回旧精度值。另外一个版本号不接受參数。返回当前精度值。setprecision操纵符接受一个參数,用来设置精度。
用scientific用来指定科学记数法,fixed指定为定点十进制。hexfloat指定为十六进制的浮点数。defaultfloat将流恢复到默认的状态。
设置showpoint能够用来强制打印小数。
8.5 输出补白
setw:指定下一个数字或字符串的最小空间
left:表示左对齐输出。
right:表示右对齐输出,右对齐是默认格式。
internal:控制负数的符号的位置,它左对齐符号。右对齐值。用空格填满全部中间空间。
setfill:同意指定一个字符取代默认的空格来补白输出。
int i = -16;
double d = 3.14159;
cout << "i:" << setw(12) << i << '\n'
<< "d:" << setw(12) << d << '\n';
cout << left
<< "i:" << setw(12) << i << '\n'
<< "d:" << setw(12) << d << '\n';
cout << right
<< "i:" << setw(12) << i << '\n'
<< "d:" << setw(12) << d << '\n';
cout << internal
<< "i:" << setw(12) << i << '\n'
<< "d:" << setw(12) << d << '\n';
cout << setfill('#')
<< "i:" << setw(12) << i << '\n'
<< "d:" << setw(12) << d << '\n'
<< setfill(' ');
9,流的随机訪问
不同的流类型一般支持对相关流中数据的随机訪问。
能够又一次定位流,以便围绕跳过。首先读最后一行,再读第一行。以此类推。
标准库提供一对函数来定位(seek)给定位置并告诉(tell)相关流中的当前位置。
9.1 seek和tell函数
seekg:又一次定位输入流中的标记
tellg:返回输入流中标记的当前位置
seekp:又一次定位输出流中的标记
tellp:返回输出流中标记的当前位置
逻辑上,仅仅能在istream或者ifstream或者istringstream上使用g版本号。而且仅仅能在ostream类型或其派生类性ofstream或者ostringstream之上使用p版本号。
iostream对象,fstream或者stringstream对象对相关流既能够读也能够写,能够使用两个版本号的随意版本号。9.
9.2 仅仅有一个标记
尽管标准库区分输入和输入而有两个版本号,但它仅仅在文件里维持一个标记——没有可区分的读标记和写标记。
仅仅是试图在ifstream对象上调用tellp的时候,编译器将会给出错误提示。反之亦然。
使用既能够读又能写的fstream类型以及stringstream类型的时候,仅仅有一个保存数据的缓冲区和一个表示缓冲器中当前位置的标记,标准库将g版本号和p版本号都映射到这个标记。
9.3 普通iostream对象一般不同意随机訪问。
9.4 又一次定位标记
seekg(new_position);
seekp (new_position);
seekg( offset, dir);
seekp( offset, dir);
第一个版本号将当前位置切换到给定地点,第二个版本号接受一个偏移量以及从何处计算偏移的指示器。
9.5 訪问标记
tell函数返回的一个值,使用适当类的pos_type成员来保存。
10,一个实例
假定给定一个文件来读,我们将在文件的末尾写一个新行,改行包括了每一行开头的相对位置(程序不必写第一行的偏移量,由于它总是0)。
比如给定以下的文件,
abcd
efg
hi
j
这段程序应产生改动过的文件例如以下:
abcd
efg
hi
j
5 9 12 14
#include <iostream>
#include <fstream>
#include <string>
using std::fstream;
using std::cerr;
using std::endl;
using std::ifstream;
using std::ofstream;
using std::string;
using std::getline;
using std::cout;
//using namespace std;
int main()
{
fstream inOut("copyOut.txt",
fstream::ate | fstream::in | fstream::out); //用ate方式打开。会将文件的位置定位到文件末尾。
if( !inOut ) { cerr << "unable to open file" <<endl; return EXIT_FAILURE; } inOut.seekg(-1,fstream::end); //go to the last char if( inOut.peek() != 10) //if the last char of the file is not a newline,add it. { inOut.seekg(0,fstream::end); inOut.put('\n'); } inOut.seekg(0,fstream::end); ifstream::pos_type endMark = inOut.tellg(); //record the last position . inOut.seekg(0,fstream::beg); int cnt = 0; //accumulator for byte count string line; //hold each line of input while( inOut && inOut.tellg() != endMark && getline(inOut , line) ) { cnt += line.size() + 1; // add 1 to acount for the newline ifstream::pos_type mark = inOut.tellg(); inOut.seekp( 0, fstream::end); //set write marker to end inOut << cnt; if( mark != endMark) inOut << " "; inOut.seekg(mark); //restore read position } inOut.clear(); //clear flags in case we hit an error inOut.seekp(0 , fstream::end); //seek to end inOut << endl; //write a newline at end of file return 0; }