什么是使用C++读取文本文件的最优雅的方式?

67

我想用C++将文本文件的全部内容读入std::string对象。

使用Python,我可以写成:

text = open("text.txt", "rt").read()

这很简单又优雅。我不喜欢丑陋的东西,所以我想知道——用C++读取文本文件最优雅的方式是什么? 谢谢。


26
如果你讨厌丑陋的东西,最好不要使用 C++ :P - OregonGhost
6
有关优雅性的说明,即使最优雅的iostream解决方案对你来说仍然看起来很丑陋,你可以将其封装在一个漂亮的函数中,这样就不会让你感到难看了 ;) - OregonGhost
2
关于“丑陋的东西”参数:while(ugly()) encapsulate_more(); - Marco A.
5个回答

138

有许多方法,你可以选择最适合你的优雅方式。

读取 char*:

ifstream file ("file.txt", ios::in|ios::binary|ios::ate);
if (file.is_open())
{
    file.seekg(0, ios::end);
    size = file.tellg();
    char *contents = new char [size];
    file.seekg (0, ios::beg);
    file.read (contents, size);
    file.close();
    //... do something with it
    delete [] contents;
}

转换为 std::string:

std::ifstream in("file.txt");
std::string contents((std::istreambuf_iterator<char>(in)), 
    std::istreambuf_iterator<char>());

转换为vector<char>:

std::ifstream in("file.txt");
std::vector<char> contents((std::istreambuf_iterator<char>(in)),
    std::istreambuf_iterator<char>());

使用 stringstream 转换为字符串:

std::ifstream in("file.txt");
std::stringstream buffer;
buffer << in.rdbuf();
std::string contents(buffer.str());

file.txt 只是一个例子,对二进制文件同样适用,只需确保在 ifstream 构造函数中使用 ios::binary。


1
我喜欢你的答案,甚至比我的更好,这不是我经常说的话。干得好!+1 - C. K. Young
8
实际上,您需要在使用istreambuf_iterator<>构造函数的第一个参数周围添加额外的括号,以防止它被视为函数声明。 - Greg Rogers
2
@Shadow2531:我觉得在你完成它的某些操作之前不应该将其删除。 - Milan Babuškov
1
@Ferruccio:请查看Greg Rogers上面的评论。 - Milan Babuškov
第一种解决方案有两个问题。首先,您没有给出大小的类型(应该是int,我假设?),其次char*没有正确终止。这是一个经过测试的版本:ifstream file("..\TESAITest\data\BlueprintManagerTestData.json", ios::in | ios::binary | ios::ate); if (file.is_open()) { file.seekg(0, ios::end); int size = file.tellg(); char *contents = new char[size+1]; file.seekg(0, ios::beg); file.read(contents, size); file.close(); contents[size] = '\0';// 做一些事情 delete[] contents;} - Kevin Dill
显示剩余11条评论

13

这个主题有另一个帖子

我的解决方案来自于这个帖子(都是一行代码):

好的解决方案(见米兰的第二个方案):

string str((istreambuf_iterator<char>(ifs)), istreambuf_iterator<char>());

速度也很快:

string str(static_cast<stringstream const&>(stringstream() << ifs.rdbuf()).str());

实际上,第一个更快,因为它直接操作istream缓冲区,而后者依赖于第一个但添加了一些失败状态位。 - t.g.
@t.g. 第一个使用了非常低效的复制方式来构建字符串,没有预先分配空间,这导致了很多重新分配内存的情况。第二个则预先分配了所需大小的缓冲区。 - Konrad Rudolph
1
我刚用VC++10测试了一下,实际上这取决于文件大小。对于较小的文件来说,第一种速度更快;对于较大的文件,第二种更快,这好像证明了你所说的。 - t.g.
string str((istreambuf_iterator<char>(ifs))); 对我来说运行良好,有什么问题吗? - Thomas E
@ThomasE 它使用了一个不存在的构造函数重载。不知道为什么它可以在你的编译器上工作,也不知道它到底做了什么。 - Konrad Rudolph
https://dev59.com/RnVC5IYBdhLWcg3wvT_g#1EwooYgBc1ULPQZF_Shd - Nae

3
你似乎把"少量代码"的优雅看作是一种确定的属性。这在某种程度上是主观的。有些人会说省略所有错误处理并不优雅。有些人会说清晰而紧凑的代码很容易理解,这就是优雅。
编写自己的一行函数/方法来读取文件内容,但在表面下使其严格和安全,并且您将涵盖优雅的两个方面。
祝一切顺利。
/罗伯特

3
推论:优雅的代码在实际表现中才能看出其优雅之处;不同编程语言和范式下的优雅代码概念有所区别。C++程序员认为优雅的代码可能会让Ruby或Python程序员感到可怕,反之亦然。 - Rob

2
但要注意,C++字符串(或更具体地说,STL字符串)与C字符串一样,不能容纳任意长度的字符串 - 当然不行!
请查看成员max_size(),它给出了字符串可能包含的最大字符数。这是一个实现定义的数字,并且在不同平台之间可能不可移植。Visual Studio为字符串提供约4GB的值,其他平台可能仅提供64k,而在64位平台上,它可能会给您提供非常巨大的东西!这取决于情况,当然通常您会在达到4GB限制之前很长时间遇到内存耗尽的bad_alloc异常...
顺便说一句:max_size()也是其他STL容器的成员!它将为您提供某种类型(对于您实例化容器的类型)的元素数量,该容器将(理论上)能够容纳。
因此,如果您正在从未知来源的文件中读取,则应:
- 检查其大小并确保其小于max_size()
- 捕获并处理bad_alloc异常
还有一点: 为什么您渴望将文件读入字符串?我希望进一步通过逐步解析等方式进行处理,对吧?因此,您可以将其读入stringstream(基本上只是一些语法糖),并进行处理。但是,您也可以直接从文件中读取进行处理。因为如果正确编程,stringstream可以无缝地被文件流(即文件本身)替换。或者也可以通过任何其他输入流进行替换,它们都共享相同的成员和运算符,因此可以无缝地互换!
对于处理本身:编译器还可以自动化许多操作!例如,假设您想将字符串标记化。在定义适当的模板时,以下操作:
- 从文件中读取(或从字符串或任何其他输入流中读取)
- 对内容进行标记化
- 将所有找到的标记推入STL容器中
- 按字母顺序排序标记
- 消除任何重复值
所有这些操作都可以在一行C++代码中完成(除了模板本身和错误处理之外)!这只是一个std::copy()函数的单个调用!只需搜索“token iterator”,您就会了解我所说的意思。因此,这对我来说甚至比仅从文件中读取更“优雅”...

需要注意的是,max_size() 是相对于 size_t 的大小定义的,而 size_t 的大小又与您的平台的位数相关。这样定义是为了允许字符串的大小达到您的平台可以寻址的最大值。 - Devin Lane

1

我喜欢米兰的char*方式,但使用std::string。


#include <iostream>
#include <string>
#include <fstream>
#include <cstdlib>
using namespace std;

string& getfile(const string& filename, string& buffer) {
    ifstream in(filename.c_str(), ios_base::binary | ios_base::ate);
    in.exceptions(ios_base::badbit | ios_base::failbit | ios_base::eofbit);
    buffer.resize(in.tellg());
    in.seekg(0, ios_base::beg);
    in.read(&buffer[0], buffer.size());
    return buffer;
}

int main(int argc, char* argv[]) {
    if (argc != 2) {
        cerr << "Usage: this_executable file_to_read\n";
        return EXIT_FAILURE;
    }
    string buffer;
    cout << getfile(argv[1], buffer).size() << "\n";
}

(根据您是否希望换行符被翻译,可以使用或不使用ios_base::binary。您还可以将getfile更改为仅返回一个字符串,以便无需传递缓冲区字符串。然后,测试编译器在返回时是否优化了复制。)

但是,这可能看起来更好一些(并且速度会慢得多):


#include <iostream>
#include <string>
#include <fstream>
#include <cstdlib>
using namespace std;

string getfile(const string& filename) {
    ifstream in(filename.c_str(), ios_base::binary);
    in.exceptions(ios_base::badbit | ios_base::failbit | ios_base::eofbit);
    return string(istreambuf_iterator<char>(in), istreambuf_iterator<char>());
}

int main(int argc, char* argv[]) {
    if (argc != 2) {
        cerr << "Usage: this_executable file_to_read\n";
        return EXIT_FAILURE;
    }
    cout << getfile(argv[1]).size() << "\n";
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接