【C++学习】 IO 流揭秘:高效数据读写的最佳实践
✨ 今朝有酒今朝醉,明日愁来明日愁 🌏
📃个人主页:island1314
🔥个人专栏:C++学习
⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏 💞 💞 💞
引言:
🚀C++ IO 流(输入输出流)是一种用于处理数据流的机制,允许程序读取和写入数据。它包括
- 标准输入流 (
std::cin
) - 标准输出流 (
std::cout
) - 标准错误流 (
std::cerr
) - 文件流 (
std::ifstream
和std::ofstream
)。
🔥🔥🔥 这些流对象利用流类(如 istream
和 ostream
)进行数据的读取和写入,同时支持各种格式化操作和错误处理。
1. C语言的输入与输出
🥝 C语言中我们用到的最频繁的输入输出方式就是 scanf ( ) 与 printf ( )。
- scanf() : 从标准输入设备(键盘)读取数据,并将值存放在变量中。
- printf() : 将指定的文字/字符串输出到标准输出设备(屏幕)。
💢注:对宽度输出和精度输出控制。C语言借助了相应的缓冲区来进行输入与输出。
如下图所示:
🔥下面来看下对输入输出缓冲区的理解:
- 可以屏蔽掉低级 I/O 的实现,低级I/O的实现依赖操作系统本身内核的实现,所以如果能够屏蔽这部分的差异,可以很容易写出可移植的程序。
- 可以使用这部分的内容实现 “行” 读取的行为,对于计算机而言是没有 “行” 这个概念,有了这部分,就可以定义“行”的概念,然后解析缓冲区的内容,返回一个“行”。
💢总的来说:在 C++ 中,输入输出缓冲区用于优化数据的读取和写入操作。对于输出流,数据首先被写入缓冲区,然后才实际写入目标设备(如屏幕或文件),这样可以减少每次操作的系统调用次数。对于输入流,数据先从设备读取到缓冲区,再从缓冲区提供给程序。这种机制可以提高效率,但有时可能需要手动刷新缓冲区(如使用
std::flush
)或处理缓冲区的状态(如检查流是否处于良好状态)。
2. 流是什么?
从语言文字层面来看:
- “流” 即是流动的意思,是物质从一处向另一处流动的过程,是对一种有序连续且具有方向性的数据 ( 其单位可以是bit,byte,packet ) 的抽象描述。
💢而在 C++ 标准输入输出库中,流(stream)指的是一种用于处理数据输入和输出的机制。具体来说,它指信息从外部输入设备(如键盘)向计算机内部(如内存)输入和从内存向外部输出设备(显示器)输出的过程。这种输入输出的过程被形象的比喻为“流”。
🐛 它的特性是:有序连续、具有方向性
为了实现这种流动, C++定义了 I/O 标准类库,这些每个类都称为流/流类,用以完成某方面的功能
3. C++ ios类
3.1 概念
🍐 ios_base 类是基类,ios类 继承了 ios_base。通过 ios又分别设置了两个子类istream 和 ostream 。这两个类分别都有一个实例对象 cin和 cout!此外ostream还要标准错误cerr和日志输出clog。除了标准IO外,istream 和 ostream 还有子类,文件流和string流,来提供特殊的使用!
C++ 的 I/O 流主要包括以下几类:
-
输入流:
std::istream
:基本输入流类。std::ifstream
:用于从文件读取数据。std::istringstream
:用于从字符串读取数据。std::cin
:标准输入流,通常与键盘交互。
-
输出流:
std::ostream
:基本输出流类。std::ofstream
:用于向文件写入数据。std::ostringstream
:用于向字符串写入数据。std::cout
:标准输出流,通常用于屏幕显示。
-
双向流:
std::iostream
:继承了std::istream
和std::ostream
,用于同时进行输入和输出操作。std::fstream
:用于在文件中同时进行读写操作。std::stringstream
:提供了在内存中进行读写操作的能力。
-
错误和日志流:
std::cerr
:标准错误流,用于输出错误信息。std::clog
:标准日志流,用于输出日志信息。
这些流对象都定义在 <iostream>
、<fstream>
和 <sstream>
头文件中。
📆 其中值得注意的是 cerr、clog 是很少用的,它们本来是用做 错误输出 和 日志输出 的,但是因为 cout 也能做到,所以这两个很少被使用。
3.2 ios类及派生类
🍌C++系统实现了一个庞大的类库,其中ios为基类,其他类都是直接或间接派生自ios类
-
<ios>:
ios
类是所有输入输出流类的基类,提供了流的基本功能,如流状态标志、格式设置、同步等。- 主要功能包括流状态标志(如
failbit
、eofbit
)、流的格式设置(如precision
、width
)、同步机制等。
-
<iostream>:
iostream
是从ios
类派生出来的,结合了输入和输出流的功能。- 它通过包含
istream
和ostream
来实现输入和输出的功能。
-
<istream>:
istream
类用于输入流操作,负责从输入设备(如键盘、文件)读取数据。- 它是
ios
的派生类,并扩展了输入操作的功能。
-
<ostream>:
ostream
类用于输出流操作,负责将数据写入输出设备(如屏幕、文件)。- 它也是
ios
的派生类,并扩展了输出操作的功能。
-
<ifstream> 和 <ofstream> :
ifstream
(输入文件流)和ofstream
(输出文件流)是istream
和ostream
的派生类,分别用于从文件读取数据和向文件写入数据。- 这些类提供了文件流的特定功能,并继承了
ios
和istream
/ostream
的基本功能。
-
<fstream>:
fstream
类结合了ifstream
和ofstream
的功能,支持通过流的方式来读写文件。提供了对文件的输入和输出操作。- 它是
iostream
的一个派生类,用于处理文件的读写操作。
-
<sstream>:
- 对于 stringsream:字符串常用这个
- 用来支持字符串的序列化与反序列化,多用于网络,与流关系不大
3.3 主要功能
-
流状态:
ios
提供了流状态的管理功能,比如检测输入输出操作是否成功(goodbit
)、是否遇到错误(failbit
)、是否到达文件末尾(eofbit
)。 -
格式设置:
ios
允许设置和查询流的格式,包括数字的精度、宽度、对齐方式等。 -
同步:
ios
支持流的同步机制,确保多线程环境下流操作的安全性。
3.4 案例代码
🌰下面是一个简单的示例,展示了如何使用这些流类:
#include <iostream>
#include <fstream>
int main() {
// 输出到标准输出流
std::cout << "Hello, World!" << std::endl;
// 从标准输入流读取数据
int number;
std::cout << "Enter a number: ";
std::cin >> number;
// 输出到文件流
std::ofstream outFile("example.txt");
if (outFile.is_open()) {
outFile << "The number is: " << number << std::endl;
outFile.close();
}
else {
std::cerr << "Failed to open file." << std::endl;
}
return 0;
}
在这个示例中:
std::cout
是ostream
类的一个实例,用于输出数据到屏幕。std::cin
是istream
类的一个实例,用于从标准输入读取数据。std::ofstream
是ostream
的派生类,用于将数据写入文件。
总的来说:
🍅ios
是 C++ 流库中的核心类,提供了流的基本功能和状态管理。istream
和 ostream
继承自 ios
,分别处理输入和输出,而 ifstream
、ofstream
和 fstream
进一步扩展了对文件的输入输出操作。
4. C++ 标准 IO 流(istream)
🍐C++标准库提供了4个全局流对象cin、cout、cerr、clog,使用cout进行标准输出,即数据从内存流向控制台(显示器)。使用cin进行标准输入即数据通过键盘输入到程序中,同时C++标准库还提供了cerr用来进行标准错误的输出,以及clog进行日志的输出,从上图可以看出:cout、cerr、clog是ostream类的三个不同的对象,因此这三个对象现在基本没有区别,只是应用场景不同!
🐛 注: 在使用时候必须要包含文件并引入 std 标准命名空间。
注意:
- cin为缓冲流。键盘输入的数据保存在缓冲区中,当要提取时,是从缓冲区中拿。如果一次输入过多,会留在那儿慢慢用,如果输入错了,必须在回车之前修改,如果回车键按下就无法挽回了。只有把输入缓冲区中的数据取完后,才要求输入新的数据。除此之外, cin 不能重定向。
- cin 输入的数据类型必须与要提取的数据类型一致,否则出错。出错只是在流的状态字state中对应位置位(置1),程序继续。
- 空格和回车都可以作为数据之间的分格符,所以多个数据可以在一行输入,也可以分行输入。但如果是字符型和字符串,则空格(ASCII码为32)无法用cin输入,字符串中也不能有空格。回车符也无法读入。如果想将字符串整行读取,则要使用getline()。
#include<iostream> using namespace std; int main() { // 输入 hello world string a; cin >> a; cout << a << endl; // hello cin >> a; cout << a << endl; // world getline(cin,a); cout << a << endl; return 0; }
- cin和cout可以直接输入和输出 内置类型数据,原因:标准库已经将所有内置类型的输入和输出全部重载了。
-
对于 自定义类型,如果要支持cin和cout的标准输入输出,需要对<<和>>进行重载。
-
发生错误时,系统需要立即输出以提醒用户,因此错误输出流对象 cerr 不具备缓冲区。
-
在线OJ中的输入和输出:
① 对于IO类型的算法,一般都需要循环输入。
② 输出:严格按照题目的要求进行,多一个少一个空格都不行。
③ 连续输入时,vs系列编译器下在输入ctrl+Z时结束。
④ scanf函数当读取发生错误或读到文件末尾,会返回EOF(-1)。 -
istream类型对象转换为逻辑条件判断值。
istream& operator>> (int& val); explicit operator bool() const;
实际上我们看到使用 while(cin>>i) 去流中提取对象数据时,调用的是operator>>,返回值是istream类型的对象,那么这里可以做逻辑条件值,源自于 istream 的对象又调用了 operator bool,operator bool 调用时如果接收流失败,或者有结束标志,则返回false。
5. C++ 文件 IO 流(fstream)
在C++中,文件输入输出(I/O)流是处理文件读写操作的关键。标准库提供了一套灵活的文件流机制,包括输入流(ifstream
)、输出流(ofstream
)以及同时支持输入和输出的流(fstream
)。以下是有关这些类和它们的用法的详细信息。
C++根据文件内容的数据格式分为二进制文件和文本文件。采用文件流对象操作文件的一般步骤如下:
- 定义一个文件流对象:
☑️ifstream(只输入用)
☑️ofstream(只输出用)
☑️fstream(既输入又输出用) (继承了ifstream和ofstream)- 使用文件流对象的成员函数打开一个磁盘文件,使得文件流对象和磁盘文件之间建立联系 。open:打开文件,可以设置对应的打开方式和C语言很类似。
- 使用提取和插入运算符对文件进行读写操作,或使用成员函数进行读写。关闭文件。
🍋🟩文件流类
1. 🍉ifstream
ifstream(输入文件流)
用于从文件中读取数据。你可以使用它来打开一个文件,并从中读取内容。
常用操作:
- 打开文件:通过构造函数或
open()
方法。 - 读取数据:使用
>>
运算符或getline()
方法。 - 检查状态:使用
eof()
,fail()
,bad()
,good()
等方法。 - 关闭文件:使用
close()
方法。
示例:
#include <iostream>
#include <fstream>
#include <string>
int main() {
std::ifstream inFile("input.txt"); // 打开文件
if (!inFile.is_open()) { // 检查文件是否成功打开
std::cerr << "Unable to open file for reading" << std::endl;
return 1;
}
std::string line;
while (std::getline(inFile, line)) { // 逐行读取
std::cout << line << std::endl; // 输出到控制台
}
inFile.close(); // 关闭文件
return 0;
}
2. 🥑ofstream
ofstream(输出文件流)
用于向文件中写入数据。你可以使用它来创建新文件或覆盖现有文件的内容。
常用操作:
- 打开文件:通过构造函数或
open()
方法。 - 写入数据:使用
<<
运算符。 - 关闭文件:使用
close()
方法。
示例:
#include <iostream>
#include <fstream>
int main() {
std::ofstream outFile("output.txt"); // 打开文件
if (!outFile.is_open()) { // 检查文件是否成功打开
std::cerr << "Unable to open file for writing" << std::endl;
return 1;
}
outFile << "Hello, World!" << std::endl; // 写入数据
outFile << "This is a test." << std::endl;
outFile.close(); // 关闭文件
return 0;
}
3. 🥝fstream
fstream(文件流)
支持同时读写操作。你可以用它来打开一个文件进行读写操作,而不仅仅是读取或写入。
常用操作:
- 打开文件:通过构造函数或
open()
方法,指定读写模式。 - 读取和写入数据:使用
>>
和<<
运算符。 - 关闭文件:使用
close()
方法。
示例:
#include <iostream>
#include <fstream>
#include <string>
int main() {
std::fstream file("example.txt", std::ios::in | std::ios::out | std::ios::app); // 打开文件
if (!file.is_open()) { // 检查文件是否成功打开
std::cerr << "Unable to open file" << std::endl;
return 1;
}
// 写入数据
file << "Appending this line to the file." << std::endl;
// 重新定位到文件开始
file.seekg(0);
// 读取数据
std::string line;
while (std::getline(file, line)) {
std::cout << line << std::endl;
}
file.close(); // 关闭文件
return 0;
}
🥬文件模式
🍐在打开文件时,可以使用不同的模式来指定文件的打开方式:(std::ios::打开方式)
打开方式 | 功能 |
---|---|
in | Input mode (输入模式)。以读取模式打开文件用于输入操作。 |
out | Output mode (输出模式)。以写入模式打开文件用于输出操作。如果文件已存在,内容将被截断。 |
app | Append mode (追加模式)。在每次写入时,数据将被追加到文件的末尾,而不是覆盖现有内容 |
binary | Binary mode (二进制模式)。以二进制方式打开文件,不进行任何字符转换。这对于非文本文件(如图像或可执行文件)是必要的。 |
ate | At end mode (文件末尾模式)。打开文件时,文件指针定位到文件末尾。 |
trunc | Truncate mode (截断模式)。如果文件已经存在,则在打开时将其长度截断为0,即删除文件中的所有内容(当与 std::ios::out 一起使用时)。 |
🍐 写入操作可以使用<<进行流写入,也可以通过write写入一个缓冲区字符串。读取操作可以通过>>来一个一个字符读取,也可以通过read直接读取到缓冲区中。
示例:
#include <iostream>
#include <fstream>
int main() {
std::fstream file("example.txt", std::ios::out | std::ios::trunc); // 清空文件内容
if (!file.is_open()) { // 检查文件是否成功打开
std::cerr << "Unable to open file" << std::endl;
return 1;
}
file << "This is a new file content." << std::endl;
file.close(); // 关闭文件
return 0;
}
总的来说:
🍅C++ 的文件 I/O 流机制提供了强大而灵活的文件操作功能。通过使用 ifstream
、ofstream
和 fstream
,你可以高效地进行文件的读取、写入和同时操作。掌握这些基本用法和模式设置能够帮助你在实际编程中更好地处理文件数据。
6. C++ 流类检查
☘️在 C++ 中,输入输出流(I/O 流)的状态检查是确保流操作成功与否的关键部分。C++ 的标准库提供了几个方法和标志来检查流的状态。这些状态检查方法可以帮助你处理流中的错误,确保程序的稳定性和正确性。
6.1 流的状态标志
C++ 的流类(如 std::ifstream
, std::ofstream
, std::stringstream
等)提供了以下几种主要的状态标志,用于检测流的不同状态:
名称 | 语法 | 功能 |
good() | stream.good() | 检查流是否处于良好的状态。返回 true 如果流没有发生任何错误。 |
fail() | stream.fail() | 检查流是否处于失败状态。通常在流操作失败时(如读取或写入时发生错误)返回 true 。 |
eof() | stream.eof() | 检查流是否到达了文件末尾(EOF)。如果读取操作到达了文件末尾,返回 true 。 |
bad() | stream.bad() | 检查流是否处于坏状态。这是一个严重的错误状态,通常表示流出现了不可恢复的错误(如硬件故障)。一般来说,很少会遇到这种错误! |
清理流状态
在处理流时,可能需要重置流的状态以继续进行操作。可以使用 clear()
函数来重置流的状态标志
file.clear(); // 重置流状态
6.2 标准流的状态检查
int main()
{
string s;
cout << cin.good() << endl;
cout << cin.eof() << endl;
cout << cin.bad() << endl;
cout << cin.fail() << endl << endl;
//istream operator >> (istream & is, string & s);
//while (cin >> s) //和上面等价
while (operator >> (cin, s).operator bool()) {
cout << cin.good() << endl;
cout << cin.eof() << endl;
cout << cin.bad() << endl;
cout << cin.fail() << endl << endl;
cout << s << endl;
}
cout << cin.good() << endl;
cout << cin.eof() << endl;
cout << cin.bad() << endl;
cout << cin.fail() << endl << endl;
// 恢复标志状态,再输入
cin.clear();
while (cin >> s)
{
cout << cin.good() << endl;
cout << cin.eof() << endl;
cout << cin.bad() << endl;
cout << cin.fail() << endl << endl;
cout << s << endl;
}
return 0;
}
6.3 文件流的状态检查
#include <iostream>
#include <fstream>
int main() {
std::ifstream inFile("example.txt");
if (!inFile) { // 检查文件是否成功打开
std::cerr << "Failed to open file." << std::endl;
return 1;
}
std::string line;
while (std::getline(inFile, line)) {
if (inFile.eof()) {
std::cout << "End of file reached." << std::endl;
}
if (inFile.fail()) {
std::cout << "File operation failed." << std::endl;
}
std::cout << line << std::endl;
}
inFile.close(); // 关闭文件
return 0;
}
总的来说:
🍅C++ 提供了一些标准的方法和标志来检查流的状态,这些方法帮助程序员了解流的当前状态,并在发生错误时采取适当的措施。通过合理使用这些方法,可以确保程序的稳定性和可靠性。
7. C++ 非文件流(sstream)
在 C++ 中,除了文件流(如 std::ifstream
和 std::ofstream
),还有其他几种流类型用于处理不同的数据源和目标。以下是一些常见的非文件流类型:
7.1 stringstream
在C语言中,如果想要将一个整形变量的数据转化为字符串格式,如何去做?
- 使用 itoa() 函数
- 使用 sprintf() 函数
但是两个函数在转化时,都得需要先给出保存结果的空间,那空间要给多大呢,就不太好界定,而且转化格式不匹配时,可能还会得到错误的结果甚至程序崩溃。
而在C++中,可以使用 stringstream 类对象来避开此问题。
std::stringstream
是 C++ 标准库中的一个类,用于在内存中处理字符串流。它继承自 std::istream
和 std::ostream
,可以像文件流一样用来读取和写入数据,但数据是在内存中的字符串。
示例:
- 将数值类型数据格式化为字符串
#include<sstream> int main() { int a = 12345678; string sa; // 将一个整形变量转化为字符串,存储到string类对象中 stringstream s; s << a; s >> sa; // clear() // 注意多次转换时,必须使用clear将上次转换状态清空掉 // stringstreams在转换结尾时(即最后一个转换后),会将其内部状态设置为badbit // 因此下一次转换是必须调用clear()将状态重置为goodbit才可以转换 // 但是clear()不会将stringstreams底层字符串清空掉 // s.str(""); // 将stringstream底层管理string对象设置成"", // 否则多次转换时,会将结果全部累积在底层string对象中 s.str(""); s.clear(); // 清空s, 不清空会转化失败 double d = 12.34; s << d; s >> sa; string sValue; sValue = s.str(); // str()方法:返回stringsteam中管理的string类型 cout << sValue << endl; return 0; }
- 字符串拼接
int main() { stringstream sstream; // 将多个字符串放入 sstream 中 sstream << "first" << " " << "string,"; sstream << " second string"; cout << "strResult is: " << sstream.str() << endl; // 清空 sstream sstream.str(""); sstream << "third string"; cout << "After clear, strResult is: " << sstream.str() << endl; return 0; }
- 序列化和反序列化结构数据
struct ChatInfo { string _name; // 名字 int _id; // id Date _date; // 时间 string _msg; // 聊天信息 }; int main() { // 结构信息序列化为字符串 ChatInfo winfo = { "张三", 135246, { 2022, 4, 10 }, "晚上一起看电影吧"}; ostringstream oss; oss << winfo._name << " " << winfo._id << " " << winfo._date << " " << winfo._msg; string str = oss.str(); cout << str << endl << endl; // 我们通过网络这个字符串发送给对象,实际开发中,信息相对更复杂, // 一般会选用Json、xml等方式进行更好的支持 // 字符串解析成结构信息 ChatInfo rInfo; istringstream iss(str); iss >> rInfo._name >> rInfo._id >> rInfo._date >> rInfo._msg; cout << "-------------------------------------------------------" << endl; cout << "姓名:" << rInfo._name << "(" << rInfo._id << ") "; cout << rInfo._date << endl; cout << rInfo._name << ":>" << rInfo._msg << endl; cout << "-------------------------------------------------------" << endl; return 0; }
7.2 istringstream
std::istringstream
是一个输入流类,用于从字符串中读取数据。它提供了与 std::istream
类似的功能,但数据源是字符串。
示例:
#include <iostream>
#include <sstream>
#include <string>
int main() {
std::string data = "123 456.78";
// 创建一个从字符串中读取数据的输入字符串流对象
std::istringstream iss(data);
int number;
double floating;
// 从字符串流中读取数据
iss >> number >> floating;
// 输出结果
std::cout << "Read integer: " << number << std::endl;
std::cout << "Read floating-point: " << floating << std::endl;
return 0;
}
7.3 ostringtream
std::ostringstream
是一个输出流类,用于将数据写入到字符串中。它提供了与 std::ostream
类似的功能,但数据目标是字符串。
示例:
#include <iostream>
#include <sstream>
#include <string>
int main() {
// 创建一个向字符串中写入数据的输出字符串流对象
std::ostringstream oss;
// 向字符串流中写入数据
oss << "Hello, " << "world!" << std::endl;
oss << 123 << " " << 45.67 << std::endl;
// 获取字符串流中的字符串
std::string result = oss.str();
// 输出结果
std::cout << "String stream content: " << result;
return 0;
}
📖 总结
C++ 的 I/O 流系统包括输入流、输出流和双向流。输入流(如 std::cin
、std::ifstream
)用于读取数据,输出流(如 std::cout
、std::ofstream
)用于写入数据,而 双向流(如 std::fstream
、std::stringstream
)则支持同时读写操作。标准错误流(std::cerr
)和标准日志流(std::clog
)用于错误报告和日志记录。这些流对象提供了强大的数据处理功能,是 C++ 编程的核心组件之一。
💞 💞 💞那么本篇到此就结束,希望我的这篇博客可以给你提供有益的参考和启示,感谢大家支持!!!祝大家天天开心