在 C++ 中,读取普通文本文件最常用的是 <fstream> 头文件中的 std::ifstream。它的用法和我们熟悉的 std::cin 非常相似:std::cin 是从键盘读取数据,而 std::ifstream 是从磁盘文件中读取数据。
根据文件内容的排版格式,读取文本文件一般分为 2 种方式:一种是使用 >> 运算符按空白分隔逐个读取单词或数字,另一种是调用 std::getline() 函数读取包含空格的一整行文本。
使用 >> 读取数据
在 C++ 中,使用 >> 运算符从文件中读取数据,通常称为 “格式化读取”。简单来说,程序会根据接收变量的数据类型,自动把文件里的文本转换成对应的字符串、整数或小数。
语法:
file >> 变量1 >> 变量2;说明:
在读取数据之前,我们需要确保 file 是已经成功打开的 std::ifstream 对象。>> 运算符会自动跳过开头的空格、Tab 和换行符等空白字符,依次把文件中的数据存入对应的变量中,直到遇到下一个空白字符为止。
示例 1:读取不同类型的字段
#include <fstream>
#include <iostream>
#include <string>
int main() {
// 准备测试文件 info.txt
std::ofstream outFile("info.txt");
outFile << "Jack 20 95.5\n";
outFile.close();
std::ifstream file("info.txt");
if (!file.is_open()) {
std::cout << "文件打开失败\n";
return 1;
}
std::string name;
int age = 0;
double score = 0.0;
// 依次读取姓名、年龄和成绩
file >> name >> age >> score;
std::cout << "姓名:" << name << '\n';
std::cout << "年龄:" << age << '\n';
std::cout << "成绩:" << score << '\n';
return 0;
}运行结果如下。
姓名:Jack
年龄:20
成绩:95.5分析:
在这个例子中,为了方便大家直接在线运行观察,我们在程序开头先借用 std::ofstream 生成了测试文件 info.txt。如果本地目录里已经有现成的文件,我们直接从 std::ifstream 那一行开始写就可以了。
随后执行的 file >> name >> age >> score; 与 std::cin 的连续读取用法完全一致。由于 3 个变量分别是 std::string、int 和 double 类型,>> 会按空格切分,依次把 "Jack"、20 和 95.5 存入对应的变量中。
需要注意的是,当接收变量是 std::string 类型时,>> 同样会把空格当成结束分隔符。也就是说,一旦遇到空格,当前这一次字符串读取就会立即停止。下面我们来看一个例子。
示例 2:>> 遇到空格停止读取
#include <fstream>
#include <iostream>
#include <string>
int main() {
// 准备含空格的测试文件 site.txt
std::ofstream outFile("site.txt");
outFile << "Hello lvyenet world\n";
outFile.close();
std::ifstream file("site.txt");
if (!file.is_open()) {
std::cout << "文件打开失败\n";
return 1;
}
std::string word;
file >> word;
std::cout << word << '\n';
return 0;
}运行结果如下。
Hello分析:
file >> word; 表示从文件中读取一个字符串并存入变量 word。在读取 "Hello lvyenet world" 时,程序刚读到 "Hello" 后面的空格就停下来了,因此 word 中只保存了第 1 个单词 "Hello"。
如果文件里存放的是一组按空格或换行分隔的数字或单词,我们想把它们从头到尾全部读出来,又应该怎么写呢?这时可以直接把 file >> 变量 放进 while 循环的条件括号里。请看下面例子。
示例 3:循环读取多个数据
#include <fstream>
#include <iostream>
int main() {
// 准备测试文件 nums.txt
std::ofstream outFile("nums.txt");
outFile << "10 20 30\n40 50\n";
outFile.close();
std::ifstream file("nums.txt");
if (!file.is_open()) {
std::cout << "文件打开失败\n";
return 1;
}
int num = 0;
int sum = 0;
while (file >> num) {
sum += num;
}
std::cout << "所有整数之和:" << sum << '\n';
return 0;
}运行结果如下。
所有整数之和:150分析:
每次判断 while (file >> num) 时,程序都会自动跳过空格和换行符,读取下一个整数存入 num 中。只要本次读取成功,条件判断就为 true;当读完最后一个数字到达文件末尾时,条件判断变为 false,循环随之结束。
不过,如果我们希望把 "Hello lvyenet world" 作为完整的一句话读出来,同时保留句子中间的空格,就不能使用 >> 了。遇到这种情况,我们需要改用下面介绍的 std::getline() 函数。
使用 getline() 读取一整行
在 C++ 中,如果想要一次性读取包含空格的一整行文本,我们可以使用 std::getline() 函数来实现。
语法:
std::getline(file, line);说明:
std::getline() 函数接收以下 2 个参数:
- file(必选):正在读取的输入文件流对象,例如
std::ifstream。 - line(必选):用来保存这一行文本的
std::string变量,使用前需要引入<string>头文件。
调用后,std::getline() 会从当前位置一直读到行尾的换行符 \n 为止。整行中间的空格都会完整保留下来,而行尾的换行符本身会被丢弃,不会存入 line 中。
示例 4:读取一整行文本
#include <fstream>
#include <iostream>
#include <string>
int main() {
std::ofstream outFile("note.txt");
outFile << "Welcome to lvyenet!\n";
outFile.close();
std::ifstream file("note.txt");
if (!file.is_open()) {
std::cout << "文件打开失败\n";
return 1;
}
std::string line;
std::getline(file, line);
std::cout << line << '\n';
return 0;
}运行结果如下。
Welcome to lvyenet!分析:
std::getline(file, line); 表示把 note.txt 中的整行文字完整读取到变量 line 中。可以看到单词之间的空格完好保留,没有被截断。
在实际开发中,这两种读取方式的分工非常明确。读取按空格分隔的数字或独立单词时,我们优先使用 file >> value;读取包含空格的句子或配置行时,则使用 std::getline(file, line)。
另外,调用一次 std::getline() 只会读取一行文本。如果小伙伴们想把多行文件从头到尾全部读完,我们会在下一节 “C++ 逐行读取文件” 中展开介绍。
C++ 读取文件的注意事项
刚开始练习读取文件时,小伙伴们最容易遇到 “数据格式不匹配导致读取失败” 和 “相对路径找不到文件” 这两个问题,下面我们逐一来看。
1. 文件内容与变量类型不匹配
文件能够成功打开,只说明磁盘上有这个文件并且允许访问,但并不保证里面的文字一定能按预期的类型解析出来。假设 fruit.txt 里原本应该保存水果名称和数字价格,但价格不小心被写成了字母 abc,我们来看看会发生什么。
示例 5:数据格式不匹配
#include <fstream>
#include <iostream>
#include <string>
int main() {
// 准备含错误格式的测试文件 fruit.txt
std::ofstream outFile("fruit.txt");
outFile << "apple abc\n";
outFile.close();
std::ifstream file("fruit.txt");
if (!file.is_open()) {
std::cout << "文件打开失败\n";
return 1;
}
std::string name;
double price = 0.0;
file >> name >> price;
if (!file) {
std::cout << "读取失败:数据格式不匹配\n";
return 1;
}
std::cout << "水果:" << name << ",价格:" << price << '\n';
return 0;
}运行结果如下。
读取失败:数据格式不匹配分析:
从运行结果可以看出,程序提示数据格式不匹配并提前退出了。当我们执行 file >> name >> price; 时,第 1 个变量 name 是 std::string 类型,可以正常读出 "apple"。
但第 2 个变量 price 是 double 类型,文件中的 "abc" 没法转换成数字。因此本次读取立即失败,文件流进入错误状态。下方的 if (!file) 及时检测到错误并终止程序,避免后面拿着无效的 price 继续计算。
注意 “文件打开成功” 和 “数据读取成功” 不是一回事。平时我们应该先通过 if (!file.is_open()) 确认文件有没有打开,在执行 >> 或 std::getline() 读取外部数据之后,再通过 if (!file) 判断本次数据是否成功读出。
2. 相对路径的查找起点
很多小伙伴在本地运行代码时会遇到这样一种情况:info.txt 明明就放在源代码文件 main.cpp 旁边,为什么程序运行起来却一直提示 “文件打开失败” 呢?
这通常是因为搞错了 “相对路径” 的查找起点。像下面这种没有写出 D:/project/info.txt 完整磁盘位置的写法,都属于相对路径:
std::ifstream file1("info.txt");
std::ifstream file2("data/note.txt");在 C++ 中,相对路径是按照程序运行时的 “当前工作目录” 去查找的,而不是固定按照 main.cpp 所在的文件夹去查找。比如源代码放在 project/src 下,而编辑器把运行工作目录设在了 project,程序就会去 project 目录下找 info.txt。
另外在书写文件路径字符串时,即使在 Windows 系统下,我们也推荐统一使用正斜杠 /,比如 "D:/project/info.txt"。如果直接写单个反斜杠 \,很容易被编译器当成 \n、\t 等转义字符而导致路径出错。
遇到相对路径打不开文件的情况时,我们可以按以下 3 点依次排查:
- 检查文件名与隐藏扩展名:先确认文件名拼写无误。如果在 Windows 资源管理器中隐藏了已知文件扩展名,我们手动新建的
info.txt实际文件名可能变成了info.txt.txt。 - 确认当前工作目录位置:检查测试文件是否放在了程序实际运行的工作目录下。我们也可以先使用
std::ofstream生成一个测试文件,看看它出现在哪个文件夹里。 - 检查子目录是否存在:如果代码里的路径写的是
"data/note.txt",我们需要确认当前工作目录下确实建好了data这个文件夹。
常见问题
1. 为什么先使用 >> 读取数字,紧接着调用 std::getline() 却读出了一行空字符串?
这是混合使用 >> 和 std::getline() 时最常见的一个小陷阱。假设文件第 1 行是数字 1001,第 2 行是文本 "Hello lvye"。当我们先执行 file >> id; 时,>> 只把数字 1001 读走了,第 1 行末尾的换行符 \n 依然留在文件流中。
紧接着调用 std::getline(file, line); 时,它一上来就遇到了刚才残留的 \n。程序误以为已经读完了一整行,于是直接把空字符串 "" 存进了 line。
解决办法很简单:在 file >> id; 和 std::getline(file, line); 之间加上一行 file.ignore(1000, '\n');,先把第 1 行残留的换行符清理掉,后面就能正常读取第 2 行的完整文本了。
