Skip to content

C++ 文件与流

到目前为止,我们使用了 <iostream> 标准库,它提供了 std::cin 和 std::cout 用于从标准输入读取和向标准输出写入。为了处理文件,C++ 提供了 <fstream> 库。

<fstream> 库定义了三种主要的文件 I/O 数据类型:

数据类型描述
std::ofstream输出文件流(Output File Stream):用于创建文件和向文件写入数据。
std::ifstream输入文件流(Input File Stream):用于从文件读取数据。
std::fstream文件流(File Stream):结合了 ofstream 和 ifstream 的功能,用于读写文件。

要在 C++ 中执行文件处理,必须包含 <fstream> 头文件。通常也会包含 <iostream> 用于控制台交互,以及 <string> 用于处理文本。

在现代 C++ 中,打开文件最简单的方式是在流对象构造时进行。这使用了 RAII(资源获取即初始化,Resource Acquisition Is Initialization)原则,意味着当流对象超出其作用域时(例如,在函数结束时),文件会自动关闭。

std::ofstream outputFile("mydata.txt"); // Opens for writing (truncates if exists)
// 打开用于写入(如果文件存在则截断)
std::ifstream inputFile("mydata.txt"); // Opens for reading
// 打开用于读取
std::fstream ioFile("mydata.txt", std::ios::in | std::ios::out); // Opens for read/write
// 打开用于读/写

应该始终检查文件是否成功打开:

std::ofstream outputFile("mydata.txt");
if (!outputFile) { // Or !outputFile.is_open()
// 或者 !outputFile.is_open()
std::cerr << "Error opening file for writing!" << std::endl;
// Handle error, maybe return or exit
// 处理错误,可能返回或退出
}

或者,可以先创建流对象,然后调用 open() 成员函数。第二个参数指定打开 模式(Mode):

void open(const std::string& filename, std::ios_base::openmode mode = std::ios_base::out); // ofstream default
// ofstream 默认
void open(const std::string& filename, std::ios_base::openmode mode = std::ios_base::in); // ifstream default
// ifstream 默认

常见的文件模式(定义在 std::ios_base 中):

模式标志描述
std::ios::app追加(Append)到文件末尾。
std::ios::ate打开文件后定位到文件末尾(Seek to the end)。
std::ios::binary以二进制模式(Binary Mode)打开(而不是文本模式)。
std::ios::in打开用于读取(ifstream 的默认模式)。
std::ios::out打开用于写入(ofstream 的默认模式)。
std::ios::trunc如果文件存在,则将其长度截断(Truncate)为零(ofstream 在不使用 app 或 in 时的默认模式)。

使用位或运算符(|)组合多种模式。例如,以二进制模式打开用于输出,如果文件存在则追加内容:

std::ofstream outfile;
outfile.open("data.bin", std::ios::out | std::ios::binary | std::ios::app);

如果使用 RAII 方法(在构造函数中打开),文件将在流对象销毁时自动关闭。这是首选方法,因为它不易出错。

如果使用了 open() 方法,可以使用 close() 显式关闭文件。然而,依赖 RAII 通常更好。

// Only needed if RAII wasn't used or if you need to check close status
// 仅当未使用 RAII 或需要检查关闭状态时才需要
if (outfile.is_open()) {
outfile.close();
}

使用流插入运算符(<<)与 ofstream 或 fstream 对象,就像使用 std::cout 一样:

std::ofstream outputFile("greeting.txt");
if (outputFile) {
std::string name = "Alice";
int age = 30;
outputFile << "Hello, " << name << "!\n";
outputFile << "Your age is: " << age << std::endl;
} // File automatically closed here
// 文件在此处超出作用域时自动关闭

使用流提取运算符(>>)与 ifstream 或 fstream 对象,类似于 std::cin。要逐行读取,使用 std::getline()。

// Reading whitespace-separated values
// 读取由空白字符分隔的值
std::ifstream inputFile("data.txt");
int value1;
double value2;
std::string word;
if (inputFile) {
inputFile >> value1 >> value2 >> word;
// Check if reads were successful: if (!inputFile) { handle error... }
// 检查读取是否成功: if (!inputFile) { 处理错误... }
}
// Reading line by line
// 逐行读取
std::ifstream textFile("document.txt");
std::string line;
if (textFile) {
while (std::getline(textFile, line)) { // Reads until newline or EOF
// 读取直到换行符或文件末尾(EOF)
std::cout << "Read line: " << line << std::endl;
}
} // File automatically closed here
// 文件在此处超出作用域时自动关闭

重要提示:混合使用 >> 和 std::getline() 需要小心,因为 >> 通常会在流中留下换行符,这可能导致 std::getline() 读取到空行。在使用 >> 之后、std::getline() 之前,你可能需要使用 inputFile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');。

这个程序会询问用户的姓名和年龄,将它们写入 user_data.txt 文件,然后重新读取数据并显示。

#include <fstream>
#include <iostream>
#include <string>
#include <limits> // Required for numeric_limits
// numeric_limits 所需
int main() {
std::string name;
int age;
const std::string filename = "user_data.txt";
// --- Writing to the file ---
// --- 写入文件 ---
{
std::ofstream outfile(filename); // Opens for writing (RAII style)
// 打开用于写入(RAII 风格)
if (!outfile) {
std::cerr << "Error opening " << filename << " for writing." << std::endl;
// 错误:无法打开文件 " << filename << " 用于写入。
return 1;
}
std::cout << "Writing to the file " << filename << std::endl;
// 正在写入文件 " << filename << std::endl;
std::cout << "Enter your name: ";
// 输入你的姓名:
std::getline(std::cin, name); // Read full name with spaces
// 读取包含空格的完整姓名
std::cout << "Enter your age: ";
// 输入你的年龄:
std::cin >> age;
// Ignore the newline character left by std::cin >> age
// 忽略 std::cin >> age 留下的换行符
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
// Write data to the file
// 将数据写入文件
outfile << name << std::endl; // Write name on its own line
// 将姓名写入单独一行
outfile << age << std::endl; // Write age on its own line
// 将年龄写入单独一行
std::cout << "Data written successfully." << std::endl;
// 数据写入成功。
// outfile is automatically closed when it goes out of scope here
// outfile 在此处超出作用域时自动关闭
}
std::cout << "\nReading from the file " << filename << std::endl;
// \n 正在从文件 " << filename << " 读取。
// --- Reading from the file ---
// --- 从文件读取 ---
{
std::ifstream infile(filename); // Opens for reading (RAII style)
// 打开用于读取(RAII 风格)
if (!infile) {
std::cerr << "Error opening " << filename << " for reading." << std::endl;
// 错误:无法打开文件 " << filename << " 用于读取。
return 1;
}
std::string read_name;
int read_age;
// Read data from the file
// 从文件读取数据
if (std::getline(infile, read_name) && (infile >> read_age)) {
// Display the data
// 显示数据
std::cout << "Name read: " << read_name << std::endl;
// 读取的姓名:
std::cout << "Age read: " << read_age << std::endl;
// 读取的年龄:
} else {
std::cerr << "Error reading data from the file." << std::endl;
// 错误:从文件读取数据失败。
}
// infile is automatically closed when it goes out of scope here
// infile 在此处超出作用域时自动关闭
}
return 0;
}

示例交互:

Writing to the file user_data.txt
Enter your name: Ada Lovelace
Enter your age: 36
Data written successfully.
Reading from the file user_data.txt
Name read: Ada Lovelace
Age read: 36

输入流(istream)和输出流(ostream)都维护一个 位置指示器(Position Indicator),指定下一个读/写位置。可以使用 seekg(seek get,用于输入流)和 seekp(seek put,用于输出流)来操作它们。

这些函数通常接受一个 偏移量(Offset)(通常是 std::streamoff,一个类似于 long long 的类型)和一个可选的方向(std::ios_base::seekdir):

  • std::ios::beg:相对于流开始位置的偏移量(默认)。
  • std::ios::cur:相对于当前位置的偏移量。
  • std::ios::end:相对于流结束位置的偏移量。

输入流 fileObject 的示例用法:

// Position to the 10th byte from the beginning
// 定位到从开始位置起的第 10 个字节
fileObject.seekg(10, std::ios::beg); // or fileObject.seekg(10);
// 或者 fileObject.seekg(10);
// Move 5 bytes forward from the current position
// 从当前位置向前移动 5 个字节
fileObject.seekg(5, std::ios::cur);
// Position 8 bytes back from the end
// 从末尾向后移动 8 个字节
fileObject.seekg(-8, std::ios::end);
// Position at the end of the file
// 定位到文件末尾
fileObject.seekg(0, std::ios::end);
// Get current position
// 获取当前位置
std::streampos currentPos = fileObject.tellg();

seekp 和 tellp 是输出流(ostream)对应的函数。在文本模式下使用定位时要小心,因为偏移量的解释可能因系统而异,这取决于换行符转换。在二进制模式(std::ios::binary)下进行定位更可靠。

对于更高级的文件系统操作,如创建目录、检查文件是否存在或遍历目录内容,请考虑使用 C++17 的 <filesystem> 库。