c++进阶---IO类的详细介绍(一)

来源:互联网 发布:java数据脱敏技术 编辑:程序博客网 时间:2024/05/16 13:41

前言
Men fail much oftener from want of perseverance than from want of talent.
Name:Willam
Time:2017/2/23

1、c++流的含义

c++的流分为输入流和输出流,所谓的输入流就是:信息从外部输入设备(如显示器、磁盘、键盘、鼠标等)向计算机内部(即内存)中。所谓的输出流就是:信息从内存向外部输出设备(如显示器、磁盘)输出的过程。c++中定义了特定的IO库来专门处理输入流和输出流。

  • 对系统指定的标准设备的输入输出,我们称为标准IO。(设备:输入如:键盘、鼠标,输出如:显示器)
  • 对外存磁盘(或者光盘、移动盘)文件为对像进行输入和输出。称为文件的IO,(文件)
  • 对内存指定空间的为对象进行输入和输出,称为串IO。(内存)

2、IO类

c++为了更好的处理不同的种类的IO的操作,它在STL的IO库中定义了一个庞大的类库来处理不同种类的IO操作,该类库组成如下图所示:

这里写图片描述

首先,我们先了解一下这个庞大的IO库各个类之间的关系。

  • ios是最基本的父类,其中istream类和ostream类都继承了ios类。
  • iostream类通过多重继承继承了istream类和ostream类。
  • ifstream、istringstream两个类都是继承了istream类,oftream、ostringstream两个类都继承了ostream类。

OK,另外,我们要知道:

  • 处理标准输入输出的类:istream(从流中读取数据,即处理输入)、ostream(向流中写入数据,即处理输出)、iostream(处理输入和输出),这些类都被定义在头文件:iostream中。
  • 处理文件的输入和输出的类:ifstream(从文件读取数据)、ofstream(向文件写入数据)、fstream(读写文件),这些类都被定义在头文件:fstream中
  • 处理string流的类:istringstream(从string中读取数据)、ostringstream(向string写入数据)、stringstream(读写string)。然后,这些类都是定义在头文件:sstream中。

另外,我们再补充一下标准输入输出中的一些知识:

  • cin是STL中定义的一个istream对象,它的作用是用于输入。
  • cout、cerr、clog是STL中定义的一个ostream对象,它们的作用是用于输出,其中cout是标准输出,cerr是用于输出错误或者警告信息,clog是用于输出程序的一般性信息。其中cerr不经过缓冲区,直接把错误信息输出到显示器中,clog则先把信息放在缓冲区中。如果我们输入一个endl,那么它将会把我们输出缓冲区的内容全部输出,并且输出一个换行。

3、cin的详解

程序的每次输入都会建立一个输入缓冲区。而我们的cin就是直接从这个输入缓冲区获取数据的,如果我们的缓冲区中有数据残留(不为空时),那么cin对象直接从缓冲区读取数据而不是请求输入。另外,之前我们已经说过了cin是istream定义的一个对象,所以我们每次使用cin进行请求输入的时候,都是在调用istream这个对象的方法,其中istream这个对象处理输入的方法三种:cin>> 、cin.get() 、cin.getline().另外,cin是可以连续从缓冲区中读取想要的数据的,它是以(tab 、space、enter)中的一种作为分隔符,

  • cin>>方法的介绍
    注意事项:
    (1)cin>>,其实是调用类的:istream & operator >>方法,它很多种重载的版本,分别用于处理字符、浮点数、整型等数据类型的输入。
    (2)cin>>,这个方法遇到了分割符(tab 、space、enter)时,就返回结束该次cin>>方法的调用。然后,如果我们调用cin>>方法时,从缓冲区读取数据,如果开头的数据是分隔符,那么直接把分割符忽略并且清除,直至第一个字符不是分隔符时才开始输入。

示例展示:

#include<iostream>#include<string>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之前会有两个换行。

  • cin.get()方法介绍
    注意事项:
    (1)cin.get()它有四种比较常见的重载格式:
int cin.get();    //不带参数时,返回值为整型,但遇到了文件的结束,返回EOF,其实就是:-1istream & 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<iostream>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.getline()方法的介绍
    注意事项:
    (1)cin.getline()方法重要的几个重载版本
//这里我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<iostream>#include<string>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是把我们输入缓冲区的那个回车一并输出来了。

  • 补充:getline函数的介绍(用于输入一行时使用,接受空格的输入)
    (1):C++中定义了一个在std名字空间的全局函数getline,因为这个getline函数的参数使用了string字符串,所以声明在了string头文件中了。
    getline利用cin可以从标准输入设备键盘读取一行,当遇到如下三种情况会结束读操作:1)到文件结束,2)遇到函数的定界符,3)输入达到最大限度。

函数原型有两个重载形式:

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<iostream>#include<string>using namespace std;int main(){    string str;    getline(cin, str);    cout << str;    cout << endl;    return 0;}

输入:
i am a student;
输出:
这里写图片描述

  • gets_s 函数的介绍(一般不要用它好了,c中定义的语法),原来是gets,但是在新的vs2015中已经没有gets了。只有gets_s

gets是C中的库函数,在< stdio.h>申明,从标准输入设备读字符串,可以无限读取,不会判断上限,以回车结束或者EOF时停止读取,所以程序员应该确保buffer的空间足够大,以便在执行读操作时不发生溢出。
函数原型:char *gets_s( char *buffer );

代码示例:

#include<iostream>using namespace std;int main(){    char array[20] = { NULL };    gets_s(array);    cout << array << endl;    return 0;}

输入:
new find
输出:
这里写图片描述

重点内容补充:IO类的对象都是不可以拷贝的,所以如果我们需要使用IO类的对象作为函数的参数的时候或者返回值的时候,我们都要使用引用类型

4、条件状态

因为我们在使用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的值为0时,表示流未发生错误。

最后就是,只要badbit、eofbit、goodbit任意一个状态被置位,那么一切以流状态为条件的语句都是返回false。例如:while(cin>>word),这条语句,只要上述三个中,有一个被置位,循环就结束。

IO库中还定义了一组函数,用于查询当前某种状态的值。具体函数如下:其中s为一个流的对象。

s.eof() // 若流s的eofbit置位,则返回trues.fail() // 若流s的failbit或badbit置位,则返回trues.bad() // 若流s的badbit被置位,则返回trues.good() // 若流s处于有效状态,则返回true

在实际我们在循环中判断流的状态是否有效时,都直接使用流对象本身,比如:while(cin>>variable){cout<

#include<iostream>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;}

输入:1[空格]2[空格]d[回车]
输出:
这里写图片描述

所以,从输出结果,我们可以很直观的看出,fail的值由于failbit被置位,所以返回为true。循环由于good的返回false而结束 。

IO类库还提供了3个函数来管理和设置流的状态:

s.clear(); // 将流s中所有条件状态复位,将流的状态设置为有效,调用good会返回trues.clear(flags); // 根据给定的flags标志位,将流s中对应的条件状态复位,flags的类型为iostates.setstate(flags); // 根据给定的flags标志位,将流s中对应的条件状态置位。,flags的类型为iostates.rdstate(); // 返回一个iostate类型的值,对应流当前的状态。

注释:Windows下标准输入输入文件结束符为Ctrl+z,Linux为Ctrl+d。

最后就是,我们来完成一下《c++ primer 第五版》的281那个练习题,代码如下:

#include<iostream>#include<string>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;}

测试结果:
这里写图片描述

5、缓冲区详解

(1)输入缓冲区

观察如下代码:

#include<iostream>using namespace std;int main(){    char ch;    while (cin >> ch)    {        cout << ch;    }    system("pause");    return 0;}

我们预想的是,我们输入一个字符就显示一个字符,但是实际上情况是如下图:
这里写图片描述

其实,输入字符立即回显是非缓冲或直接输入的一个形式,它表示你所键入的字符对正在等待的程序立即变为可用。相反,延迟回显是缓冲输入的例子,这种情况下你所键入的字符块被收集并存储在一个被称为缓冲区的临时存储区域中。按下回车键可使你输入的字符段对程序起作用。

  • 缓冲输入一般常用在文本程序内,当你输入有错误时,就可以使用你的键盘更正修正错误。当最终按下回车键时,你就可以发送正确的输入。

  • 而在一些交互性的游戏里需要非缓冲输入,如:游戏里你按下一个键时就要执行某个命令。

输入缓冲分类:

  1. 完全缓冲:缓冲区被充满时被清空(内容发送到其目的地)。这种类型的缓冲通常出现在文件输入中。

  2. 行缓冲:遇到一个换行字符时被清空缓冲区。键盘的输入是标准的行缓冲,因此按下回车键将清空缓冲区

(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; // 回到正常的缓冲方式
1 0
原创粉丝点击