c++为了更好的处理不同的种类的IO的操作,IO库中定义了庞大的类库来处理不同种类的IO操作,该类库组成如下图所示:
首先,我们先了解一下这个庞大的IO库各个类之间的关系。
OK,另外,我们要知道:
另外,我们再补充一下标准输入输出中的一些知识:
程序的每次输入都会建立一个输入缓冲区。而我们的cin就是直接从这个输入缓冲区获取数据的,如果我们的缓冲区中有数据残留(不为空时),那么cin对象直接从缓冲区读取数据而不是请求输入。另外,之前我们已经说过了cin是istream定义的一个对象,所以我们每次使用cin进行请求输入的时候,都是在调用istream这个对象的方法,其中istream这个对象处理输入的方法三种:cin>> 、cin.get() 、cin.getline().另外,cin是可以连续从缓冲区中读取想要的数据的,它是以(tab 、space、enter)中的一种作为分隔符,
示例展示:
#include
#include
using namespace std;
int main()
{
int num;
string s;
cin >> num >> s;
cout << num << " " << s << endl;
getline(cin,s);
cout << "string:" << s << endl;
cout << endl;
cout << "test" << endl;
cout << endl;
return 0;
}
输入:
[回车][回车][回车]12 abcd[回车]
输出:
ps:首先了getline函数是不会忽略开头的分割符的,我们开始保存到缓冲区的三个回车都被cin>>方法忽略了,而最后一个回车也在缓冲区中,但是没有被忽略,而是被geiline函数读了进来,所以在输出test之前会有两个换行。
int cin.get(); //不带参数时,返回值为整型,但遇到了文件的结束,返回EOF,其实就是:-1
istream & cin.get(char & s) //返回值为输入流。
istream & cin.get(char * s,streamsize n) //用于输入一个字符串,但是它只接受参数时c语言风格字符串风格的参数,即是不接受string类的参数,而且它默认是以换行作为结束符。
istream & cin.get(char * s,streamsize n,char end) //用于输入一个字符串,同样参数必须是c风格的,当时它是以:字符end,作为结束符。
(2)上面的streamsize在头文件iostream中的定义为long long型的数据类型的别名。所有版本的cin.get()方法是不会忽略开头的分隔符符号的,而且它和cin>>一样,遇到分隔符(tab、space 、enter)时,就结束该次方法的调用。最后的那个分隔符还是保存在缓冲区中。
示例展示:
#include
using namespace std;
int main()
{
char s;
char s1;
s1 = cin.get();
cin.get(s);
cout << (int)s1 << " " << (int)s << endl;
char str[5] = {NULL};
char end;
cin.get(str,5);
cin.get(end);
cout << str << " " << (int)end << endl;
cout << endl;
return 0;
}
输入:
[回车][回车]test[回车]
ok,我们从结果可以看出,我们开始输入的两个回车都被作为s1和s的初始值了,而我们字符串的最后一个回车也被end作为初始值了,从而可以看出cin.get()是不会忽略缓冲区中的分隔符的。
//这里我cin.get后面的两个方法一样,用于输入字符串。
//cin.getline不会将最后一个输入的结束符或者换行符残留在输入缓冲区中,
//而是一起输出到显示器中。默认遇到‘\n’结束输入。
istream & cin.getline(char *s ,streamsize n)
//这个方法就是结束方式不同,它是遇到了字符:end就结束输入。然后,输出长度为n-1范围内,end之前的所有字符。
istream & cin.getline(char * s,streamsize n,char end)
代码示例:
#include
#include
using namespace std;
int main()
{
char s[5];
char t;
cin.getline(s, 5);
cout << s << endl;;
cin >> t;
cout << t << endl;
cout << endl;
return 0;
}
输入:
new[回车]f
输出:
ok,从输出的结果,我们可以发现,cin.getline是把我们输入缓冲区的那个回车一并输出来了。
函数原型有两个重载形式:
istream& getline ( istream& is, string& str);//默认以换行符结束
istream& getline ( istream& is, string& str, char end);//以end字符结束
(2)注意,getline遇到结束符时,会将结束符一并读入指定的string中,再将结束符替换为空字符。因此,进行从键盘读取一行字符时,建议使用getline,较为安全。但是,最好还是要进行标准输入的安全检查,提高程序容错能力。另外,cin.getline()类似,但是cin.getline()属于istream流,而getline()属于string流,是不一样的两个函数。
代码示例:
#include
#include
using namespace std;
int main()
{
string str;
getline(cin, str);
cout << str;
cout << endl;
return 0;
}
gets是C中的库函数,在< stdio.h>申明,从标准输入设备读字符串,可以无限读取,不会判断上限,以回车结束或者EOF时停止读取,所以程序员应该确保buffer的空间足够大,以便在执行读操作时不发生溢出。
函数原型:char *gets_s( char *buffer );
代码示例:
#include
using namespace std;
int main()
{
char array[20] = { NULL };
gets_s(array);
cout << array << endl;
return 0;
}
重点内容补充:IO类的对象都是不可以拷贝的,所以如果我们需要使用IO类的对象作为函数的参数的时候或者返回值的时候,我们都要使用引用类型
因为我们在使用IO类的时候,总会出现一些错误,于是每个IO类都定义了一个iostate这个数据类型表示当前某个操作所处的状态。其中IO库定义了4个iostate类型的constexpr的值,它们分别是:
strm::badbit :首先,我们要说明strm代表的是IO类中的一种,例如istream、ostream、fstream等,babit表示系统级的错误,如果IO发生了不可修复的错误,badbit将被置位,而且流将无法再使用。
strm::failbit:这个会在流发生可修复的错误时,failbit将会被置位,例如:当我们本来是要求输入一个数值的却输入了一个字符等错误,这种问题可以被修正的,流还可以继续被使用。
strm::eofbit:当流达到了文件的结束的位置,eofbit和failbit这两个状态都会被置位。
strm::goodbit:当goodbit的值为1时,表示流未发生错误。
最后就是,只要badbit、eofbit、failbit任意一个状态被置位,那么一切以流状态为条件的语句都是返回false。例如:while(cin>>word),这条语句,只要上述三个中,有一个被置位,循环就结束。
IO库中还定义了一组函数,用于查询当前某种状态的值。具体函数如下:其中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<
#include
using namespace std;
int main()
{
int t;
while (cin>>t)
{
cout << "cin.good的值:"<<cin.good() << endl;
cout << "cin.eof的值:" << cin.eof() << endl;
cout << "cin.fail的值:" << cin.fail() << endl;
cout << "cin.bad的值:" << cin.bad() << endl;
}
cout << "结束后" << endl;
cout << "cin.good的值:" << cin.good() << endl;
cout << "cin.eof的值:" << cin.eof() << endl;
cout << "cin.fail的值:" << cin.fail() << endl;
cout << "cin.bad的值:" << cin.bad() << endl;
system("pause");
return 0;
}
IO类库还提供了3个函数来管理和设置流的状态:
s.clear(); // 将流s中所有条件状态复位,将流的状态设置为有效,调用good会返回true
s.clear(flags); // 根据给定的flags标志位,将流s中对应的条件状态复位,flags的类型为iostate
s.setstate(flags); // 根据给定的flags标志位,将流s中对应的条件状态置位。,flags的类型为iostate
s.rdstate(); // 返回一个iostate类型的值,对应流当前的状态。
注释:Windows下标准输入输入文件结束符为Ctrl+z,Linux为Ctrl+d。
最后就是,我们来完成一下《c++ primer 第五版》的281那个练习题,代码如下:
#include
#include
using namespace std;
istream & test(istream & s)
{
string str;
while ((s >> str).eof() == false)
{
cout << str << " " << endl;
}
cout << "结束" << endl;
s.clear();
return s;
}
int main()
{
//Windows下标准输入输入文件结束符为Ctrl+z,Linux为Ctrl+d。
test(cin);
system("pause");
return 0;
}
(1)输入缓冲区
观察如下代码:
#include
using namespace std;
int main()
{
char ch;
while (cin >> ch)
{
cout << ch;
}
system("pause");
return 0;
}
我们预想的是,我们输入一个字符就显示一个字符,但是实际上情况是如下图:
其实,输入字符立即回显是非缓冲或直接输入的一个形式,它表示你所键入的字符对正在等待的程序立即变为可用。相反,延迟回显是缓冲输入的例子,这种情况下你所键入的字符块被收集并存储在一个被称为缓冲区的临时存储区域中。按下回车键可使你输入的字符段对程序起作用。
缓冲输入一般常用在文本程序内,当你输入有错误时,就可以使用你的键盘更正修正错误。当最终按下回车键时,你就可以发送正确的输入。
而在一些交互性的游戏里需要非缓冲输入,如:游戏里你按下一个键时就要执行某个命令。
输入缓冲分类:
完全缓冲:缓冲区被充满时被清空(内容发送到其目的地)。这种类型的缓冲通常出现在文件输入中。
行缓冲:遇到一个换行字符时被清空缓冲区。键盘的输入是标准的行缓冲,因此按下回车键将清空缓冲区
(2)输出缓冲区
每个输出流都会管理一个缓冲区,用了保存程序读写的数据,这个和输入缓冲区是一个道理的,但是输出不一样,我们是希望在程序结束是,输出缓冲区中的内容都要被输出去,所以会有缓冲区刷新,在下面这几种情况会引起缓冲区的刷新(注意:如要程序异常终止,输出缓冲区是不会被刷新的。当一个程序崩溃后,它所输出的数据很可能停留在输出缓冲区中等待打印。所以最好在每个输出后加一个缓冲区刷新的操作):
程序正常结束,作为main函数的return操作的一部分,缓冲刷新被执行。
缓冲区满时,需要刷新缓冲,而后新的数据才能继续写入缓冲区。
我们可以使用操纵符endl来显式刷新缓冲区。
在每个输出之后,我们可以用操纵符unitbuf设置流的内部状态,来清空缓冲区。默认情况下,对cerr是设置unitbuf的,因此写到cerr的内容都是立即刷新的。
一个输出流被关联到另一个流。在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新,cin和cerr都关联到cout。因此读cin或写cerr会导致cout的缓冲区被刷新(cin立即回显的一个原因)。
IO库中除了endl可以刷新缓冲区外,ends和flush也会刷新缓冲区。只是它们会有一点差别:
cout << "hi!" << endl; // 输出 hi 和一个换行符,然后刷新缓冲区
cout << "hi!" << flush; // 输出hi,然后刷新缓冲区,不附加任何额外字符
cout << "hi!" << ends; // 输出hi和一个空字符。然后刷新缓冲区
unitbuf操作符,如果我们希望每次输出操作后都要刷新缓冲区,那么就可以使用:unitbuf,它就是告诉系统,以后每次进行输出操作后都要指向flush操作,我们之前提到的cerr就是设置了unitbuf的
cout << unitbuf; // 所有输出操作后都立即刷新缓冲区
// 任何输出都立即刷新,无缓冲
cout << nounitbuf; // 回到正常的缓冲方式