忽略C++中的字节顺序标记,从流中读取
Ignore byte-order marks in C++, reading from a stream
我有一个函数可以读取ifstream
:中单行上一个变量(整数、双精度或布尔值)的值
template <typename Type>
void readFromFile (ifstream &in, Type &val)
{
string str;
getline (in, str);
stringstream ss(str);
ss >> val;
}
然而,对于使用编辑器在第一行开头插入BOM(字节顺序标记)创建的文本文件,它失败了,不幸的是,其中包括{Note,Word}pad.如果str
开头有字节顺序标记,我如何修改此函数以忽略它?
您可以将文件打开为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可以被视为符合标准。)
您必须从读取流的第一个或两个字节开始,然后决定它是否是BOM的一部分。有点疼,因为单个字节只能putback
,而您通常会想读四本。最简单的解决方案是打开文件,读取初始字节,记住需要跳过的字节数,然后返回开始并跳过它们。
使用一个不太干净的解决方案,我通过删除非打印字符来解决问题:
bool isNotAlnum(unsigned char c)
{
return (c < ' ' || c > '~');
}
str.erase(remove_if(str.begin(), str.end(), isNotAlnum), str.end());
这里有一个简单的C++函数,可以跳过Windows上输入流上的BOM。这假设了字节大小的数据,如UTF-8:
// skip BOM for UTF-8 on Windows
void skip_bom(auto& fs) {
const unsigned char boms[]{ 0xef, 0xbb, 0xbf };
bool have_bom{ true };
for(const auto& c : boms) {
if((unsigned char)fs.get() != c) have_bom = false;
}
if(!have_bom) fs.seekg(0);
return;
}
它只需检查UTF-8 BOM签名的前三个字节,如果它们都匹配,则跳过它们。如果没有BOM就没有害处。
编辑:这适用于文件流,但不适用于cin
。我发现它确实可以在带有GCC-11的Linux上使用cin
,但这显然是不可移植的。请参阅下面的@Dúthomhas评论。
- 独立读取-修改-写入顺序
- C++编译器是否优化了顺序静态变量读取?
- 内存排序或读取-修改-写入操作,仅(读/写)内存顺序
- 如何在 C++ 中将从文本文件中读取的元素推送和弹出到数组中,并按 Revserse 顺序输出堆栈?
- 如何从OpenCV中的目录中按顺序读取文件并使用它进行处理?
- 从文件中读取并按字母顺序排序
- 使用相同的文件对象按顺序写入和读取文件
- 如何以相反的顺序读取C++程序的输入
- 插座读取和写作未按顺序进行
- C++按顺序读取多个输入文件
- 从管道中按顺序读取OpenEXR
- 如何保存字符串并按顺序读取/写入它们
- 如何从OpenCV中的目录中按顺序读取文件
- 在数组中使用常量字符 * 来读取顺序文件
- 为什么我的ifstream程序以错误的顺序读取单词
- 如何使用C++以相反的顺序读取文件
- 忽略C++中的字节顺序标记,从流中读取
- 从文本文件中读取顺序部分
- 按顺序读取大文件
- c++中顺序插入/读取/删除最快的数据结构是什么?