文件操作、流对象示例
一、向文件中写入内容
我们现在桌面创建一个.txt文件,将里面的内容设置为abcdefghijklmn
如果想要向这个文件中写入内容,我们需要借助文件流对象建立起文件与程序之间的联系。
#include<iostream>
#include<fstream>
#include<sstream>
using namespace std;int main()
{ofstream outFile("C:\Users\31604\Desktop\example.txt", ios::app);if (outFile.is_open()){outFile << " opqrst " << endl;outFile.close();}else{throw runtime_error("Fail to open file");cout << endl;}return 0;
}
这么写会报错,因为在C++中,\是转义字符,如果只使用单个反斜杠,会报错:通用字符名的格式不正确。
解决方法有两个:
方法一:加两个反斜杠,就代表我们真的想写入反斜杠。
#include<iostream>
#include<fstream>
#include<sstream>
using namespace std;int main()
{ofstream outFile("C:\\Users\\31604\\Desktop\\example.txt", ios::app);if (outFile.is_open()){outFile << " opqrst " << endl;outFile.close();}else{throw runtime_error("Fail to open file");cout << endl;}return 0;
}
方法二:使用原始字符串字面量。从 C++11 开始,可以使用原始字符串字面量,在字符串前加上R,并用 (
)
括起字符串内容,这样字符串中的反斜杠就不会被当作转义字符处理。代码如下:
#include<iostream>
#include<fstream>
#include<sstream>
using namespace std;int main()
{ofstream outFile(R"(C:\Users\31604\Desktop\example.txt)", ios::app);if (outFile.is_open()){outFile << " opqrst " << endl;outFile.close();}else{throw runtime_error("Fail to open file");cout << endl;}return 0;
}
我们此时再打开这个example.txt文件,就会发现里面的内容变成了这样:
说明成功写入。
需要注意的是,当我们在 IDE中运行程序时,程序的当前工作目录不一定是桌面。所以它可能尝试在其他目录中创建或追加到名为example.txt的文件,而不是桌面上的那个文件。
ios::app是什么?
ofstream(输出文件流)对象的构造函数有多个重载版本,以下是两个常见的版本:
ofstream(const char* filename);//只指定文件路径
ofstream(const char* filename,ios_base::openmode mode);//指定文件路径和打开方式
ios_base::openmode是一个位掩码类型,定义了多种打开模式,常见的有:
ios_base::app 追加模式,写入数据时将数据追加到文件末尾
ios_base::ate 文件打开后立即将文件指针移动到文件末尾
ios_base::in 以读模式打开文件(通常用于ifstream)
ios_base::out 以写模式打开文件(默认覆盖写入)
ios_base::trunc 如果文件存在,截断文件,即删除原有内容
这些模式可以通过按位或(|)运算符组合使用。例如:
#include <iostream>
#include <fstream>int main()
{// 使用 ios_base 设置文件打开模式std::ofstream outFile("example.txt", std::ios_base::out | std::ios_base::trunc);if (outFile.is_open()) {// 使用 ios_base 设置格式标志outFile << std::ios_base::hex << 100 << std::endl; // 以十六进制输出 100,输出结果为 64outFile.close();}return 0;
}
追问:ios_base和ios有什么区别?
ios_base类是ios类的父类,ios类是istream类和ostream类的父类。
ios_base类定义了一些基本的流操作特性和常量,比如格式标志(控制输入输出的格式)、流操作符等。当涉及到文件打开模式、格式标志(例如控制数字输出的进制)这些基础的流操作特性常量时,要用ios_base.例如ios_base::in(用于输入文件流打开方式)、ios_base::hex(设置十六进制输出格式)等。
而当我们需要处理流的状态管理,比如判断流是否处于良好状态(ios::good())、是否到达文件末尾(ios::eof()),或者设置和获取流的错误状态标志等操作时,就使用ios类及其派生类(istream\ostream等)
如果是进行输入输出操作,比如从输入流读取数据,或者向输出流写入数据,则使用istream和ostream及其派生类(ifstream\ofstream等)。
举一个使用ios管理流状态的例子:
#include <iostream>
#include <sstream>int main()
{std::string str = "123";std::istringstream iss(str);int num;iss >> num;if (iss.good()) {std::cout << "成功读取数字: " << num << std::endl;} else if (iss.eof()) {std::cout << "到达流末尾,但可能读取不完整。" << std::endl;} else {std::cout << "读取失败。" << std::endl;}return 0;
}
需要注意的是,ios类尽管确实是从ios_base类public继承而来的,但那些用于文件打开模式的常量(ios_base::out、ios_base::trunc等),它们并不是以常规的成员变量或成员函数的访问控制方式来决定是否能在子类ios中直接使用。这些常量是在ios_base的作用域内定义的枚举类型成员。虽然ios继承了ios_base的成员,但在 C++ 语法规则里,要使用这些枚举常量,必须通过定义它们的类名(也就是ios_base)来限定访问,即便继承是public的。
二、将文件中的内容读入字符串中
#include<iostream>
#include<fstream>
#include<sstream>
using namespace std;int main()
{ifstream inFile(R"(C:\Users\31604\Desktop\example.txt)");if (inFile.is_open()){string line;while (getline(inFile, line)){cout << line << endl;}inFile.close();}else{cerr << "无法打开文件" << endl;}return 0;
}
运行结果:
对getline()不熟悉的,请参阅:对流对象的理解-CSDN博客
我们还可以指定分隔符,不一定读到默认的换行符,代码如下:
#include<iostream>
#include<fstream>
#include<sstream>
using namespace std;int main()
{ifstream inFile(R"(C:\Users\31604\Desktop\example.txt)");if (inFile.is_open()){string line;while (getline(inFile, line,'g')){cout << line << endl;}inFile.close();}else{cerr << "无法打开文件" << endl;}return 0;
}
运行结果:
也就是说,在重载版本getline(istream&is,string&str,char ch)中,ch作为分隔符,充当了原本换行符的角色。换行后,getline()会继续读入,会覆盖原有str中的内容,所以输出结果就会如上所示。
三、使用istringstream读取字符串中的内容
#include<iostream>
#include<fstream>
#include<sstream>
#include<string>
using namespace std;int main()
{string str = "123 45.6 hello";istringstream iss(str);//构造函数int int_val;double double_val;string string_val;iss >> int_val;iss >> double_val;iss >> string_val;cout << "读取的整数:" << int_val << endl;cout << "读取的双精度浮点数:" << double_val << endl;cout << "读取的字符串:" << string_val << endl;return 0;
}
运行结果:
如果数据类型和读取的顺序不一致,例如下面这样:
#include<iostream>
#include<fstream>
#include<sstream>
#include<string>
using namespace std;int main()
{string str = "123 four 78.9 hello";istringstream iss(str);//构造函数int int_val;double double_val;string string_val;iss >> int_val;iss >> double_val;iss >> string_val;cout << "读取的整数:" << int_val << endl;cout << "读取的双精度浮点数:" << double_val << endl;cout << "读取的字符串:" << string_val << endl;return 0;
}
会显示 读取的双精度浮点数为0,读取的字符串为空
因为从输入流iss读取数据时,它期望读取一个符合双精度浮点数格式的值。然而在读取完整数123后,下一个非空白字符序列是"four",这不是一个有效的双精度浮点数表示。
当istringstream尝试将"four"转换为双精度浮点数时,转换失败。这会导致输入流进入错误状态(failbit被设置),并且后续从该流的读取操作(如iss>>string_val;)将不再进行,因为一旦流处于错误状态,它会忽略后续的读取请求。
要解决这个问题,可以在每次读取操作后检查流的状态,以确保读取成功。例如:
#include <iostream>
#include <sstream>
#include <string>
using namespace std;int main()
{string str = "123 four 78.9 hello";istringstream iss(str);int int_val;double double_val;string string_val;if (!(iss >> int_val)) {cerr << "无法读取整数" << endl;return 1;}if (!(iss >> double_val)) {// 跳过 "four" 并尝试重新读取双精度浮点数string skip;iss >> skip;if (!(iss >> double_val)) {cerr << "无法读取双精度浮点数" << endl;return 1;}}if (!(iss >> string_val)) {cerr << "无法读取字符串" << endl;return 1;}cout << "读取的整数:" << int_val << endl;cout << "读取的双精度浮点数:" << double_val << endl;cout << "读取的字符串:" << string_val << endl;return 0;
}
四、使用ostringstream向字符串中写入内容
#include<iostream>
#include<fstream>
#include<sstream>
#include<string>
using namespace std;int main()
{int num = 100;double pi = 3.14159;string name = "kvermouth";ostringstream oss;oss << "数字:" << num << ",圆周率:" << pi << ",名字:" << name;//写入内容string res = oss.str();//oss.str()用于获取最终生成的字符串cout << res << endl;return 0;
}
运行结果:
五、使用stringstream,既能读,又能写
#include<iostream>
#include<fstream>
#include<sstream>
#include<string>
using namespace std;int main()
{stringstream ss;int num = 66;string str = "world";//写入ss << "Hello," << num << " " << str;//读取string new_str;ss >> new_str;cout << new_str << endl;return 0;
}
运行结果:
为什么输出的结果不是Hello,66 world呢?
- 首先,代码中创建了一个stringstream对象ss,然后将数据写入这个流中。执行 ss << "Hello," << num << " " << str;时,stringstream会按照顺序将"Hello"、整数66(转换为字符串形式"66")、一个空格字符" "以及字符串"world"连接起来写入流中。此时stringstream内部维护的缓冲区内容大致为"Hello,66 world"
- 接着执行ss>>new str; stringstream从其缓冲区读取数据时,会以空白字符(空格、制表符、换行符等)作为分隔符。所以它会读取到第一个空白字符(这里是空格)之前的内容,也就是"Hello,66"并将其存储到new_str中。
如果我们想要读取整个字符串的内容,可以使用getline()
#include<iostream>
#include<fstream>
#include<sstream>
#include<string>
using namespace std;int main()
{stringstream ss;int num = 66;string str = "world";//写入ss << "Hello," << num << " " << str;//读取string new_str;getline(ss, new_str);cout << new_str << endl;return 0;
}