《C++程序设计原理与实践》笔记 第10章 输入/输出流

news2025/1/20 3:39:27

在本章和下一章中,我们将介绍C++标准库中用于处理来自各种源的输入和输出的功能:I/O流。本章关注基本模型:如何读写单个值,以及如何打开和读写整个文件。下一章将介绍具体细节。

10.1 输入和输出

如果没有数据,计算就毫无意义。我们需要将数据输入到程序中来进行一些有价值的计算,并获取输出。数据的输入源和输出目标非常广泛。因此,我们需要一种将程序的读写操作与实际使用的输入/输出设备分离的方法。

I/O库提供了I/O的一个抽象,从而程序员不必关心设备和设备驱动程序:

I/O库抽象

使用这样是模型,输入和输出就可以看作由I/O库处理的字节(字符)流。程序员的工作就变为:

  • 创建数据源或目的地的I/O流
  • 读写这些流

从程序员的角度,输入和输出有很多种。例如:

  • 大量数据项构成的流(文件、网络连接、录音设备、显示设备等)
  • 通过键盘与用户交互
  • 通过图形界面与用户交互

其中,前两种I/O由C++标准库I/O流提供,图形用户交互则由其他一些库支持(第12~16章)。

10.2 I/O流模型

C++标准库<iostream>提供了istream类型来处理输入流、ostream类型来处理输出流。我们已经使用过标准输入流cin和标准输出流cout

注:各种I/O流及继承关系如下图所示(详见Input/Output library)

I/O流继承关系

ostream负责

  • 将不同类型的值转换为字符序列
  • 将这些字符发送到“某处”(例如控制台、文件、内存或者另一台计算机)

ostream

缓冲区(buffer)是ostream内部用于保存数据并与操作系统通信的数据结构。如果写入ostream和字符出现在目的设备之间存在“延迟”,通常是因为字符还在缓冲区中。缓冲是提高性能的重要技术,而处理大量数据时性能是很重要的。

istream负责

  • 将字符序列转换为不同类型的值
  • 从“某处”(例如控制台、文件、内存或者另一台计算机)读取字符

istream

ostream一样,istream也使用缓冲区来与操作系统通信。对于istream,缓冲区对用户是可见的。例如,使用cin从键盘输入数据时,输入的内容都留在缓冲区中,直到按Enter键,在此之前可以通退格键清除字符来“改变主意”。

10.3 文件

基本上,文件就是一个从0开始编号的字节序列:

文件

文件具有格式,即有一组规则来确定字节的含义。 例如,文本文件中,一个字节表示一个字符(在特定字符集下的编码);使用二进制表示整数的文件中,4个字节表示一个整数,如下图所示。只有知道文件的格式才能知道文件中字节数据的含义。

不同文件格式

例如,在文本文件中,48 65 6c 70四个字节分别表示 ‘H’, ‘e’, ‘l’, ‘p’ 四个字符;而在二进制整数文件中,同样的四个字节表示十六进制数0x48656c70,等于十进制的1214606448。

注:一个字节(byte)是8个比特位(bit),能表示的二进制数范围是00000000~11111111,即十进制的0~255、十六进制的00~ff。

对于一个文件,ostream将内存中的对象转换为字节流,并将其写入磁盘。istream进行相反的操作:从磁盘读取字节流,并将其转换为对象:

I/O流处理文件
为了读一个文件,需要知道文件名,(以读模式)打开文件,读入字符,关闭文件(通常隐式完成)。为了写一个文件,需要知道文件名,(以写模式)打开文件,写出对象,关闭文件(通常隐式完成)。

10.4 打开文件

如果要读写文件,必须打开一个专门用于该文件的流。ifstream是用于读文件的istreamofstream是用于写文件的ostreamfstream是既可以读文件又可以写文件的iostream,这些类型定义在标准库头文件 <fstream> 中。在使用文件流之前,必须将其关联到文件。例如:

// write to file
string filename = "test.txt";
ofstream ofs(filename);   // open file for writing
if (!ofs.is_open())       // check if file is open
    cout << "failed to open " << filename << endl;
else
    ofs << 123 << "abc";  // write to ofstream

ofstream的构造函数参数指定文件名,如果文件不存在则创建。成员函数is_open()检查文件是否被成功打开(文件流是否成功关联到文件),如果打开失败则返回false。之后可以像任何ostream一样使用运算符<<写出数据。

例如,如果上面的代码运行成功,则文件test.txt的内容如下:

123abc

ifstream的用法与ofstream类似:

// read from file
string filename = "test.txt";
ifstream ifs(filename);   // open file for reading
if (!ifs.is_open())       // check if file is open
    cout << "failed to open " << filename << endl;
else {
    int n;
    string s;
    ifs >> n >> s;        // read from ifstream
    cout << "n = " << n << ", s = " << s << endl;
}

ifstream的构造函数参数指定文件名,文件必须存在。成员函数is_open()检查文件是否被成功打开,如果因文件不存在、没有权限等原因打开失败则返回false。之后可以像任何istream一样使用运算符>>读取数据。

例如,text.txt的内容为上一段代码的输出结果,则这段代码的输出如下:

n = 123, s = abc

通常,最好在重要的计算开始之前就打开文件。毕竟,如果在完成计算之后才发现无法保存结果将会浪费计算资源。

当一个文件流离开作用域时,它关联的文件将被关闭,文件流内部的缓冲区会被刷新(flush),即缓冲区中的字符会被写入文件。

在创建文件流时打开文件、依赖流的作用域来隐式关闭文件是一种理想的方法。 另外,也可以通过open()close()函数显式打开和关闭文件。然而,依赖作用域的方式避免了两类错误:在打开文件之间或关闭文件之后使用文件流对象。例如:

ifstream ifs;
// ...
ifs >> foo;      // won't succeed: no file opened for ifs
// ...
ifs.open(name);  // open file named name for reading
// ...
ifs.close();     // close file
// ...
ifs >> bar;      // won't succeed: ifs's file was closed
// ...

不能在关闭一个文件流之前第二次打开它。例如:

fstream fs;
fs.open("foo", ios_base::in);   // open for input
// close() missing
fs.open("foo", ios_base::out);  // won't succeed: fs is already open
if (!fs) error("impossible");

在打开一个流之后不要忘记检测是否成功。

注:

(1)fstream构造函数的第二个参数是打开模式,其类型是枚举ios_base::openmode,其各枚举值都是只有一个二进制位为1、其他位均为0的整数(2的幂),即位掩码,可以使用按位或运算符|组合。C++文件流的打开模式与C标准库fopen()函数的打开模式对应关系如下:

C++模式C模式含义
inr
outout | truncw写(覆盖原有内容)
appout | appa追加
in | outr+读或写
in | out | truncw+读或写(覆盖原有内容)
in | out | appa+读或追加
in | binaryrb读二进制文件
out | binaryout | trunc | binarywb写二进制文件(覆盖原有内容)
app | binaryout | app | binaryab追加二进制文件
in | out | binaryr+b读或写二进制文件
in | out | trunc | binaryw+b读或写二进制文件(覆盖原有内容)
in | out | app | binarya+b读或追加二进制文件

另外,ifstreamofstream的构造函数也有第二个参数,默认值分别是ios_base::inios_base::out,即使指定了其他模式,也会分别自动添加ios_base::inios_base::out

(2)在上面的代码中,第二次调用open()后,fs通过运算符operator bool()转换为布尔值false,表示处于失败状态(详见10.6节),但is_open()仍然为true;如果第一次open()失败了,则fsis_open()都是false

10.5 读写文件

考虑这样一个问题:如何从文件读取一组测量结果并在内存中表示?例如,从气象站获取的温度数据:

0 60.7
1 60.6
2 60.3
3 59.22
...

这个数据文件包含了一系列(小时,温度)数值对。小时的值为0~23,温度为华氏度,没有任何其他格式。这是最简单的情况。

读取温度数据文件

其中的while循环是一个典型的输入循环,ifs可以是任何一种istream,这段代码都能够适用,因为所有的istream都支持运算符>>。对于输出流也是同理。

10.6 I/O错误处理

在处理输入时,我们必须预料到并处理错误。 错误的原因可能是人为失误(理解错了指令、打字错误、让猫在键盘上散步等)、文件格式不符合规范、我们(程序员)预料错误,等等。输入错误的可能性是无限的,但istream将所有可能的情况归结为四类,称为流状态(stream state)。流状态用枚举ios_base::iostate表示,也是位掩码:

流状态含义
goodbit操作成功
eofbit到达输入结尾(“end of file”, EOF)
failbit发生意外情况
badbit发生严重意外情况

注:istream提供了一些成员函数来检查和更新错误状态

成员函数作用
rdstate()返回流状态
setstate()将流状态与给定值按位取或(即设置指定的状态位,其他状态位不变)
clear()将流状态设置为给定值,默认为goodbit(即设置指定的状态位,清除其他状态位)
good()如果没有任何错误标识被置位则返回true,等价于rdstate() == goodbit
eof()如果eofbit被置位则返回true
fail()如果failbitbadbit被置位则返回true
bad()如果badbit被置位则返回true
operator bool等价于!fail()
operator!等价于fail()

failbitbadbit之间的区别并未准确定义(由流的作者决定),但基本思想是:如果输入操作遇到简单的格式错误(例如读取数字时遇到 ‘x’)则设置failbit,即假定此时可以从错误中恢复;如果遇到严重错误(例如磁盘读故障)则设置badbit,即假定此时只能放弃从这个流获取数据。bad()状态的流也是fail()状态,因此有如下的通用逻辑:

int i = 0;
s >> i;
if (!s) {
    // we get here (only) if an input operation failed
    if (s.bad())  // stream corrupted: let's get out of here!
        error("cin is bad");
    if (s.eof()) {
        // no more input
        // this is often how we want a sequence of input operations to end
    }
    if (s.fail()) {
        // stream encountered something unexpected
        s.clear();  // make ready for more input
        // somehow recover
    }
}

注意处理fail()时所使用的clear():为了从错误中恢复,通过clear()将流恢复到good()状态(否则后续输入操作都会失败)。

下面是一个如何使用流状态的例子。假设要读取一系列整数到一个vector中,以字符 “*” 或EOF(Windows系统是Ctrl+Z,UNIX系统是Ctrl+D)结束。例如:

1 2 3 4 5 *

可以使用以下函数实现:

读取整数向量

这段代码看似不复杂,但实际上有很多需要注意的细节:

  • 运算符>>遇到输入结尾时,输入流的eofbitfailbit都会被设置。运算符>>的返回值是输入流本身,而istream定义了operator bool(即将输入流本身解释为布尔值),因此将ist >> i作为for循环测试条件的含义是:先执行输入操作,之后判断ist.operator bool()是否为true。另外,operator bool等价于!fail(),而fail()failbitbadbit被设置时都会返回true。综上,无论输入操作遇到输入结尾、格式错误还是严重错误,fail()都会返回trueoperator bool都会返回false
  • 在尝试读取终结符之前,必须先调用clear()将所有的错误状态清除,否则输入操作ist >> c将会失败。
  • 如果读取的字符不是终结符,则使用unget()将该字符放回ist(输入操作不能随意丢弃未使用的字符),并通过ist.clear(ios_base::failbit)将流状态重新设置为failbit(表示遇到了格式错误)。带参数的clear()有些令人迷惑:设置指定的状态位,清除其他状态位unget()putback()(见6.8.1节)的简洁版,它依赖流记住最后一个字符是什么,而不需要在参数中给出。
  • exceptions()是作用是:当流处于指定的状态时,将抛出标准库异常ios_base::failure。因此在fill_vector()中不必单独处理bad()(在几乎所有情况下,我们能做的也只是抛出异常)。
  • 如果需要,fill_vector()的调用者可以通过测试isteof()fail()或捕获异常来知道输入终止的原因。

ostream也有与istream相同的四种状态。但对于本书中的程序,输出错误比输入错误罕见得多,因此通常不检测ostream的状态。

10.7 读取单个值

10.7.1 将问题分解为可处理的部分

10.7.2 将对话与函数分离

10.8 用户自定义输出运算符

10.9 用户自定义输入运算符

10.10 一个标准的输入循环

10.11 读取结构化的文件

10.11.1 内存表示

10.11.2 读取结构化的值

10.11.3 改变表示

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/161448.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【正点原子FPGA连载】第十三章Linux内核移植 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Linux开发指南

1&#xff09;实验平台&#xff1a;正点原子MPSoC开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id692450874670 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第十三章Linux内…

2023版软件测试学习路线图(超详细自学路线)

送福利了&#xff01;超详细的软件测试学习路线图来啦&#xff0c;2023版是首发哟&#xff01;软件测试学习路线图分为9个阶段&#xff0c;包含&#xff1a;软件测试环境配置和管理-->软件测试数据管理与数据库测试-->web前端测试技术-->通用软件测试技术-->Python…

回顾2022! 链上NFT精彩项目大盘点

过去一年&#xff0c;WEB3和元宇宙无疑吸引了一大波关注度和热度。不少知名品牌如耐克、GUCCI、百事可乐、星巴克、麦当劳等都纷纷加入这波浪潮&#xff0c;通过推出NFT、数字商品等&#xff0c;来尝试WEB3机制&#xff0c;进而塑造更好的用户消费体验和参与度。NFT兼具身份、功…

springboot,vue二手交易平台

开发工具&#xff1a;IDEA服务器&#xff1a;Tomcat9.0&#xff0c; jdk1.8项目构建&#xff1a;maven数据库&#xff1a;mysql5.7系统用户前台和管理后台两部分&#xff0c;项目采用前后端分离前端技术&#xff1a;vue elementUI服务端技术&#xff1a;springbootmybatis项目功…

0基础快速掌握正则表达式

背景 在日常开发中&#xff0c;我们经常会遇到使用正则表达式的场景&#xff0c;比如一些常见的表单校验&#xff0c;会让你匹配用户输入的手机号或者身份信息是否规范&#xff0c;这就可以用正则表达式去匹配。相信大多数人在碰到这种场景的时候都是直接去网上找&#xff0c;…

在 2023 ETH Denver 与 Cartesi 一起建设

我们非常高兴的加入了 2023年ETHDenver&#xff0c;参加了BUIDLathon 赛道和现场研讨会等活动。作为规模最大、持续时间最长的ETH 活动之一&#xff0c;我们将向热衷于为全球区块链生态系统做出贡献的新开发者社区分享 Cartesi 技术。你想在2023年#BUIDL 做一些有趣有意义的事情…

基于springboot的景区旅游信息管理系统(源代码+数据库)

基于springboot的景区旅游信息管理系统(源代码数据库) 一、系统介绍 本项目分为管理员与普通用户两种角色 用户登录 前台功能&#xff1a;旅游路线、旅游景点、旅游酒店、旅游车票、旅游保险、旅游策略管理员登录 后台功能&#xff1a;用户管理、旅游路线管理、旅游景点管理…

Codeforces Round #843 (Div. 2)(A~C,E)

A1/A2. Gardener and the Capybaras (easy version)三个字符串&#xff0c;按照顺序连在一起&#xff0c;三个字符串满足第二个字符串大于等于第一个和第三个&#xff0c;或者第二个字符串小于等于第一个和第三个&#xff0c;输出满足情况的三个字符串。思路&#xff1a;对于长…

ubuntu18.04系统下挂载新的机械硬盘

ubuntu18.04系统下挂载新的机械硬盘1.显示硬盘以及所属分区情况sudo fdisk -lDisk /dev/sda doesnt contain a valid partition table硬盘分区 对机械硬盘进行操作 sudo fdisk /dev/sda下图表示的是具体流程截图&#xff1a; The partition table has been altered!硬盘格式…

AWS RDS开启审计日志

问题 需要对AWS的RDS开启相关日志。先检查RDS是否开启日志&#xff0c;如下图&#xff1a; 选中一个数据库实例&#xff0c;查看到只开启了数据库的错误日志。但是&#xff0c;我们需要开启其他类型的审计日志。下面开始怎么样开启其他类型日志&#xff0c;来启用高级审计模…

corrosion 靶机(ffuf模糊测试,命令执行)

环境准备 靶机链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;c2j6 虚拟机网络链接模式&#xff1a;桥接模式 攻击机系统&#xff1a;kali linux 2022.03 信息收集 1.探测目标靶机开放端口和服务情况 2.用gobuster扫描目录&#xff0c;并访问 gobuster dir -…

手把手编译FFmpeg

支持centos8.6、ubuntu20.04 export 建议开始之前&#xff0c;弄一台干净的机子&#xff0c;或者系统恢复到出厂设置&#xff0c;否则容易出问题 然后设置动态库默认加载目录&#xff08;注意/usr/local/lib不是系统默认的路径&#xff0c;/lib和/usr/lib才是&#xff09; …

jsp库存管理管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 库存管理管理系统 是一套完善的系统源码&#xff0c;对理解JSP java serlvet MVC编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;以及相应配套的设计文档&#xff0c;系统主要采用B/S 模式开发。 通过本系统建设&#xff0c…

ArcGIS基础实验操作100例--实验97计算河道方向坡度

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 空间分析篇--实验97 计算河道方向坡度 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff08;1&…

内存管理-模板初阶理解-string类的模拟实现

文章目录1. 内存管理operator new和operator delete面试题&#xff1a;malloc、free和new、delete的区别2. 内存泄漏1. 内存泄漏&#xff1a;2. 内存泄漏危害&#xff1a;3.堆内存泄漏4.系统资源泄漏3. 模板初阶函数模板类模板&#xff1a;模板运行时不检查数据类型&#xff0c…

黑马编程资源最新最全全清单:速来收藏~

今年是黑马坚持免费分享视频教程的第16年&#xff0c;每年到了这个时候&#xff0c;「成绩单」也不会缺席&#xff0c;不仅是对过往的回顾&#xff0c;更是对那些选择跟着黑马持续学习的小伙伴们的一种激励。 黑马视频教程2022年速报 截至年底&#xff0c;黑马程序员 B 站累计…

ArcGIS基础实验操作100例--实验96创建地形剖面图

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 空间分析篇--实验96 创建地形剖面图 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff08;1&am…

头戴式耳机跑步方便吗、公认最好的跑步耳机排行榜

平时&#xff0c;我们总能看到许多运动健身的人群&#xff0c;在锻炼时都佩戴着耳机。但运动耳机的选择&#xff0c;同样是大有学问的。如果佩戴传统的真无线蓝牙耳机&#xff0c;有可能出现佩戴不稳、耳道肿胀等问题&#xff0c;影响运动体验。所以今天我们特意给大家带来几款…

时间段查询将00:00:00更换成23:59:59

目录 一.问题描述 二.解决问题 2.1.思路一(时间戳赋值) 2.2思路二(LocalDateTime 方法赋值) 三.总结 一.问题描述 在我们作web项目的时候总会有时间段的查询条件 例如: 问题: 前端传到后台的时间: reportStartTime: 2023-01-01 00:00:00 reportEndTime: 2023-01-13 00…

Vue3和Vue2的区别

vue经历从2.0到3.0更新之后&#xff0c;简⽽⾔之就是变得更轻&#xff0c;更快&#xff0c;使⽤起来更加⽅便&#xff0c;每⼀次的版本迭代都是对上⼀个版本的升级优化&#xff0c;不管 是对于我们开发者还是对于⽤户体验都是不断地在越来越⽅便&#xff0c; 选项式Api与组合式…