C++中的文件操作
使用C++标准库进行文件操作
1. 概述
C++标准库提供了 <fstream>
头文件中的几个类来进行文件操作,这些类封装了底层的文件操作,提供了面向对象和类型安全的接口,使得文件读写更加便捷和高效。主要的文件流类包括:
std::ifstream
:用于从文件中读取数据。
std::ofstream
:用于向文件中写入数据。
std::fstream
:用于同时读取和写入文件。
这些文件流类(std::ifstream、std::ofstream、std::fstream)继承自 std::istream、std::ostream 和 std::iostream,这些类本身继承自 std::ios,从而提供了丰富的成员函数和操作符来处理文件I/O。
更详细的关于继承关系的介绍:C++标准库中文件流类的继承关系
2. 文件流类详解
2.1 std::ifstream
:输入文件流
std::ifstream 用于从文件中读取数据。它继承自 std::istream,具备所有输入流的功能,同时添加了文件特有的操作。
例子:
#include <fstream>
#include <iostream>
#include <string>
int main() {
// 创建并打开输入文件流
std::ifstream infile("input.txt"); // "input.txt" 是要读取的文件名
// 检查文件是否成功打开
if (!infile) {
std::cerr << "无法打开文件 input.txt" << std::endl;
return 1;
}
// 读取并输出文件内容
std::string line;
while (std::getline(infile, line)) {
std::cout << line << std::endl;
}
// 关闭文件流(可选,因为析构函数会自动关闭)
infile.close();
return 0;
}
要点:
-
构造函数可以在创建对象时指定文件名和打开模式。
-
检查文件是否成功打开,以避免后续操作错误。
-
使用 std::getline 逐行读取文件内容。
补充:
对std::ifstream infile("input.txt");
的解释:
-
std::ifstream
定义:std::ifstream 是 C++ 标准库中用于输入文件流的类,定义在
<fstream>
头文件中。用途:用于从文件中读取数据。ifstream 代表“输入文件流”(input file stream)。
继承关系:std::ifstream 继承自 std::istream,而 std::istream 又继承自 std::ios,提供了丰富的成员函数和操作符来处理文件输入操作。
-
infile
定义:infile 是一个变量名,是 std::ifstream 类的一个对象实例。在这里,infile 可以被认为是一个文件读取流,用于从指定的文件中读取数据。
作用:通过 infile,你可以执行各种读取操作,例如读取单词、行、整个文件内容,甚至是二进制数据。
-
构造函数
std::ifstream infile("input.txt")
功能:这行代码做了两件事:
-
创建对象:声明并创建了一个 std::ifstream 对象,名为 infile。
-
打开文件:在创建对象的同时,通过构造函数参数 "input.txt" 指定要打开的文件名。默认情况下,std::ifstream 会以只读模式打开文件。
过程:
-
当 infile 对象被创建时,std::ifstream 的构造函数会尝试打开 "input.txt" 文件。
-
如果文件成功打开,infile 就可以用于后续的读取操作。
-
如果文件无法打开(例如文件不存在或权限不足),infile 的状态会被设置为失败,你可以通过检查流的状态来处理这种情况。
-
-
infile 的灵活性
-
读取不同类型的数据:
你可以使用不同的方法和操作符来读取文件内容。例如,使用
>>
操作符从文件中读取单词,或者使用read()
方法读取二进制数据。 -
错误处理:
除了检查文件是否成功打开,还可以检查读取操作是否成功,例如使用
infile.good()
,infile.eof()
,infile.fail()
等成员函数。 -
多种打开模式:
std::ifstream 还支持多种打开模式,通过构造函数的第二个参数指定。例如,以二进制模式打开文件:
std::ifstream infile("input.bin", std::ios::binary);
-
2.2 std::ofstream
:输出文件流
std::ofstream
用于向文件中写入数据。它继承自 std::ostream,具备所有输出流的功能,同时添加了文件特有的操作。
例子:
#include <fstream>
#include <iostream>
#include <string>
int main() {
// 创建并打开输出文件流
std::ofstream outfile("output.txt"); // "output.txt" 是要写入的文件名
// 检查文件是否成功打开
if (!outfile) {
std::cerr << "无法打开文件 output.txt" << std::endl;
return 1;
}
// 写入数据到文件
outfile << "Hello, World!" << std::endl;
outfile << "这是第二行文本。" << std::endl;
// 关闭文件流(可选,因为析构函数会自动关闭)
outfile.close();
return 0;
}
要点:
-
默认情况下,std::ofstream 以写入模式打开文件。如果文件不存在,会创建新文件;如果文件存在,会截断文件内容。
-
使用
<<
操作符向文件中插入数据。
2.3 std::fstream
:文件流(读写)
std::fstream 用于同时读取和写入文件。它继承自 std::iostream,结合了 std::istream 和 std::ostream 的功能
例子:
#include <fstream>
#include <iostream>
#include <string>
int main() {
// 创建并打开文件流,设置为读写模式
std::fstream file("file.txt", std::ios::in | std::ios::out | std::ios::app);
// 检查文件是否成功打开
if (!file) {
std::cerr << "无法打开文件 file.txt" << std::endl;
return 1;
}
// 写入数据到文件
file << "追加一行内容。" << std::endl;
// 移动读指针到文件开头
file.seekg(0, std::ios::beg);
// 读取并输出文件内容
std::string line;
while (std::getline(file, line)) {
std::cout << line << std::endl;
}
// 关闭文件流
file.close();
return 0;
}
要点:
-
通过构造函数的第二个参数指定文件打开模式,可以组合多个模式。
-
适用于需要同时进行读取和写入操作的场景。
3. 打开和关闭文件
文件流类提供了多种方式来打开和关闭文件。
使用构造函数打开文件
可以在创建文件流对象时,通过构造函数直接指定文件名和打开模式。
比如:
std::ifstream infile("input.txt"); // 以默认模式(即只读方式)打开 input.txt
std::ofstream outfile("output.txt", std::ios::out | std::ios::trunc); // 以写入和截断模式打开 output.txt
使用 open() 成员函数
如果在创建对象时没有指定文件名,可以使用 open()
成员函数在后续打开文件。
比如:
#include <fstream>
#include <iostream>
int main() {
std::ifstream infile; // 创建输入文件流对象
infile.open("input.txt"); // 打开文件
if (!infile.is_open()) {
std::cerr << "无法打开文件 input.txt" << std::endl;
return 1;
}
// 进行读取操作...
infile.close(); // 关闭文件
return 0;
}
关闭文件
使用 close()
成员函数可以显式关闭文件流。尽管在对象销毁时,析构函数会自动关闭文件,但在需要提前释放资源时,显式调用 close() 是有用
的。
比如:
infile.close();
outfile.close();
4. 文件打开模式
文件打开模式通过 std::ios::openmode
枚举类型指定,可以组合使用多个模式。
常用的打开模式:
-
std::ios::in
:以读取模式打开文件。 -
std::ios::out
:以写入模式打开文件。 -
std::ios::app
:以追加模式打开文件,写入操作将添加到文件末尾。 -
std::ios::ate
:打开文件后,将文件指针定位到文件末尾。 -
std::ios::trunc
:如果文件已存在,则截断文件长度为0(默认与 std::ofstream 相关)。 -
std::ios::binary
:以二进制模式打开文件。
比如:
// 以读写模式打开文件,不截断现有内容
std::fstream file("data.txt", std::ios::in | std::ios::out);
// 以二进制模式写入数据, 截断现有文件内容
std::ofstream binaryOut("data.bin", std::ios::out | std::ios::binary | std::ios::trunc);
说明:
-
组合使用:通过按位或操作符
|
组合多个打开模式,如std::ios::in | std::ios::out
表示同时具备读取和写入权限。 -
二进制模式:对于非文本文件(如图片、音频等),应使用
std::ios::binary
模式,以防止数据在读取或写入过程中被转换。
5. 读取文件
5.1 使用 >>
操作符读取单词
>>
操作符用于从文件中提取数据,自动跳过空白字符(如空格、换行符、制表符)。
比如:
假设 words.txt 文件内容如下:
运行以下程序:
#include <fstream>
#include <iostream>
#include <string>
int main() {
std::ifstream infile("words.txt");
if (!infile) {
std::cerr << "无法打开文件 words.txt" << std::endl;
return 1;
}
std::string word;
while (infile >> word) { // 逐词读取
std::cout << "读取到的单词: " << word << std::endl;
}
infile.close();
return 0;
}
输出如下:
说明:
-
适用于逐词读取数据,适合处理由空白字符分隔的数据。
-
无法读取包含空格的完整句子或短语。
5.2 使用 std::getline
逐行读取
std::getline
函数用于从文件中逐行读取数据,适用于处理文本文件中的行数据。
比如:
假设 lines.txt 文件内容如下:
执行以下代码:
#include <fstream>
#include <iostream>
#include <string>
int main() {
std::ifstream infile("lines.txt");
if (!infile) {
std::cerr << "无法打开文件 lines.txt" << std::endl;
return 1;
}
std::string line;
while (std::getline(infile, line)) { // 逐行读取
std::cout << "读取到的行: " << line << std::endl;
}
infile.close();
return 0;
}
输出如下:
说明:
-
适用于逐行处理文件内容,保留每行的完整信息。
-
可以处理包含空格和其他特殊字符的行。
5.3 读取整个文件内容
有时需要一次性读取整个文件内容,尤其适用于小文件或需要对整个文件内容进行处理的场景。
比如:
假设 content.txt 文件内容如下:
执行以下代码:
#include <fstream>
#include <iostream>
#include <string>
int main() {
std::ifstream infile("content.txt");
if (!infile) {
std::cerr << "无法打开文件 content.txt" << std::endl;
return 1;
}
// 使用迭代器读取整个文件内容到字符串
std::string content((std::istreambuf_iterator<char>(infile)),
std::istreambuf_iterator<char>());
std::cout << "文件内容:\n" << content << std::endl;
infile.close();
return 0;
}
输出如下:
说明:
-
使用 std::istreambuf_iterator 迭代器可以高效地读取整个文件内容。
-
适用于小至中等大小的文件,对于非常大的文件,可能需要分块读取以避免内存问题。
-
对此行代码的理解:
std::string content((std::istreambuf_iterator<char>(infile)), std::istreambuf_iterator<char>());
-
这行代码使用了 std::istreambuf_iterator 来从输入文件流 infile 读取内容并构建一个 std::string 对象。具体理解如下:
-
std::istreambuf_iterator
(infile):创建一个输入迭代器,从 infile 的缓冲区读取字符。 -
std::istreambuf_iterator
():这是一个空的迭代器,用于表示输入结束。
构造 std::string:通过传入两个迭代器,构造函数会从 infile 中读取所有字符,直到遇到结束迭代器。 -
最终,这行代码的作用是将整个文件的内容读入到 content 字符串中。这样,你就可以方便地对文件内容进行操作。
-
5.4 读取二进制数据
对于非文本文件(如图片、音频、视频等),需要以二进制模式读取数据,以防止数据在读取过程中被转换或丢失。
比如:
假设需要读取一个图片文件 image.jpg 并输出其大小。
执行以下代码:
#include <fstream>
#include <iostream>
#include <vector>
int main() {
// 以二进制模式打开文件
std::ifstream infile("image.jpg", std::ios::binary);
if (!infile) {
std::cerr << "无法打开文件 image.jpg" << std::endl;
return 1;
}
// 移动读指针到文件末尾以获取文件大小
infile.seekg(0, std::ios::end);
std::streamsize size = infile.tellg();
infile.seekg(0, std::ios::beg);
// 读取文件内容到缓冲区
std::vector<char> buffer(size);
if (infile.read(buffer.data(), size)) {
std::cout << "成功读取 " << size << " 字节。" << std::endl;
} else {
std::cerr << "读取文件失败!" << std::endl;
}
infile.close();
return 0;
}
输出如下:
说明:
-
使用 std::ios::binary 模式打开文件,确保数据按原样读取。
-
通过 seekg 和 tellg 获取文件大小,预分配缓冲区。
-
使用 read 函数读取二进制数据到缓冲区。
-
对这段代码的理解:
infile.seekg(0, std::ios::end); std::streamsize size = infile.tellg(); infile.seekg(0, std::ios::beg);
这段代码用于确定并重置文件流 infile 的位置,具体含义如下:
-
infile.seekg(0, std::ios::end);
:这行代码将文件指针移动到文件的末尾。seekg 函数用于设置输入流的位置,0 是偏移量,std::ios::end 指定从文件的末尾开始偏移。因此,文件指针现在位于文件末尾。
-
std::streamsize size = infile.tellg();
:tellg 函数返回当前文件指针的位置(即文件的长度)。因为我们刚刚将指针移动到了文件末尾,size 将包含文件的字节数。
-
infile.seekg(0, std::ios::beg);
:这行代码将文件指针重置回文件的开头。std::ios::beg 指定从文件的开头开始偏移,因此现在指针又回到了文件的起始位置。
总结:这段代码的主要目的是获取文件的大小,并在读取文件内容之前重置文件指针,以便可以从头开始读取数据。
-
6. 写入文件
6.1 使用 <<
操作符写入数据
<<
操作符用于向文件中插入数据,类似于向标准输出流 std::cout 中插入数据。
比如:
将用户信息写入文件 users.txt。
执行以下代码:
#include <fstream>
#include <iostream>
#include <string>
int main() {
std::ofstream outfile("users.txt");
if (!outfile) {
std::cerr << "无法打开文件 users.txt" << std::endl;
return 1;
}
// 写入用户信息
outfile << "用户名: Alice\n年龄: 30\n" << std::endl;
outfile << "用户名: Bob\n年龄: 25\n" << std::endl;
outfile << "用户名: Charlie\n年龄: 35\n" << std::endl;
outfile.close();
std::cout << "用户信息已写入 users.txt" << std::endl;
return 0;
}
输出如下:
未执行前,目录如下:
执行后,目录如下:
自动创建了个 users.txt 文件,内容如下:
说明:
-
使用 << 操作符可以方便地将各种数据类型写入文件。
-
std::endl 用于插入换行符并刷新缓冲区。
6.2 使用 std::ofstream::write
写入二进制数据
对于需要写入二进制数据的场景,使用 write()
成员函数更为合适。
比如:
将一个字符数组写入二进制文件 output.bin。
执行以下代码:
#include <fstream>
#include <iostream>
#include <vector>
int main() {
std::ofstream outfile("output.bin", std::ios::binary | std::ios::trunc);
if (!outfile) {
std::cerr << "无法打开文件 output.bin" << std::endl;
return 1;
}
// 准备二进制数据
std::vector<char> data = {'H', 'e', 'l', 'l', 'o', '\0'};
// 写入二进制数据到文件
outfile.write(data.data(), data.size());
if (!outfile) {
std::cerr << "写入文件失败!" << std::endl;
} else {
std::cout << "成功写入 " << data.size() << " 字节到 output.bin" << std::endl;
}
outfile.close();
return 0;
}
输出如下:
同理,在目录处也自动创建了一个 output.bin 文件,内容如下:
说明:
-
使用
write()
可以指定要写入的数据地址和字节数,适用于二进制数据。 -
确保文件以二进制模式打开,防止数据被意外转换。
-
对
outfile.write(data.data(), data.size());
这行代码的理解:这行代码用于将数据写入输出文件流 outfile,具体含义如下:
-
data.data():获取 data 字符串的指针,指向字符串的首字符。这是写入的起始地址。
-
data.size():返回字符串 data 的长度,表示要写入的字节数。
-
outfile.write(data.data(), data.size());
:调用 write 函数,将从 data 指针开始的 data.size() 字节写入 outfile 中。
总体而言,这行代码的作用是将 data 字符串的内容写入到 outfile 文件中。
-
7. 错误处理
文件操作过程中可能会遇到各种错误,如文件不存在、权限不足、磁盘满等。因此,进行适当的错误处理至关重要。
7.1 检查文件是否成功打开
每次打开文件后,都应该检查文件流对象的状态,以确保文件成功打开。
比如:
#include <fstream>
#include <iostream>
int main() {
std::ifstream infile("nonexistent.txt");
if (!infile.is_open()) { // 或者使用 !infile
std::cerr << "无法打开文件 nonexistent.txt" << std::endl;
return 1;
}
// 进行读取操作...
infile.close();
return 0;
}
说明:
-
使用
is_open()
成员函数检查文件是否成功打开。 -
也可以直接检查流对象的状态,如
if (!infile)
。
7.2 使用异常处理
C++文件流可以配置为在发生错误时抛出异常,提供更灵活的错误处理机制。
比如:
使用异常处理机制读取文件内容。
#include <fstream>
#include <iostream>
#include <string>
int main() {
std::ifstream infile;
// 设置文件流在 failbit 或 badbit 时抛出异常
infile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try {
infile.open("data.txt");
std::string line;
while (std::getline(infile, line)) {
std::cout << line << std::endl;
}
infile.close();
} catch (const std::ifstream::failure& e) {
std::cerr << "文件操作异常: " << e.what() << std::endl;
return 1;
}
return 0;
}
输出如下(因为没有创建data.txt文件,所以抛出异常):
在Visual Studio 2019中:
在Dev-C++中:
说明:
-
使用 exceptions() 成员函数设置文件流在特定错误状态下抛出异常。
-
捕获 std::ifstream::failure 异常,进行错误处理。
-
默认情况下,文件流不会抛出异常,需显式启用。
-
对这段代码的解释:
-
std::ifstream infile;
:声明一个输入文件流对象 infile,用于读取文件。 -
infile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
:设置文件流在发生失败(failbit)或严重错误(badbit)时抛出异常,这样可以通过 try-catch 机制来捕获错误。
-
try {
:开始一个异常处理块,尝试执行可能抛出异常的代码。 -
infile.open("data.txt");
:打开名为 data.txt 的文件进行读取。如果文件无法打开,将抛出异常。 -
std::string line;
:声明一个字符串变量 line,用于存储从文件中读取的每一行。 -
while (std::getline(infile, line)) {
:使用 getline 函数从 infile 中逐行读取数据,直到文件结束。每读取一行,将其存储在 line 中。
-
std::cout << line << std::endl;
:将读取的每一行输出到控制台。 -
infile.close();
:关闭文件流,释放资源。 -
} catch (const std::ifstream::failure& e) {
:捕获文件流的异常,处理文件操作错误。
-
std::cerr << "文件操作异常: " << e.what() << std::endl;
:输出错误信息到标准错误流,e.what() 提供具体的错误描述。
-
return 1;
:如果发生异常,返回 1 表示程序异常结束。 -
return 0;
:正常结束程序,返回 0。
-
8. 高级主题
8.1 文件指针位置控制
文件流对象维护了读写指针的位置,可以通过成员函数 seekg()
(用于输入流)和 seekp()
(用于输出流)来移动指针。
比如:
读取文件中指定位置的数据。
假设 data.txt 的文本文件,内容如下:
执行以下代码:
#include <fstream>
#include <iostream>
#include <string>
int main() {
std::ifstream infile("data.txt", std::ios::binary);
if (!infile) {
std::cerr << "无法打开文件 data.txt" << std::endl;
return 1;
}
// 获取文件大小
infile.seekg(0, std::ios::end);
std::streampos size = infile.tellg();
std::cout << "文件大小: " << size << " 字节" << std::endl;
// 移动指针到文件中间位置
std::streampos mid = size / 2;
infile.seekg(mid, std::ios::beg);
std::cout << "移动到文件中间位置: " << mid << std::endl;
// 读取接下来的10个字节
char buffer[11] = {0}; // 10个字符 + 1个终止符
infile.read(buffer, 10);
std::cout << "读取的数据: " << buffer << std::endl;
infile.close();
return 0;
}
输出如下:
说明:
-
使用
seekg()
移动读指针位置。 -
使用
tellg()
获取当前读指针的位置。 -
适用于需要随机访问文件内容的场景。
8.2 缓冲管理
文件流对象使用缓冲区来提高I/O效率。可以使用 flush()
成员函数来强制将缓冲区的数据写入文件。
比如:
在写入数据后立即刷新缓冲区。
#include <fstream>
#include <iostream>
#include <string>
int main() {
std::ofstream outfile("buffered_output.txt");
if (!outfile) {
std::cerr << "无法打开文件 buffered_output.txt" << std::endl;
return 1;
}
outfile << "这是一行数据。";
outfile.flush(); // 强制刷新缓冲区
outfile << "这是另一行数据。\n";
outfile.close();
return 0;
}
自动创建了buffered_output.txt文件,内容如下:
9. 举几个例子
为了更好地理解如何综合运用上述知识,以下提供几个完整的示例,涵盖读取、写入、错误处理和二进制数据操作。
9.1 读取文本文件并处理数据
任务: 从 students.txt 文件中读取学生信息,并输出到控制台。
假设 students.txt 内容如下:
执行以下代码:
#include <fstream>
#include <iostream>
#include <string>
int main() {
std::ifstream infile("students.txt");
if (!infile) {
std::cerr << "无法打开文件 students.txt" << std::endl;
return 1;
}
std::string name;
int age;
while (infile >> name >> age) {
std::cout << "学生姓名: " << name << ", 年龄: " << age << std::endl;
}
if (infile.bad()) {
std::cerr << "读取文件时发生严重错误!" << std::endl;
} else if (infile.eof()) {
std::cout << "已到达文件末尾。" << std::endl;
} else if (infile.fail()) {
std::cerr << "读取文件时发生格式错误!" << std::endl;
}
infile.close();
return 0;
}
输出如下:
说明:
-
使用 >> 操作符逐个读取学生姓名和年龄。
-
通过检查
bad()
、eof()
和fail()
来处理不同类型的错误。
9.2 写入文本文件
任务: 将多个产品信息写入 products.txt 文件。
执行以下代码:
#include <fstream>
#include <iostream>
#include <string>
int main() {
std::ofstream outfile("products.txt");
if (!outfile) {
std::cerr << "无法打开文件 products.txt" << std::endl;
return 1;
}
// 写入产品信息
outfile << "产品名称: 手机\n价格: 5000元\n\n";
outfile << "产品名称: 笔记本电脑\n价格: 8000元\n\n";
outfile << "产品名称: 电子书阅读器\n价格: 1200元\n\n";
outfile.close();
std::cout << "产品信息已写入 products.txt" << std::endl;
return 0;
}
输出如下:
自动创建的products.txt文件,内容如下:
说明:
-
使用
<<
操作符将字符串和数值写入文件。 -
通过插入
\n
实现换行和段落分隔。
9.3 复制二进制文件
任务: 复制一个二进制文件 source.jpg 到 destination.jpg。
假设 source.jpg 内容如下:
执行以下代码:
#include <fstream>
#include <iostream>
int main() {
std::ifstream src("source.jpg", std::ios::binary);
if (!src) {
std::cerr << "无法打开源文件 source.jpg" << std::endl;
return 1;
}
std::ofstream dest("destination.jpg", std::ios::binary | std::ios::trunc);
if (!dest) {
std::cerr << "无法创建目标文件 destination.jpg" << std::endl;
return 1;
}
// 复制文件内容
dest << src.rdbuf();
if (!dest) {
std::cerr << "复制文件失败!" << std::endl;
return 1;
} else {
std::cout << "文件复制成功。" << std::endl;
}
src.close();
dest.close();
return 0;
}
输出如下:
自动创建了destination.jpg文件,内容如下:
说明:
-
使用
rdbuf()
将源文件流的缓冲区内容直接传输到目标文件流。 -
适用于高效复制文件内容,尤其是大文件。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!