您需要了解一些基本的编码概念。我推荐阅读这篇文章:The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets。基本上你不能假设每个字节都是一个字母并且每个字母都适合char。此外,系统必须知道如何从文件中的字节序列中提取字母。
假设您的文件以 UTF-8 编码,这很可能是因为您使用的是 Linux。我假设您的终端也支持它。如果您使用带有字符的std::string 直接阅读,则一切正常。看:
// olá
#include <iostream>
#include <fstream>
int main() {
std::fstream file("test.cpp");
std::string str;
std::getline(file, str);
std::cout << str << std::endl;
}
输出是您所期望的,但这并不正确。看看是怎么回事:文件是用utf-8编码的。这意味着第一行是这个字节序列:
/ / o l á
47 47 32 111 108 195 161
请注意á is encoded with two bytes。如果你问字符串的大小(str.size()),你确实会得到错误的值:7。发生这种情况是因为字符串认为每个字节都是一个字符。当您将其发送到std::cout 时,该字符串将被提供给终端进行打印。神奇的部分:终端默认使用 utf-8。所以它只是假设字符串是 utf-8 并正确打印 6 个字符。
您会发现它有效,但实际上并不正确。尝试对数据进行任何字符串操作,您可能会破坏 utf-8 编码,并且永远无法再次打印!
让我们去找wstrings。他们用wchar_t 存储每个字母,在Linux 上,它有4 个字节。这足以容纳任何可能的 Unicode 字符。但它不会直接工作,因为 C++ 默认使用 "C" 语言环境。语言环境是如何处理系统各个方面的规范,例如“如何打印日期”或“如何格式化货币值”甚至“如何解码文本”。最后一个因素很重要,默认的"C" 编码表示:“假设一切都是ASCII”。当它正在读取文件并尝试解码非 ASCII 字节时,它只是默默地失败。
修正很简单:使用 UTF-8 语言环境。看:
// olá
#include <iostream>
#include <fstream>
#include <locale>
int main() {
std::ios::sync_with_stdio(false);
std::locale loc("en_US.UTF-8"); // You can also use "" for the default system locale
std::wcout.imbue(loc); // Use it for output
std::wfstream file("test.cpp");
file.imbue(loc); // Use it for file input
std::wstring str;
std::getline(file, str); // str.size() will be 6
std::wcout << str << std::endl;
}
您可能会问std::ios::sync_with_stdio(false); 是什么意思。这是必需的,因为默认情况下 C++ 流与 C 流保持同步。这很好,因为您可以在同一个程序上同时使用cout 和printf。我们必须禁用它,因为 C 流会破坏 utf-8 编码并在输出中产生垃圾。