C++未格式化的输入/输出操作,流随机访问

未格式化的输入/输出操作

到目前为止,我们的程序只使用过格式化IO操作。输入和输出运算符(<<和>>)根据读取或写入的数据类型来格式化它们。输入运算符忽略空白符,输出运算符应用补白、精度等规则。

标准库还提供了一组低层操作,支持未格式化IO(unformattedIO)。这些操作允许我们将一个流当作一个无解释的字节序列来处理。

单字节操作

有几个未格式化操作每次一个字节地处理流。这些操作列在表中,它们会读取而不是忽略空白符。

例如,我们可以使用未格式化IO操作get和put来读取和写入一个字符:

char ch;
while (cin.get(ch))
cout.put (ch);


此程序保留输入中的空白符,其输出与输入完全相同。它的执行过程与前一个使用noskipws的程序完全相同。

单字节低层IO操作
is.get (ch)从istream is读取下一个字节存入字符ch中。返回is
os.put (ch)将字符ch 输出到ostream os。返回os
is.get()将is的下一个字节作为int返回
is.putback(ch)将字符ch放回is。返回is
is.unget()将is向后移动一个字节。返回is
is.peek()将下一个字节作为int返回,但不从流中删除它

将字符放回输入流

有时我们需要读取一个字符才能知道还未准备好处理它。在这种情况下,我们希望将字符放回流中。

标准库提供了三种方法退回字符,它们有着细微的差别:

  1. peek 返回输入流中下一个字符的副本,但不会将它从流中删除,peek 返回的值仍然留在流中。
  2. unget 使得输入流向后移动,从而最后读取的值又回到流中。即使我们不知道最后从流中读取什么值,仍然可以调用unget。
  3. putback是更特殊版本的unget:它退回从流中读取的最后一个值,但它接受个参数,此参数必须与最后读取的值相同。

一般情况下,在读取下一个值之前,标准库保证我们可以退回最多一个值。即,标准库不材教辅 保证在中间不进行读取操作的情况下能连续调用putback或unget。

从输入操作返回的int值

函数 peek和无参的get版本都以int类型从输入流返回一个字符。这有些令人吃惊,可能这些函数

返回一个char看起来会更自然。

这些函数返回一个int的原因是:可以返回文件尾标记。我们使用char范围中的每个值来表示一个真实字符,因此,取值范围中没有额外的值可以用来表示文件尾。

返回int的函数将它们要返回的字符先转换为unsigned char,然后再将结果提升到int。因此,即使字符集中有字符映射到负值,这些操作返回的int 也是正值。

而标准库使用负值表示文件尾,这样就可以保证与任何合法字符的值都不同。头文件cstdio定义了一个名为EOF的const,我们可以用它来检测从get返回的值是否是文件尾,而不必记忆表示文件尾的实际数值。

对我们来说重要的是,用一个int 来保存从这些函数返回的值:

int ch; //使用一个int,而不是一个char来保存get()的返回值
//循环读取并输出输入中的所有数据
while ((ch = cin.get()) != EOF)
cout,put (ch);


此程序与上面的程序完成相同的工作,唯一的不同是用来读取输入的get版本不同。

多字节操作

一些未格式化IO操作一次处理大块数据。

如果速度是要考虑的重点问题的话,这些操作是很重要的,但类似其他低层操作,这些操作也容易出错。

特别是,这些操作要求我们自己分配并管理用来保存和提取数据的字符数组。

多字节低层IO操作
is.get (sink, size, delim)从is中读取最多size个字节,并保存在字符数组中,字符数组的起始地址由sink给出。读取过程直至遇到字符delim或读取了size个字节或遇到文件尾时停止。如果遇到了delim,则将其留在输入流中,不读取出来存入sink
is.getline(sink, size, delim)与接受三个参数的get版本类似,但会读取并丢弃delim
is.read(sink, size)读取最多size个字节,存入字符数组sink中。返回is
is.gcount()返回上一个未格式化读取操作从is读取的字节数
os.write(source, size)将字符数组source中的size个字节写入 os。返回is
s.ignore(size,delim)读取并忽略最多size个字符,包括de11m,与其他未格式化函数不同,lgnore有默认参数:size的默认值为1,delim的默认值为文件尾

get 和getline函数接受相同的参数,它们的行为类似但不相同。在两个函数中,sink都是一个char数组,用来保存数据。两个函数都一直读取数据,直至下面条作之一发生

  1. 已读取了size-1个字符
  2. 遇到了文件尾
  3. 遇到了分隔符

两个函数的差别是处理分隔符的方式:get将分隔符留作stream中的下一个字符,getline 则读取并去弃分隔符。 而无论哪个函数都不会将分隔符保存在sink中。

一个常见的错误是本想从流中删除分隔符,但却忘了做。

确定读取了多少个字符

某些操作从输入读取未知个数的字节。我们可以调用gcount来确定最后一个未格式化输入操作读取了多少个字符。

应该在任何后续未格式化输入操作之前调用gcount。

特别是,将字符退回流的单字符操作也属于未格式化输入操作。

如果在调用gcount之前调用了peek、unget 或putback,则gcount的返回值为0。

小心:低层函数容易出错

一般情况下,我们主张使用标准军提供的高层抽象。返回int的IO操作很好地解
释了原因。

一个常见的编程错误是将get或peek的返回值赋予一个char而不是一个int。这样做是错误的,但编译器却不能发现这个错误。最终会发生什么依赖于程序运行于哪台机器以及输入数据是什么。

例如,在一台char被实现为unsigned char的机器上,下面的循环永远不会停止:

char ch; //此处使用char就是引入灾难!
// 从cin.get返回的值被转换为char,然后与一个int 比较
while ((ch =cin.get()) != EOF)
cout.put(ch);

问题出在当get返回EOF时,此值会被转换为一个 unsigned char。转换得到的值与EOF的int值不再相等,因此循环永远也不会停止。

这种错误很可能在调试时发现。

在一台char被实现为 signed char的机器上,我们不能确定循环的行为。当一个越界的值被赋予一个signed变量时会发生什么完全取决于编译器。

在很多机器上,这个循环可以正常工作,除非输入序列中有一个字符与EOF值匹配。虽然在普通数据中这种字符不太可能出现,但低层IO通常用于读取二进制值的场合,而这些二进制值不能直接映射到普通字符和数值。

例如,在我们的机器上,如果输入中包含有一个值为'377'的字符,则循环会提前终止。因为在我们的机器上,将-1转换为一个signed char,就会得到'377'。如果输入中有这个值,则它会被(过早)当作文件尾指示符。

当我们读写有类型的值时,这种错误就不会发生。如果你可以使用标准库提供的类型更加安全、更高层的操作,就应该使用它们。

流随机访问

各种流类型通常都支持对流中数据的随机访问。我们可以重定位流,使之跳过一些数据,首先读取最后一行,然后读取第一行,依此类推。

标准库提供了一对函数,来定位(seek)到流中给定的位置,以及告诉(tell)我们当前位置。

随机IO本质上是依赖于系统的。为了理解如何使用这些特性,你必须查询系统文档。

虽然标准库为所有流类型都定义了seek和tell函数,但它们是否会做有意义的事情依赖于流绑定到哪个设备。

在大多数系统中,绑定到cin、cout、cerr和clog的流不支持随机访问——毕竟,当我们向cout直接输出数据时,类似向回跳十个位置这种操作是没有意义的。

对这些流我们可以调用seek和tell函数, 但在运行时会出错,将流置于一个无效状态。

由于istream和ostream 类型通常不支持随机访问,所以本节剩余内容只适用于fstream和sstream类型。

seek和tell函数

为了支持随机访问,IO类型维护一个标记来确定下一个读写操作要在哪里进行。它们还提供了两个函数:一个函数通过将标记seek到一个给定位置来重定位它;另一个函数tell我们标记的当前位置。

标准库实际上定义了两对seek和tell函数。

一对用于输入流,另一对用于输出流。输入和输出版本的差别在于名字的后缀是g还是p。g版本表示我们正在“获得”(读取)数据,而p版本表示我们正在“放置”(写入)数据。

seek和tell函数

tellg()

tellp()

返回一个输入流中(tellg)或输出流中(tellp)标记的当前位置

seekg (pos)

seekp(pos)

在一个输入流或输出流中将标记重定位到给定的绝对地址。pos通常是前一个tellg或tellp返回的值

seekp(off,from)

seekg(off,from)

在一个输入流或输出流中将标记定位到from之前或之后off个seekg(off, from) 字符,from可以是下列值之一
  1. beg,偏移量相对于流开始位置
  2. cur,偏移量相对于流当前位置
  3. end,偏移量相对于流结尾位置

从逻辑上讲,我们只能对 istream 和派生自istream 的类型 ifstream和istringstream使用g版本

同样只能对ostream和派生自ostream 的类型 ofstream和ostringstream使用p版本。


fstream或stringstream既能读又能写关联的流,因此对这些类型的对象既能使用g版本又能使用p版本。

只有一个标记

标准库区分seek和tell函数的“放置”和“获得”版本这一特性可能会导致误解。

即使标准库进行了区分,但它在一个流中只维护单一的标记——并不存在独立的读标记和写标记。

当我们处理一个只读或只写的流时,两种版本的区别甚至是不明显的。我们可以对这此流只使用g或只使用p版本。

如果我们试图对一个ifstream流调用tellp,编译器会报告错误。类似的,编译器也不允许我们对一个ostrinastream调用seekg。

fstream和stringstream类型可以读写同一个流。在这些类刑中,有单一的缓冲区用于保存读写的数据,同样,标记也只有一个,表示缓冲区中的当前位置。标准库将g和p版本的读写位置都映射到这个单一的标记。

由于只有单一的标记,因此只要我们在读写操作间切换,就必须进行 seek 操 766Note 作来重定位标记。

重定位标记

seek函数有两个版本:一个移动到文件中的“绝对”地址:另一个移动到一个给定位置的指定偏移量:

//将标记移动到一个固定位置
seekg(new_position);// 将读标记移动到指定的 pos_type 类型的位置
seekp(new_position);// 将写标记移动到指定的pos_type类型的位置// 移动到给定起始点之前或之后指定的偏移位置
seekg(offset,from);// 将读标记移动到距from偏移量为offset的位置
seekp(offset, from);//将写标记移动到距from偏移量为offset的位置


from的可能值如表17.21所示。

参数new position和offset的类型分别是pos_type和off_type,这两个类型都是机器相关的,它们定义在头文件istream和ostream中。pos_type表示一个文件位置,而off_type表示距当前位置的一个偏移量。一个off_type类型的值可以是正的也可以是负的,即,我们可以在文件中向前移动或向后移动。

访问标记

函数tellg和tellp返回一个pos_type值,表示流的当前位置。tell函数通常用来记住一个位置,以便稍后再定位回来:

// 记住当前写位置
Ostringstream writestr;// 输出stringstream
ostringstream::pos_type mark = writestr.tellp();
//...
if (cancelEntry)
// 回到刚才记住的位置
writestr.seekp (mark);

读写同一个文件

我们来考察一个编程实例。假定已经给定了一个要读取的文件,我们要在此文件的末尾写入新的一行,这一行包含文件中每行的相对起始位置。

例如,给定下面文件:

abed
efg
hi
j

程序应该生成如下修改过的文件:

abed
efg
hi
j
5 9 12 14


注意,我们的程序不必输出第一行的偏移——它总是从位置0开始。

还要注意,统计偏移量时必须包含每行末尾不可见的换行符。

最后,注意输出的最后一个数是我们的输出开始那行的偏移量。

在输出中包含了这些偏移量后,我们的输出就与文件的原始内容区分开来了。

我们可以读取结果文件中最后一个数,定位到对应偏移量,即可得到我们的输出的起始地址。

我们的程序将逐行读取文件。对每一行,我们将递增计数器,将刚刚读取的一行的长度加到计数器上,则此计数器即为下一行的起始地址:

int main()
{// 以读写方式打开文件,并定位到文件尾
// 文件模式参数参见8.2.2节(第286页)
fstream inout("copyout",fstream::ate |fstream::in | fstream::out);if (!inout) {
cerr << "Unable to open file!" << endl;
return EXIT_FAILURE;// EXIT FAILURE 
}
// inOut以ate模式打开,因此一开始就定义到其文件尾
auto end_mark = inout.tellg(); //记住原文件尾位置
inOut.seekg(0, fstream::beg); // 重定位到文件开始
size_t cnt =0; // 字节数累加器
string line; // 保存输入中的每行
//继续读取的条件:还未遇到错误且还在读取原数据
while (inout && inout. tellg() != end_mark&& getline(inOut, line))( //且还可获取一行输入
cnt += line,size() + 1;
auto mark =inout.tellg(); //加1表示换行符
inOut.seekp(0,fstream::end); // 记住读取位置
inOut << cnt; // 将写标记移动到文件尾
//如果不是最后一行,打印一个分隔符// 输出累计的长度
if (mark != end_mark) inOut <<"";
inOut.seekg (mark);
// 恢复读位置
inOut.seekp(0, fstream::end);
// 定位到文件尾
inOut <<"\n";
return 0; //在文件尾输出一个换行符
}

我们的程序用in、out和ate模式打开fstream。前两个模式指出我们想读写同一个文件。指定 ate会将读写标记定位到文件尾。与往常一样,我们检查文件是否成功打开,如果失败就退出

由于我们的程序向输入文件写入数据,因此不能通过文件尾来判断是否停止读取,而是应该在达到原数据的末尾时停止

因此,我们必须首先记住原文件尾的位置。由于我们是以ate模式打开文件的,因此inOut已经定位到文件尾了。我们将当前位置(即,原文件尾)保存在end_mark中。记住文件尾位置之后,我们seek 到距文件起始位置偏移量为0的地方,即,将读标记重定位到文件起始位置。

while循环的条件由三部分组成:首先检查流是否合法:如果合法,通过比较当前读位置(由tellg返回)和记录在end mark中的位置来检查是否读完了原数据;最后,假定前两个检查都已成功,我们调用getline读取输入的下一行,如果getline成功,则执行 while循环体。

循环体首先将当前位置记录在mark中。我们保存当前位置是为了在输出下一个偏移量后再退回来。接下来调用seekp将写标记重定位到文件尾。我们输出计数器的值,然后调用 seekg 回到记录在mark中的位置。回退到原位置后,我们就准备好继续检查循环条件了。

每步循环都会输出下一行的偏移量。因此,最后一步循环负责输出最后一行的偏移量但是,我们还需要在文件尾输出一个换行符。与其他写操作一样,在输出换行符之前我们调用 seekp来定位到文件尾。

 

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

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

相关文章

(一)kafka实战——kafka源码编译启动

前言 本节内容是关于kafka消息中间键的源码编译&#xff0c;并通过idea工具实现kafka服务器的启动&#xff0c;使用的kafka源码版本是3.6.1&#xff0c;由于kafka源码是通过gradle编译的&#xff0c;以及服务器是通过scala语言实现&#xff0c;我们要预先安装好gradle编译工具…

[已解决]ModuleNotFoundError: No module named ‘triton‘

问题描述&#xff1a;ModuleNotFoundError: No module named triton window下难以编译&#xff0c;直接下载win版本已经编译的&#xff1a; https://huggingface.co/r4ziel/xformers_pre_built/blob/main/triton-2.0.0-cp310-cp310-win_amd64.wh

每日一练 找无重复字符的最长子串

我们来看下这个题目&#xff0c;我们要统计的是不重复的子串&#xff0c;我们可以使用“滑动窗口法”&#xff0c;其实我们很容易就能想到思路。 我们的左窗代表我们目前遍历的开始&#xff0c;即我们遍历的子串的开头&#xff0c;右窗从左窗开始进行遍历&#xff0c;每次遍历…

【C语言终章】预处理详解(上)

【C语言终章】预处理详解&#xff08;上&#xff09; 当你看到了这里时&#xff0c;首先要恭喜你&#xff01;因为这里就是C语言的最后一站了&#xff0c;你的编程大能旅途也将从此站开始&#xff0c;为坚持不懈的你鼓个掌吧&#xff01; &#x1f955;个人主页&#xff1a;开敲…

04-MySQL数据库-权限管理

一、查看权限 1&#xff0c;查看系统所有权限 mysql> show privileges; 权限字段介绍 privileges #权限名称 context #对象&#xff0c;表示可以对数据库&#xff0c;那些资源、进行哪些操作&#xff1b; comment #描述&#xff0c;备注解释说明&#xff1b; Grant…

Caddy之静态站点应用场景

一、背景与介绍 无意之中看到公司部门的软件介质下载站点不是使用Nginx部署&#xff0c;而是使用Caddy。就比较好奇了&#xff0c;这个Caddy是个什么东西? 为啥他们没用Nginx呢&#xff0c;带着好奇心搜索了一下相关资料。 官方解释: Caddy is a powerful, extensible platfo…

Redis 事务 与 管道

redis事务 谈到事务大家可能就会想起mysql中的事务 注意这里的事务不是指的是事务的四大特性acid 持久性 原子性 隔离性 一致性 事务的概念就是 一组命令,串行化执行而不被打断 这里redis的事务和mysql的事务就不太一样 传统关系型数据库的事务主要强调的是一个没有执行完成就…

neo4j使用详解(六、cypher常用函数语法——最全参考)

Neo4j系列导航&#xff1a; neo4j及简单实践 cypher语法基础 cypher插入语法 cypher插入语法 cypher查询语法 cypher通用语法 cypher函数语法 4.常用函数 主要包括谓词函数&#xff08;断言函数&#xff09;、标量函数、聚合函数、字符串函数以及集合函数 4.1.谓词函数&#…

数据结构--循环链表(C语言实现)

一.循环链表的设计 typedef struct CNode{ int data; struct CNode* next; }CNode ,*CList; 2.循环链表的示意图: 3.循环链表和单链表的区别: 唯一区别,没有空指针,尾节点的后继为头,为循环之意. 二.循环链表的实现 //初始化return true; }//返回key的前驱地址&#xff0c;如果…

Lazarus远控组件NukeSped分析

静态信息&#xff1a; 样本md5&#xff1a;9b656f5d7e679b94e7b91fc3c4f313e4 由此可见为假的Adobe Flash Player 的攻击样本 样本分析 通过五个函数&#xff0c;内部调用sub_40159D函数动态获取API函数 利用IDA python解密字符串。。 完整python代码 Python> idc.get_…

MongoDB副本集环境搭建(以单机Windows为例)

前言 近期有搭建MongoDB副本集的需求,简单记录一下搭建过程(以本地Windows环境为例)。 一、副本集选型 1 Primary节点、1 Secondary 节点、1 Arbiter节点模式副本集环境搭建。 二、搭建过程 1. 安装MongoDB服务 下载地址:https://www.mongodb.com,如下图所示: 选择…

android 13 相册和拍照问题

android 在13以下。拍照和从相册选取图片需要使用 存储权限&#xff0c;相机权限。 在使用时候 需要声申请 Manifest.permission.WRITE_EXTERNAL_STORAGEManifest.permission.CAMERA 这2个权限。 但是在android 13以及以上时候&#xff0c;不需要申请 Manifest.permission…

基于Springboot旅游网站管理系统设计和实现

基于Springboot旅游网站管理系统设计和实现 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言 文末获取源码联系…

7.卷积神经网络与计算机视觉

计算机视觉是一门研究如何使计算机识别图片的学科&#xff0c;也是深度学习的主要应用领域之一。 在众多深度模型中&#xff0c;卷积神经网络“独领风骚”&#xff0c;已经被称为计算机视觉的主要研究根据之一。 一、卷积神经网络的基本思想 卷积神经网络最初由 Yann LeCun&a…

UE4_碰撞_自定义碰撞检测通道

效果如图&#xff1a; 1、项目设置中新建追踪检测通道weapon&#xff0c;默认值为忽略。 2、新建几个actor作为枪&#xff0c;碰撞预设全部设为自定义&#xff0c;把新建的检测响应weapon设为阻挡。 3、角色进行射线检测 运行效果如下&#xff1a; 发现有些物体碰不到&#xff…

GetSystemTimes:获取CPU占用率(WIN API)

原文链接&#xff1a;https://blog.csdn.net/qq_28742901/article/details/104960653 GetSystemTimes函数&#xff1a; BOOL WINAPI GetSystemTimes(__out_opt LPFILETIME lpIdleTime, // 空闲时间__out_opt LPFILETIME lpKernelTime, // 内核进程占用时间__out_opt LPFILETI…

蓝桥杯 本质上升序列

题目描述: 小蓝特别喜欢单调递增的事物。 在一个字符串中&#xff0c;如果取出若干个字符&#xff0c;将这些字符按照在字符串中的顺序排列后是单调递增的&#xff0c;则成为这个字符串中的一个单调递增子序列。 例如&#xff0c;在字符串 lanqiao 中&#xff0c;如果取出字符…

模型训练----将pth模型转换为onnx

目录 1 安装需要的环境2、模型转换3、测试onnx模型 Github代码 1 安装需要的环境 需要在虚拟环境中安装onnx和onnxruntime&#xff08;GPU&#xff09;&#xff0c;环境和自己的cuda版本要对应上查询链接 激活环境&#xff0c;查看环境的cuda版本,我是cuda11.6 cudnn8302&a…

AI预测福彩3D第22弹【2024年3月31日预测--第5套算法开始计算第4次测试】

今天&#xff0c;咱们继续进行本套算法的测试&#xff0c;今天为第四次测试&#xff0c;仍旧是采用冷温热趋势结合AI模型进行预测。好了&#xff0c;废话不多说了。直接上结果~ 仍旧是分为两个方案&#xff0c;1大1小。 经过人工神经网络计算并进行权重赋值打分后&#xff0c;3…

js怎样获取到时间戳?

1.获取到时间戳精确到秒&#xff0c;13位&#xff1b; let timestamp Date.parse(new Date()); console.log(timestamp);//输出 1591669256000 13位2.获取到时间戳精确到毫秒&#xff0c;13位&#xff1b; let timestamp Math.round(new Date()); console.log(timestamp)…