(我假设您使用的是 Windows,因为在 UTF-8 文件中使用 U+FEFF 作为签名主要是 Windows 的事情,应该在其他地方避免使用)
您可以将文件作为 UTF-8 文件打开,然后检查第一个字符是否为 U+FEFF。您可以通过打开一个普通的基于字符的 fstream 来做到这一点,然后使用 wbuffer_convert 将其视为另一种编码中的一系列代码单元。 VS2010 对 char32_t 还没有很好的支持,所以下面在 wchar_t 中使用 UTF-16。
std::fstream fs(filename);
std::wbuffer_convert<std::codecvt_utf8_utf16<wchar_t>,wchar_t> wb(fs.rdbuf());
std::wistream is(&wb);
// if you don't do this on the stack remember to destroy the objects in reverse order of creation. is, then wb, then fs.
std::wistream::int_type ch = is.get();
const std::wistream::int_type ZERO_WIDTH_NO_BREAK_SPACE = 0xFEFF
if(ZERO_WIDTH_NO_BREAK_SPACE != ch)
is.putback(ch);
// now the stream can be passed around and used without worrying about the extra character in the stream.
int i;
readFromStream<int>(is,i);
请记住,这应该在整个文件流上完成,而不是在您的字符串流上的 readFromFile 中完成,因为只有当它是整个文件中的第一个字符(如果有的话)时,才应该忽略 U+FEFF。它不应该在其他任何地方进行。
另一方面,如果您喜欢使用基于字符的流并且只想跳过 U+FEFF(如果存在),那么 James Kanze 的建议似乎不错,所以这里有一个实现:
std::fstream fs(filename);
char a,b,c;
a = fs.get();
b = fs.get();
c = fs.get();
if (a != (char)0xEF || b != (char)0xBB || c != (char)0xBF) {
fs.seekg(0);
} else {
std::cerr << "Warning: file contains the so-called 'UTF-8 signature'\n";
}
此外,如果您想在内部使用 wchar_t,codecvt_utf8_utf16 和 codecvt_utf8 构面具有可以为您消耗“BOM”的模式。唯一的问题是,wchar_t 现在被广泛认为毫无价值*,因此您可能不应该这样做。
std::wifstream fin(filename);
fin.imbue(std::locale(fin.getloc(), new std::codecvt_utf8_utf16<wchar_t, 0x10FFFF, std::consume_header));
* wchar_t 毫无价值,因为它被指定只做一件事;提供一个固定大小的数据类型,可以表示区域设置字符库中的任何代码点。它不提供在 语言环境之间的通用表示(即,相同的wchar_t 值在不同的语言环境中可以是不同的字符,因此您不一定要转换为wchar_t,切换到另一个语言环境,然后再转换回到char 以进行iconv 类似的编码转换。)
固定大小的表示本身没有价值,有两个原因;首先,许多代码点具有语义含义,因此理解文本意味着您无论如何都必须处理多个代码点。其次,某些平台(例如 Windows)使用 UTF-16 作为 wchar_t 编码,这意味着单个 wchar_t 甚至不一定是代码点值。 (以这种方式使用 UTF-16 是否符合标准是模棱两可的。标准要求语言环境支持的每个字符都可以表示为单个 wchar_t 值;如果没有语言环境支持 BMP 之外的任何字符,则 UTF-16可以看作是一致的。)