C++ 流的操作 | 初识IO类、文件流、string流的使用

文章目录

  • 前言
  • IO头文件
    • iostream
    • fstream
    • sstream
  • 流的使用
    • 不能拷贝或对 IO对象 赋值
    • 条件状态与 iostate 类型
  • 输出缓冲区
  • 文件流
    • fstream类型
    • 文件模式
    • 文件光标函数
      • tellg() / tellp()
      • seekg() / seekp()
    • 向文件存储内容/读取文件内容
  • string流
    • istringstream
    • ostringstream


前言

我们在使用 C++ 的过程中,总避免不了 IO操作,比如经常用到的一些 IO库设施

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

但实际上可能仅仅是懵懵懂懂在使用,如果不深入了解的话,这样的使用是浅薄的。


IO头文件

iostream

定义了用于读写的基本类型。

  • istream,wistream 从流读取数据
  • ostream,wostream 向流写入数据
  • iostream,wiostream 读写流

fstream

定义了读写命名文件的类型。

  • ifstream,wifstream 从文件读取数据
  • ofstream,wofstream 向文件写入数据
  • fstream,wfstream 读写文件

sstream

定义了读写内存string对象的类型。

  • istringstream,wistringstream 从 string 读取数据
  • ostringstream,wostringstream 向 string 写入数据
  • stringstream,wstringstream 读写 string

流的使用

标准库通过继承使我们忽略不同类型流之间的差异。举例来说,类型 ifstreamistringstream 都继承自 istream。因此,我们如何使用 cin ,就可以同样地使用这些类型的对象。

不能拷贝或对 IO对象 赋值

ofstream out1, out2;
out1 = out2; // error:不能对流对象赋值
ofstream printf(ofstream); // error: 不能初始化ofstream参数
out2 = printf(out2); // error: 不能拷贝流对象
  • 由于不能拷贝IO对象,因此我们也不能将形参返回类型设置为流类型。
  • 进行IO操作的函数通常以引用方式传递和返回流。读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。

条件状态与 iostate 类型

IO操作使用不当的话会发生错误,而如果是发生在系统深处的错误,那么就超出了应用程序可以修正的范围。但也有一些错误是可以恢复的,IO类也提供了一些函数和标志来访问、操纵流的条件状态

在这里插入图片描述
在这里插入图片描述

下面对表中的四个条件位作进一步介绍。

iostate 类型

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

IO库定义了 4个 iostate类型constexpr 值(常量表达式),表示特定的位模式

  • badbit: 表示系统级错误,如不可恢复的读写错误。通常情况下,一旦 badbit 被置位,流就无法再使用了。
  • failbit: 在发生可恢复错误后被置位,如期望读取数值却读出一个字符等错误。这种问题通常是可以修正的,流还可以继续使用。
  • eofbit: 如果到达文件结束位置,连同 failbit 一起被置位。
  • goodbit: 值为 0 时,表示流未发生错误。

对他们进行一个简单的使用:

auto old_state = cin.rdstate(); // 返回流cin的当前状态,返回值类型为 strm::iostate
cin.clear(); // 将cin所有条件位复位,换言之,使cin有效
// clear重载版本允许有参数,接受一个iostate值,表示流的新状态
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit); 
// 先用rdstate读出当前条件状态,再将failbit和badbit复位生成新状态。
process_input(cin); // 使用cin
cin.setstate(old_state); // 将cin置为原有状态

输出缓冲区

以前对于输出缓冲区是没什么概念的……直到在做美团往年笔试题的时候,有道编程题如果用 endl 作为换行会报超时,原因是 endl 频繁刷新输出缓冲区,因此需要用 '\n'

操作系统的 IO操作 是很耗时的,缓冲机制使操作系统将程序的多个输出操作组合成单一的系统级写操作(写到显示设备上),对性能的提升是巨大的。

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

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

关于第三点,共有三种操作符可用来刷新缓冲:

  • endl: 换行并刷新缓冲区
  • flush: 仅刷新缓冲区,但不输出任何额外字符
  • ends: 向缓冲区插入一个空字符然后刷新缓冲区

关于第四点:

  • unitbuf操纵符: 每次写操作之后都进行一次 flush 操作
  • nounitbuf操纵符: 重置流,使其恢复默认的缓冲区刷新机制

值得一提的是,如果程序崩溃,输出缓冲区不会被刷新。

关于第五点,C++提供了 tie函数 来查看当前对象关联的输入输出流,tie 有两个重载版本:

  • 无参数版本: 返回指向输出流的指针。当前对象若关联了一个输出流,则返回指向该流的指针;若未关联流,则返回空指针
  • 参数为一个指向 ostream 的指针: 将当前对象关联到此 ostream

每个流同时最多关联到一个流,但多个流可以同时关联到同一个 ostream

示例:

cin.tie(&cout); // 标准库默认将 cin 和 cout 关联在一起
cin.tie() = cin.tie(nullptr); // 通过传递空指针,让 cin 不再与其他流关联

文件流

fstream类型

除了继承自 iostream类型 的行为外,fstream类型 还增加了一些新的成员来管理与流关联的文件。
在这里插入图片描述

open函数

fstrm(s) 之所以能在调用时打开文件s,是因为自动调用了 open函数 ,等价于:

ifstream in; // 输入文件流未与任何文件关联
in.open(ifile); // 打开指定文件,并与in绑定

对一个已经打开的文件流调用 open 会失败,此时 failbit 会被置位,随后使用文件流的操作都会失败。因此,调用 open 后检测是否成功是个好习惯:

if(in) // 成功
else // 不成功

如果想要将文件流关联到另一个文件,必须先关闭已关联的文件:

in.cloes();
in.open(ifile);

open 成功调用会将 good() 设为 true

close函数

当一个 fstream对象 被销毁时,close 会自动被调用。


文件模式

在这里插入图片描述

每个文件流类型都定义了一个默认的文件模式,当我们未指定文件模式时,就使用此默认模式。

  • ifstream关联 的文件默认以 in模式 打开;
  • ofstream关联 的文件默认以 out模式 打开;
  • fstream关联 的文件默认以 in和out模式 打开。

虽然不论是调用 open 打开文件,还是 fstrm(s) 这样隐式打开文件,都可以指定文件模式,但指定文件模式有如下限制:

  1. 只可以对 ofstreamfstream 对象设定 out 模式。
  2. 只可以对 ifstreamfstream 对象设定 in 模式。
  3. 只有 out 也被设定时才可设定 trunc 模式。
  4. 只要 trunc 没被设定,就可以设定 app 模式。在 app 模式下,即使没有显式指定 out 模式,文件也总是以输出方式被打开。
  5. 默认情况下,以 out 模式打开的文件同时使用 trunc 模式,即会被截断(内容被丢弃)。
    • 为了保留以 out 模式打开的文件的内容,我们必须同时指定 app 模式,这样只会将数据追加写到文件末尾;
    • 或者同时指定 in 模式,即打开文件同时进行读写操作。
  6. atebinary 模式可用于任何类型的文件流对象,且可以与其他任何文件模式组合使用。

关于第五点,举例详细讲一下:

/*截断*/
ofstream out1("file1"); // 隐含以out模式打开文件并截断文件
ofstream out2("file1", ofstream::out); // 隐含地截断文件
ofstream out3("file1", ofstream::out | ofstream::trunc); 
// 显式实现out模式打开文件并截断/*app模式保留文件内容*/
ofstream app1("file2", ofstream:app); // 隐含out模式
ofstream app2("file2", ofstream:out | ofstream:app); 

文件光标函数

tellg() / tellp()

该函数没有参数,返回 pos_type 类型的值,就是一个整数,代表当前 读取光标【tellg】 / 写入光标【tellp】 的位置距文件首的字节数。


seekg() / seekp()

  • g 表示 get,指示函数在输入流上工作。该函数的作用移动读操作光标。
  • pput缩写,指示函数在输出流上工作。seekp用于移动写操作光标。
// 一个参数
basic_istream<Elem, Tr>& seekg(  pos_type pos
);// 两个参数
basic_istream<Elem, Tr>& seekg(  off_type off,                                     ios_base::seekdir way
);// seekp 函数原型及参数信息同 seekg
  • pos:移动读取指针的绝对位置。要求传入的参数类型与函数tellg(见下文)的返回值类型相同。
  • off:偏移量,单位字节(B)。正数表示向右偏移,负数表示向左偏移。
  • way:基地址,off根据该地址进行偏移。有下面三个取值:
描述模式标志
文件首std::ios::beg
文件尾std::ios::end
当前光标位置std::ios::cur

注意,如果目前已经在文件末尾,则在调用seekg之前,必须清除文件末尾的标志:

fstream ioFile("文件路径");ioFile.get(ch); // 先将字符读入流
while (!ioFile.fail())
{cout.put(ch); // 再将流中内容输出到屏幕ioFile.get(ch); // 
}
// 假设此时已经读取到文件流对象的末尾
/* 缺少调用 clear() */
文件流对象.seekg(0L, ios::beg); // 移动到文件开头
文件流对象.tellg(); // 返回-1,说明上一步并为真正移动到文件开头// 正确做法
文件流对象.clear();
文件流对象.seekg(0L, ios::beg); // 移动到文件开头

向文件存储内容/读取文件内容

// url 文件路径
void write(std::string &url){std::ofstream fwrite(url, std::ios::binary);if (!fwrite.is_open()) {std::cout << "open url fail" << std::endl;}/* 写入数据 */// 内置类型int i = 1024;fwrite.write((const char *) &i, sizeof(i));// 数组std::vector<std::string> stringVec = {"a", "b", "c"};int64_t vecTotalSize = sizeof(std::string) * stringVec.size();fwrite.write((const char *) stringVec.data(), vecTotalSize);// 写入数组数据时需要记录总数据的长度fwrite.write((const char *) &vecTotalSize, sizeof(vecTotalSize));// 自定义结构People people;people.name = "lihua";people.age = 21;fwrite.write((const char *) &people, sizeof(people));// 关闭流fwrite.close();
}
void read(std::string &url){std::ifstream fread;fread.open(url, std::ofstream::binary);if (!fread.is_open()) {std::cout << "open url fail" << std::endl;}/* 读取数据,顺序和写入顺序相反 */// 自定义结构People people;int peopleLen = sizeof(people);fread.seekg(-peopleLen, std::ios::end);fread.read((char *) &people, peopleLen);// 数组int64_t vecSize; // 先读取数组数据的长度const int vecSizeLen = sizeof(vecSize);// std::ios::cur 在 People 数据的结尾处// 读取 vecSize 需要将指针左移到 vecSize 数据的开头// 这就需要经过 peopleLen 和 vecSizeLen 两个长度fread.seekg(-peopleLen - vecSizeLen, std::ios::cur);fread.read((char *) &vecSize, vecSizeLen); //8 bytesint64_t stringVecSize = vecSize / sizeof(std::string); // 根据数组数据的长度算出数组的大小std::vector<std::string> stringVec;stringVec.resize(stringVecSize);fread.seekg(-vecSizeLen - vecSize, std::ios::cur);auto pos = fread.tellg();fread.read((char *) stringVec.data(), vecSize);pos = fread.tellg();// 内置类型int j;int len = sizeof(j);fread.seekg(-vecSize - len, std::ios::cur);fread.read((char*) &j, len);// 关闭流fread.close();
}

string流

同样的,除了继承自 iostream 的操作,sstream 也增加了独有的操作。
在这里插入图片描述

istringstream

我们经常会碰到处理整行字符串的问题,比如:比较版本号

用双指针截取字符串当然是一种方法,但是使用 istringstream 这个标准库提供的利器会更加方便。当然,两种方法的时间、空间复杂度是一样的。

下面通过分析 istringstream 的使用来进一步理解如何用:

class Solution {
public:int compareVersion(string version1, string version2) {istringstream in1(version1); // 将文本version1与输入流in1绑定istringstream in2(version2);int a, b;char c;while(in1.good() || in2.good()){in1 >> a; // 从in1中读取int数据到a中,遇到空白符or非int数据停下in2 >> b;if(a > b) return 1;if(a < b) return -1;a = b = 0;in1 >> c; // 从in1中读取char类型数据到c中,遇到空白符or非char数据停下in2 >> c;}return 0;}
};

再比如,有这样的输入,人名和他们的常用密码,一个人可能有多种常用密码:

cmy 12345 22345
lx 6644
lhy 6633 1221 5665

那么我们可以这样处理:

struct per_pw{string name;vector<string> pw;
}
string s, word; // s暂存来自输入的一行文本
vector<per_pw> people;
while(getline(cin, s)){ // 处理一行文本,也就是一个人的信息per_pw pp;istringstream in(s); // 将in绑定到刚读取的sin >> pp.name; // 读取名字while(in >> word) // 读取密码pp.pw.push_back(word); // 密码存入pp的pw数组中people.push_back(pp); // 将这个人的信息保存在people数组中
}

ostringstream

当我们希望将多个输出最后一起打印时,ostringstream 是很有用的。举个简单的例子:

ostringstream out; // 创建一个未绑定的输出流
vector<string> vs = {"cmy", "lx", "lhy"};
for (string s : vs) {out << s << " ";
}
cout << out.str() << endl; 
// str():返回out保存的string的拷贝,也就是将out转换为string类型。

我们使用标准的输出运算符<<out 写入数据,有趣的是,这些写入操作实际上转换为 string 操作,向 out 中的 string 对象添加字符。

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

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

相关文章

C++ 右值引用 | 左值、右值、move、移动语义、引用限定符

文章目录C11为什么引入右值&#xff1f;区分左值引用、右值引用move移动语义移动构造函数移动赋值运算符合成的移动操作小结引用限定符规定this是左值or右值引用限定符与重载C11为什么引入右值&#xff1f; C11引入了一个扩展内存的方法——移动而非拷贝&#xff0c;移动较之拷…

且谈关于最近软件测试的面试

前段时间有新的产品需要招人&#xff0c;安排和参加了好几次面试&#xff0c;下面就谈谈具体的面试问题&#xff0c;在面试他人的同时也面试自己。 面试问题是参与面试同事各自设计的&#xff0c;我也不清楚其他同事的题目&#xff0c;就谈谈自己设计的其中2道题。 过去面试总是…

C++ 多态 | 虚函数、抽象类、虚函数表

文章目录多态虚函数重写重定义&#xff08;参数不同&#xff09;协变&#xff08;返回值不同&#xff09;析构函数重写&#xff08;函数名不同&#xff09;final和override重载、重写、重定义抽象类多态的原理虚函数常见问题解析虚函数表多态 一种事物&#xff0c;多种形态。换…

C++ 运算符重载(一) | 输入/输出,相等/不等,复合赋值,下标,自增/自减,成员访问运算符

文章目录输出运算符<<输入运算符>>相等/不等运算符复合赋值运算符下标运算符自增/自减运算符成员访问运算符输出运算符<< 通常情况下&#xff0c;输出运算符的第一个形参是一个 非常量ostream对象的引用 。之所以 ostream 是非常量是因为向流写入内容会改变…

C++ 重载函数调用运算符 | 再探lambda,函数对象,可调用对象

文章目录重载函数调用运算符lambdalambda等价于函数对象lambda等价于类标准库函数对象可调用对象与function可调用对象function函数重载与function重载函数调用运算符 函数调用运算符必须是成员函数。 一个类可以定义多个不同版本的调用运算符&#xff0c;互相之间应该在参数数…

C++ 运算符重载(二) | 类型转换运算符,二义性问题

文章目录类型转换运算符概念避免过度使用类型转换函数解决上述问题的方法转换为 bool显式的类型转换运算符类型转换二义性重载函数与类型转换结合导致的二义性重载运算符与类型转换结合导致的二义性类型转换运算符 概念 类型转换运算符&#xff08;conversion operator&#…

分布式理论:CAP、BASE | 分布式存储与一致性哈希

文章目录分布式理论CAP定理BASE理论分布式存储与一致性哈希简单哈希一致性哈希虚拟节点分布式理论 CAP定理 一致性&#xff08;Consistency&#xff09;&#xff1a; 在分布式系统中的所有数据副本&#xff0c;在同一时刻是否一致&#xff08;所有节点访问同一份最新的数据副…

分布式系统概念 | 分布式事务:2PC、3PC、本地消息表

文章目录分布式事务2PC&#xff08;二阶段提交协议&#xff09;执行流程优缺点3PC&#xff08;三阶段提交协议&#xff09;执行流程优缺点本地消息表&#xff08;异步确保&#xff09;分布式事务 分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分…

数据结构算法 | 单调栈

文章目录算法概述题目下一个更大的元素 I思路代码下一个更大元素 II思路代码132 模式思路代码接雨水思路算法概述 当题目出现 「找到最近一个比其大的元素」 的字眼时&#xff0c;自然会想到 「单调栈」 。——三叶姐 单调栈以严格递增or递减的规则将无序的数列进行选择性排序…

最长下降子序列

文章目录题目解法DP暴搜思路代码实现贪心二分思路代码实现题目 给出一组数据 nums&#xff0c;求出其最长下降子序列&#xff08;子序列允许不连续&#xff09;的长度。&#xff08;类似于lc的最长递增子序列&#xff09; 示例&#xff1a; 输入&#xff1a; 6 // 数组元素个…

Linux 服务器程序规范、服务器日志、用户、进程间的关系

文章目录服务器程序规范日志rsyslogd 守护进程syslog函数openlog函数setlogmask函数closelog函数用户进程间的关系进程组会话系统资源限制改变工作目录和根目录服务器程序后台化服务器程序规范 Linux 服务器程序一般以后台进程&#xff08;守护进程[daemon]&#xff09;形式运…

IO模型 :阻塞IO、非阻塞IO、信号驱动IO、异步IO、多路复用IO

文章目录IO模型阻塞IO非阻塞IO信号驱动IO多路复用IO异步IOIO模型 根据各自的特性不同&#xff0c;IO模型被分为阻塞IO、非阻塞IO、信号驱动IO、异步IO、多路复用IO五类。 最主要的两个区别就是阻塞与非阻塞&#xff0c;同步与异步。 阻塞与非阻塞 阻塞与非阻塞最主要的区别就…

Tomcat服务器集群与负载均衡实现

一、前言 在单一的服务器上执行WEB应用程序有一些重大的问题&#xff0c;当网站成功建成并开始接受大量请求时&#xff0c;单一服务器终究无法满足需要处理的负荷量&#xff0c;所以就有点显得有点力不从心了。另外一个常见的问题是会产生单点故障&#xff0c;如果该服务器坏掉…

Linux服务器 | 事件处理模式:Reactor模式、Proactor模式

文章目录Reactor模式Proactor模式同步I/O模型模拟Proactor模式两者的优缺点ReactorProactor同步I/O模型通常用于实现 Reactor 模式&#xff0c;异步I/O模型通常用于实现 Proactor 模式。&#xff08;不是绝对的&#xff0c;同步I/O也可模拟出 Proactor 模式&#xff09; React…

Linux服务器 | 服务器模型与三个模块、两种并发模式:半同步/半异步、领导者/追随者

文章目录两种服务器模型及三个模块C/S模型P2P模型I/O处理单元、逻辑单元、存储单元并发同步与异步半同步/半异步模式变体&#xff1a;半同步/半反应堆模式改进&#xff1a;高效的半同步/半异步模式领导者/追随者模式组件 &#xff1a;句柄集、线程集、事件处理器工作流程两种服…

字符串匹配之KMP(KnuthMorrisPratt)算法(图解)

文章目录最长相等前后缀next数组概念代码实现图解GetNext中的回溯改进代码实现代码复杂度分析最长相等前后缀 给出一个字符串 ababa 前缀集合&#xff1a;{a, ab, aba, abab} 后缀集合&#xff1a;{a, ba, aba, baba} 相等前后缀 即上面用同样颜色标识出来的集合元素&#…

Android入门(一) | Android Studio的配置与使用

文章目录安装配置Android Studio使用Android Studio模拟器更改Android SDK的路径Hello World&#xff01;安装配置Android Studio 从这一步开始&#xff1a; 一直点 next 即可&#xff0c;直到存储路径的选择上&#xff0c;可以放到非 C 盘&#xff0c;这里我放到 D 盘了&am…

Android 入门(四) | Intent 实现 Activity 切换

文章目录Intent显式 Intent定义两个 xml 文件android:orientationmatch_parent 和 wrap_contentIntent函数定义两个 Activity隐式 Intent更多隐式 Intent 的用法用隐式 Intent 打开系统浏览器自建 Activity 以响应打开网页的 Intent向下一个活动传递数据返回数据给上一个活动In…

Android入门(二) | 项目目录及主要文件作用分析

文章目录项目目录分析app目录分析AndroidManifest.xml 分析MainActivity.kt 分析build.gradle 分析最外层目录下的 build.gradleapp 目录下的 build.gradle项目目录分析 我们来看一下 src/main/res 下的一些文件&#xff1a; .gradle 和 .idea &#xff1a;这两个目录下放置…

Android入门(三) | Android 的日志工具 Logcat

文章目录日志工具类 android.util.LogLogcat 中的过滤器日志工具类 android.util.Log Log 从属日志工具类 android.util.Log &#xff0c;该类提供了五个方法供我们打印日志&#xff1a; Log.v() &#xff1a;用于打印那些最为琐碎的、意义最小的日志信息。对应级别 verbose&…