C++IO类,输入输出缓冲区,流状态

这篇具有很好参考价值的文章主要介绍了C++IO类,输入输出缓冲区,流状态。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

我们的程序已经使用了很多IO库设施:

  • istream(输入流)类型,提供输入操作。
  • ostream(输出流)类型,提供输出操作。
  • cin,一个istream对象,从标准输入读取数据。写入到标准错误。
  • cout,一个ostream对象,向标准输出写入数据。
  • cerr,一个ostream对象,通常用于输出程序错误消息,
  • >>运算符,用来从一个istream对象读取输入数据。
  • <<运算符,用来向一个ostream对象写入输出数据。
  • getline函数,从一个给定的istream读取一行数据,存入一个给定的string对象中。

 IO类

到目前为止,我们已经使用过的IO类型和对象都是操纵char数据的。

默认情况下,这些对象都是关联到用户的控制台窗口的。

当然,我们不能限制实际应用程序仅从控制台窗口进行IO操作,应用程序常常需要读写命名文件。而且,使用IO操作处理string中的字符会很方便。此外,应用程序还可能读写需要宽字符支持的语言。

为了支持这些不同种类的IO处理操作,在istream和ostream之外,标准库还定义了其他一些IO类型,我们之前都已经使用过了。

下表列出了这些类型,分别定义在三个独立的头文件中:

  • iostream定义了用于读写流的基本类型,
  • fstream定义了读写命名文件的类型,
  • sstream定义了读写内存string对象的类型。
IO库类型和头文件
头文件 类型
iostream istream, wistream从流读取数据
ostream, wostream向流写入数据
iostream, wiostream读写流
fstream ifstream,wifstream从文件读取数据
ofstream, wofstream向文件写入数据
fstream,wfstream读写文件
sstream istringstream,wistringstream从string读取数据
ostringstream, wostringstream向string写入数据
stringstream, wstringstream读写string

 为了支持使用宽字符的语言,标准库定义了一组类型和对象来操纵 wchar_t类型的数据。宽字符版本的类型和函数的名字以一个w开始。

例如,wcin、 woout 和wcerr是分别对应cin、cout 和cerr的宽字符版对象。宽字符版本的类型和对象与其对应的普通char版本的类型定义在同一个头文件中。

例如,头文件fstream定义了ifstream和wifstream类型。

IO类型间的关系

概念上,设备类型和字符大小都不会影响我们要执行的操作。

例如,我们可以用>>读取数据,而不用管是从一个控制台窗口,一个磁盘文件,还是一个string读取。类似的,我们也不用管读取的字符能存入一个char对象内,还是需要一个wchar_t对象来存储。

标准库使我们能忽略这些不同类型的流之间的差异,这是通过继承机制实现的。

利用模板,我们可以使用具有继承关系的类,而不必了解继承机制如何工作的细节。

类型ifstream和istringstream都继承自istream。

因此,我们可以像使用istream对象一样来使用ifstream和istringstream对象。

也就是说,我们是如何使用cin的,就可以同样地使用这些类型的对象。

例如,可以对一个 ifstream或istringstream对象调用 getline,也可以使用>>从一个ifstream或istringstream对象中读取数据。

类似的,类型ofstream和ostringstream都继承自ostream因此,我们是如何使用cout的,就可以同样地使用这些类型的对象。

本节剩下部分所介绍的标准库流特性都可以无差别地应用于普通流、文件流和string流,以及char或宽字符流版本。

 IO对象无拷贝或赋值

我们不能拷贝或对IO对象赋值:

ofstream outl, out2;
outl = out2; // 错误:不能对流对象赋值
ofstream print (ofstream) ;// 错误:不能初始化ofstream参数
out2 = print (out2); //错误:不能拷贝流对象

由于不能拷贝IO对象,因此我们也不能将形参或返回类型设置为流类型。

进行IO操作的函数通常以引用方式传递和返回流。

读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。

条件状态 

IO 操作一个与生俱来的问题是可能发生错误。

一些错误是可恢复的,而其他错误则发生在系统深处,已经超出了应用程序可以修正的范围。

表中列出了IO类所定义的一些函数和标志,可以帮助我们访问和操纵流的条件状态。

IO库条件状态
strm::iostate strm 是一种IO类型。iostate是一种机器相关的类型,提供了表达条件状态的完整功能
strm::badbit strm::badbit用来指出流已崩溃
strm::failbit strm::failbit 用来指出一个IO操作失败了
stm::eofbit strm::eofbit用来指出流到达了文件结束
stm::goodbit strm::goodbit用来指出流未处于错误状态。此值保证为零
s.eof() 若流s的eofbit置位,则返回true
s.fail() 若流s的failbit或badbit置位,则返回true
s.bad() 若流s的badbit置位,则返回true
s.good() 若流s处于有效状态,则返回true
s.clear() 将流s中所有条件状态位复位,将流的状态设置为有效。返回void
s.clear(flags) 根据给定的flags标志位,将流s中对应条件状态位复位。flaqs的类型为strm::iostate。返回void
s.setstate(flags) 根据给定的flags标志位,将流s中对应条件状态位置位。flags的类型为strm::iostate。返回void
s.rdstate() 返回流s的当前条件状态,返回值类型为strm::iostate


下面是一个IO错误的例子:

int ival;
cin >> ival;

如果我们在标准输入上键入Boo,读操作就会失败。

代码中的输入运算符期待读取一个int,但却得到了一个字符B。这样,cin会进入错误状态。类似的,如果我们输入一个文件结束标识,cin也会进入错误状态。

一个流一旦发生错误,其上后续的IO操作都会失败。只有当一个流处于无错状态时,我们才可以从它读取数据,向它写入数据。

由于流可能处于错误状态,因此代码通常应该在使用一个流之前检查它是否处于良好状态。

确定一个流对象的状态的最简单的方法是将它当作一个条件来使用:

while (cin >> word)
//ok:读操作成功…


while 循环检查>>表达式返回的流的状态。如果输入操作成功,流保持有效状态,则条件为真。

查询流的状态

将流作为条件使用,只能告诉我们流是否有效,而无法告诉我们具体发生了什么。

有时我们也需要知道流为什么失败。

例如,在键入文件结束标识后我们的应对措施,可能与遇到一个IO设备错误的处理方式是不同的。

IO库定义了一个与机器无关的iostate类型,它提供了表达流状态的完整功能。

这个类型应作为一个位集合来使用。

IO库定义了4个iostate类型的constexpr值,表示特定的位模式。

这些值用来表示特定类型的IO条件,可以与位运算符一起使用来一次性检测或设置多个标志位。

badbit表示系统级错误,如不可恢复的读写错误。通常情况下,一旦badbit被置位,流就无法再使用了

在发生可恢复错误后,failbit 被置位,如期望读取数值却读出一个字符等错误。这种问题通常是可以修正的,流还可以继续使用。

如果到达文件结束位置,eofbit和failbit都会被置位。goodbit的值为0,表示流未发生错误。

如果badbit、failbit和eofbit任一个被置位,则检测流状态的条件会失败。

标准库还定义了一组函数来查询这些标志位的状态。

操作good在所有错误位均未置位的情况下返回true,而bad、fail和eof则在对应错误位被置位时返回true。

此外,在badbit被置位时,fail也会返回true。这意味着,使用good或fail是确定流的总体状态的正确方法。实际上,我们将流当作条件使用的代码就等价于!fail()。而eof和bad操作只能表示特定的错误。

管理条件状态

流对象的rdstate成员返回一个iostate值,对应流的当前状态。

setstate 操作将给定条件位置位,表示发生了对应错误。

clear成员是一个重载的成员:它有一个不接受参数的版本,而另一个版本接受一个iostate类型的参数。

clear不接受参数的版本清除(复位)所有错误标志位。执行clear()后,调用good会返回true。

我们可以这样使用这些成员:

//记住cin的当前状态
auto old state = cin.rdstate();// 记住 cin的当前状态
cin.clear(); // 使cin有效
process_input (cin); //使用cin
cin.setstate(old state); // 将cin置为原有状态

带参数的clear版本接受一个iostate值,表示流的新状态。

为了复位单一的条件状态位,我们首先用rdstate读出当前条件状态,然后用位操作将所需位复位来生成新的状态。

例如,下面的代码将failbit和badbit复位,但保持eofbit不变:

//复位failbit和badbit,保持其他标志位不变
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);


 

管理输出缓冲

每个输出流都管理一个缓冲区,用来保存程序读写的数据。

例如,如果执行下面的代码

os << "please enter a value:";

文本串可能立即打印出来,但也有可能被操作系统保存在缓冲区中,随后再打印。

有了缓冲机制,操作系统就可以将程序的多个输出操作组合成单一的系统级写操作。

由于设备的写操作可能很耗时,允许操作系统将多个输出操作组合为单一的设备写操作可以带来很大的性能提升。

导致缓冲刷新(即,数据真正写到输出设备或文件)的原因有很多:

  • 程序正常结束,作为main函数的return操作的一部分,缓冲刷新被执行。
  • 缓冲区满时,需要刷新缓冲,而后续的数据才能继续写入缓冲区。
  • 我们可以使用操纵符,如endl来显式刷新缓冲区。
  • 在每个输出操作之后,我们可以用操纵符unitbuf设置流的内部状态,来清空缓冲区默认情况下,对cerr是设置unitbuf的,因此,写到cerr的内容都是立即刷新的
  • 一个输出流可能被关联到另一个流。在这种情况下,当读写被关联的流时,关联的流的缓冲区会被刷新。例如,默认情况下,cin和cerr都被关联到cout,读cin或写cexr都会导致cout的缓冲区被刷新。

刷新输出缓冲区 

我们已经使用过操纵符endl,它完成换行并刷新缓冲区的工作。

IO库中还有两个类似的操纵符:flush和ends。

  • flush刷新缓冲区,但不输出任何额外的字符;
  • ends向缓冲区插入一个空字符,然后刷新缓冲区:
cout << "hi!" << endl; //输出hi和一个换行,然后刷新缓冲区
cout << "hi!" <<flush;//输出hi,然后刷新缓冲区,不附加任何额外字符

cout << "hi!" << ends;// 输出hi和一个空字符,然后刷新缓冲区

unitbuf 操纵符

如果想在每次输出操作后都刷新缓冲区,我们可以使用unitbuf操纵符。

它告诉流在接下来的每次写操作之后都进行一次flush操作。

而nounitbuf操纵符则重置流,使其恢复使用正常的系统管理的缓冲区刷新机制:

cout << unitbuf; // 所有输出操作后都会立即刷新缓冲区
// 任何输出都立即刷新,无缓冲
cout << nounitbuf; // 回到正常的缓冲方式

警告:如果程序崩溃,输出缓冲区不会被刷新

如果程序异常终止,输出缓冲区是不会被刷新的。当一个程序崩溃后,它所输出的数据很可能停留在输出缓冲区中等待打印。

当调试一个已经崩溃的程序时,需要确认那些你认为已经输出的数据确实已经刷新了。否则,可能将大量时间浪费在追踪代码为什么没有执行上,而实际上代码已经执行了,只是程序崩溃后缓冲区没有被刷新,输出数据被挂起没有打印而已。

关联输入和输出流

当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。

标准库将cout和cin关联在一起,

因此下面语句

cin >> ival;

导致cout的缓冲区被刷新。

交互式系统通常应该关联输入流和输出流。这意味着所有输出,包括用户提示信息,都会在读操作之前被打印出来。

tie有两个重载的版本;一个版本不带参数,返回指向输出流的指针。

如果本对象当前未关联到一个输出流,则返回的就是指向这个流的指针,如果对象未关联到流,则返回空指针。

tie的第二个版本接受一个指向ostream的指针,将自己关联到此ostream。即,x.tie(&o)将流x关联到输出流o。

我们既可以将一个istream对象关联到另一个ostream,也可以将一个ostream关联到另一ostream:

cin.tie(&cout); // 仅仅是用来展示:标准库将cin和cout关联在一起

// old tie指向当前关联到cin的流(如果有的话)
ostream *old tie = cin.tie(nullptr); // cin不再与其他流关联

//将cin与cerr关联;这不是一个好主意,因为cin应该关联到cout
cin.tie(&cerr); // 读取cin会刷新cerr而不是cout

cin.tie(old tie); //重建cin和cout间的正常关联


在这段代码中,为了将一个给定的流关联到一个新的输出流,我们将新流的指针传递给了tie。

为了彻底解开流的关联,我们传递了一个空指针。每个流同时最多关联到一个流,但多个流可以同时关联到同一个ostream。文章来源地址https://www.toymoban.com/news/detail-849089.html

到了这里,关于C++IO类,输入输出缓冲区,流状态的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【玩转Linux】标准io缓冲区的操作

    (꒪ꇴ꒪ ),hello我是 祐言 博客主页:C语言基础,Linux基础,软件配置领域博主🌍 快上🚘,一起学习! 送给读者的一句鸡汤🤔: 集中起来的意志可以击穿顽石! 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏         标准I/O缓冲区是用于提高I/O性能的一种机制,

    2024年02月16日
    浏览(54)
  • 【Linux】天天直接IO?我说停停,不如试试文件缓冲区

    收录于【Linux】文件系统 专栏 关于文件描述符与文件重定向的相关内容可以移步 文件描述符与重定向操作。 可以到 浅谈文件原理与操作 了解文件操作的系统接口。 目录 系列文章 揭秘C库文件结构体  文件缓冲区 为什么需要文件缓冲区 刷新机制 内核文件缓冲区 模拟实

    2024年02月09日
    浏览(104)
  • C/C++清空控制台输入缓冲区

    今天我再写一个群聊小demo的时候发现了一个问题,我在客户端首先使用了scanf输入了昵称 然后开启了发消息线程和收消息线程,在发消息线程函数如下: 这里使用了fgets函数来接收控制台输入,但是输入昵称之后立马就发送了一个消息,原因就是scanf输入之后并没有清空输入

    2024年02月10日
    浏览(43)
  • scanf老是出错?带你详细解决输入缓冲区问题

    我们一般在进行 输入输出的时候,就会用到 scanf / printf 。 并且根据格式指定可以输入输出各种类型的数据 。可以 输入整形,字符,浮点型等其他类型的数据。 今天呢我先给大家再介绍一下 getchar 和 putchar. getchar呢是读取一个字符,并且只能读取一个字符。putchar呢则是输出

    2024年02月05日
    浏览(31)
  • 网络攻防技术——缓冲区溢出攻击(基于服务器)

          缓冲区溢出 被定义为程序试图将数据写入缓冲区边界之外的情况。恶意用户可以利用此漏洞更改程序的流控制,从而导致恶意代码的执行。本实验的目的是让学生对这种类型的漏洞有实际的了解,并学习如何利用攻击中的漏洞。      在这个实验中,学生将得到四个不

    2024年02月02日
    浏览(64)
  • 8.缓冲区管理

    双缓冲区:TC+M 假设初始状态缓冲区1满,缓冲区2空,工作区为空。 刚开始缓冲区2为空,所以设备可以向缓冲区2中冲入数据耗时T,另一方面刚开始缓冲区1中是满的,所以刚开始就可以把缓冲区1中的数据传送到工作区中,M时刻工作区被充满,CPU就开始处理数据耗时C,处理完

    2024年02月11日
    浏览(40)
  • Redis 缓冲区

    缓冲区的应用场景 : 客户端与服务器端的通信时,暂存客户端发送的命令数据,或暂存服务器端返给客户端的数据结果 主从节点间进行数据同步时,暂存主节点接收的写命令和数据 缓冲区 : 避免客户端和服务器端的请求发送和处理速度不匹配 服务器给每个连接的客户端都准

    2024年02月07日
    浏览(68)
  • 理解缓冲区

    对于这样的代码,首先可以肯定的是 printf 语句先于 sleep 执行,既然如此那么就应该是先打印语句然后进行休眠,下面看看结果: 但这里却是先休眠以后再打印语句,这是因为存在一个叫缓冲区的东西,当我们要向外设写入数据(让显示器显示就是向显示器写入数据)时会将

    2023年04月25日
    浏览(71)
  • 【Linux】理解缓冲区

    我们发现 printf 和 fwrite (库函数)都输出了2次,而 write 只输出了一次(系统调用)。为什么呢?肯定和fork有关! C接口的函数被打印了两次系统接口前后只是打印了一次:和fork函数有关,fork会创建子进程。在创建子进程的时候,数据会被处理成两份,父子进程发生写时拷

    2024年01月23日
    浏览(52)
  • 【Linux】文件缓冲区

    提到文件缓冲区这个概念我们好像并不陌生,但是我们对于这个概念好像又是模糊的存在脑海中,之间我们在介绍c语言文件操作已经简单的提过这个概念,今天我们不妨深入理解什么是文件缓冲区 通过自己实现库中的一些文件操作函数更加深入的理解文件缓冲区 自定义实现

    2024年02月10日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包