读取多语言文件-wchar_t vs char?

时间:2018-07-15 20:59:16

标签: c++

对我来说,了解Unicode,语言环境,宽字符和转换是一种可怕的经历。

我需要阅读一个包含俄语和英语,中文和乌克兰字符的文本文件,一次

我的方法是按字节块读取文件,然后对块进行操作,并在单独的线程上进行快速读取。 (Link)

这是使用std::ifstream.read(myChunkBuffer, chunk_byteSize)

完成的

但是,我知道如果坚持使用char,则无法通过255个组合来表示多语言文件中的任何字符。


为此,我将所有内容都转换为wchar_t,并希望做到最好。

我也了解Sys.setlocale(locale = "Russian") (Link),但不是然后将每个字符解释为俄语吗?在解析字节时,我不知道何时在四种语言之间切换。

在Windows操作系统上,我可以创建一个.txt文件并输入“Привет!Hello!”。在记事本++程序中,它将保存文件并用相同的字母重新打开。它会以某种方式秘密地在每个字符后添加不可见的标记,以了解何时解释为俄语,何时解释为英语吗?


我目前的理解是:将所有内容都保存为wchar_t(双字节),将任何文件解释为UTF-16(双字节)-是否正确?

此外,我希望代码可以保持跨平台。

对不起,菜鸟

5 个答案:

答案 0 :(得分:2)

不幸的是,标准的c ++对您的情况没有任何真正的支持。 (例如unicode in c++-11

您将需要使用支持它的文本处理库。像this one

答案 1 :(得分:2)

最重要的问题是,该文本文件位于什么 encoding 中。它很可能不是 个字节编码,而是某种Unicode(因为没有一种方式是将俄语和中文保存在一个文件中,否则称为AFAIK)。因此...运行file <textfile.txt>或同等功能,或在十六进制编辑器中打开文件,以确定编码(可以是UTF-8,UTF-16,UTF-32等),然后采取适当措施

不幸的是,

wchar_t对于便携式编码来说是毫无用处的。回到Microsoft决定该数据类型应为什么时,所有Unicode字符都适合16位,因此正是他们所追求的。当Unicode扩展到21位时,Microsoft保留了原来的定义,并最终使它们的API使用UTF-16编码(这打破了wchar_的“广泛”性质)。另一方面,“ Unixes”将wchar_t设为32位并使用UTF-32编码,因此...

解释不同的编码超出了简单问答的范围。 Joel Spolsky(“ The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)”)的一篇文章虽然很好地解释了Unicode。还有其他编码,我做了a table that shows the ISO/IEC 8859 encodings and common Microsoft codepages side by side

C ++ 11引入了char16_t(用于UTF-16编码的字符串)和char32_t(用于UTF-32编码的字符串),但是该标准的某些部分不能完全正确地处理Unicode (上/下转换,正确处理归一化/未归一化字符串的比较,...)。如果您想一探究竟,用于处理C / C ++中所有Unicode(包括从Unicode到其他编码的转换)的去库是ICU

答案 2 :(得分:2)

好吧,让我们这样做。让我们提供一个实用解决方案,解决从UTF-8编码的文件中读取文本并将其变成宽字符串而不丢失任何信息的特定问题。

一旦我们能够做到这一点,就应该可以了,因为这里提供的实用程序函数通常可以处理所有UTF-8到宽字符串的转换(反之亦然),而这正是您所缺少的。

那么,首先,您将如何读取数据?好吧,那很容易。因为在一个级别上,UTF-8字符串只是chars的序列,所以您可以出于多种目的简单地以这种方式对待它们。因此,您只需要对任何文本文件进行处理,例如:

std::ifstream f;
f.open ("myfile.txt", std::ifstream::in);
if (!f.fail ())
{
    std::string utf8;
    f >> utf8;
    // ...
}

到目前为止,一切都很好。一切看起来都很简单。

但是现在,要使处理我们刚刚读入的字符串变得更加容易(因为在代码中处理多字节字符串非常麻烦),我们需要将其转换为所谓的宽字符串在我们尝试对其进行任何操作之前。实际上有几种风格(由于不确定wchar_t在任何特定平台上的实际宽度),但是现在我将坚持wchar_t来简化事情,而且进行转换实际上比您想象的要容易。

因此,事不宜迟,这是您的转换函数(这是您购买机票的目的):

#include <string>
#include <codecvt>
#include <locale>

std::string narrow (const std::wstring& wide_string)
{
    std::wstring_convert <std::codecvt_utf8 <wchar_t>, wchar_t> convert;
    return convert.to_bytes (wide_string);
}

std::wstring widen (const std::string& utf8_string)
{
    std::wstring_convert <std::codecvt_utf8 <wchar_t>, wchar_t> convert;
    return convert.from_bytes (utf8_string);
}

我,这很简单,为什么这些票最初要花那么多钱?

我想那就是我真正要说的。我认为,根据您在问题中的发言,您已经对自己想做的事情有了一个很清楚的了解,只是不知道如何实现(也许还没有完全把所有的点都联系在一起)但是),以防万一,您可以随意使用 all 上的std :: basic_string方法,并且一切都会“只是工作”。而且,如果您需要将其转换回UTF-8字符串以(例如)将其写到文件中,那么现在就变得很简单了。

在最出色的Wandbox上测试程序。稍后我会整理这篇文章,仍然有几句话要说。现在该吃早餐了:)请在评论中提出任何问题。

注释(添加为修改内容):

  • codecvt在C ++ 17中已弃用(不知道为什么),但是如果将它的使用限制为仅使用这两个函数,则不必担心。如果有什么更好的事情(提示,提示,亲爱的标准人员),人们总是可以重写它们。
  • 我相信
  • codecvt可以处理其他字符编码,但是就我而言,谁在乎呢?
  • 如果std::wstring(基于wchar_t)在您的特定平台上不适合您使用,则您始终可以使用std::u16stringstd::u32string。 / li>

答案 3 :(得分:1)

这是第二个答案-关于Microsoft对wchar_t缺乏标准合规性-因为感谢标准委员会对他们的赌注,这种情况比需要的情况更加混乱。

请注意,Windows上的wchar_t仅16位宽,众所周知,如今的Unicode字符比现在多得多,因此,从表面上看,Windows不是合规性(尽管我们都知道,他们这样做是有原因的。)

因此,继续前进,我要感谢Bo Persson挖掘了这个(重点是我的):

标准在[basic.fundamental]/5中说:

  

类型wchar_­t是一种独特的类型,其值可以代表在支持的语言环境中指定的最大扩展字符集的所有成员的不同代码。类型wchar_­t与其他称为其基础类型的整数类型之一具有相同的大小,符号和对齐要求。类型char16_­tchar32_­t表示分别与uint_­least16_­t中的uint_­least32_­t<cstdint>相同的大小,符号和对齐方式的不同类型,称为基础类型

嗯。 “在受支持的语言环境中。”这是怎么回事?

好吧,我不知道,而且我也不怀疑是谁写的。它只是被放置在那里以使Microsoft摆脱困境,就这么简单。只是说话而已。

正如其他人在这里评论的(实际上),该标准是一团糟。有人应该在此放置一些其他人可以理解的东西。

答案 4 :(得分:0)

c ++标准将wchar_t定义为将支持任何代码点的类型。在linux上,这是正确的。 MSVC违反了该标准,并将其定义为16位整数,这太小了。

因此,处理字符串的唯一可移植方法是在输入时将其从本机字符串转换为utf-8,并在输出点将其从utf-8转换为本机字符串。

您当然需要使用#ifdef魔术来选择正确的转换和I / O调用,具体取决于操作系统。

不遵守标准是我们无法拥有美好事物的原因。