19

老实说,我只是没有在 C++ 标准库中得到以下设计决策。将宽字符写入文件时,wofstream转换wchar_tchar字符:

#include <fstream>
#include <string>

int main()
{
    using namespace std;

    wstring someString = L"Hello StackOverflow!";
    wofstream file(L"Test.txt");

    file << someString; // the output file will consist of ASCII characters!
}

我知道这与标准有关codecvt。有codecvtfor utf8in Boost。此外,Martin York 在 SO 上有一个codecvtfor 。问题是为什么要转换宽字符?为什么不按原样写字符!utf16standard codecvt

另外,我们是要真正unicode streams使用 C++0x 还是我在这里遗漏了什么?

4

5 回答 5

13

第一个问题的一个非常部分的答案:文件一个字节序列,因此,在处理wchar_t's 时,至少必须在and之间进行一些转换。进行这种“智能”转换需要了解字符编码,因此这就是允许这种转换依赖于语言环境的原因,因为在流的语言环境中使用了一个方面。wchar_tchar

然后,问题是如何在标准要求的唯一语言环境中进行转换:“经典”语言环境。对此没有“正确”的答案,因此标准对此非常模糊。我从您的问题中了解到,您认为在 wchar_t[] 和 char[] 之间盲目地强制转换(或 memcpy()-ing)是一个好方法。这不是不合理的,实际上是(或至少是)在某些实现中所做的。

另一个 POV 是,由于 codecvt 是一个语言环境方面,因此可以合理地预期转换是使用“语言环境的编码”进行的(我在这里是手动的,因为这个概念很模糊)。例如,人们会期望土耳其语语言环境使用 ISO-8859-9,或者日语语言环境使用 Shift JIS。通过相似性,“经典”语言环境将转换为此“语言环境的编码”。显然,微软选择了简单的修剪(如果我们假设它代表 UTF-16 并且我们停留在基本的多语言平面,这会导致 IS-8859-1 wchar_t),而我所知道的 Linux 实现决定坚持使用 ASCII。

对于你的第二个问题:

另外,我们是要使用 C++0x 获得真正的 unicode 流,还是我在这里遗漏了什么?

在 n2857(我手头最新的 C++0x 草案)的 [locale.codecvt] 部分中,可以阅读:

专业化codecvt<char16_t, char, mbstate_t>在 UTF-16 和 UTF-8 编码方案codecvt <char32_t, char, mbstate_t>之间转换,专业化在 UTF-32 和 UTF-8 编码方案之间转换。codecvt<wchar_t,char,mbstate_t>在窄字符和宽字符的本机字符集之间进行转换。

在 [locale.stdcvt] 部分,我们发现:

对于 facet codecvt_utf8: — facet 应在程序内的 UTF-8 多字节序列和 UCS2 或 UCS4(取决于 Elem 的大小)之间转换。[...]

对于 facet codecvt_utf16: — facet 应在程序内的 UTF-16 多字节序列和 UCS2 或 UCS4(取决于 Elem 的大小)之间转换。[...]

对于构面codecvt_utf8_utf16: — 构面应在程序内的 UTF-8 多字节序列和 UTF-16(一个或两个 16 位代码)之间转换。

所以我想这意味着“是”,但您必须更准确地了解“真正的 unicode 流”的含义才能确定。

于 2009-10-02T13:21:05.703 回答
7

C++ 用于字符集的模型继承自 C,因此至少可以追溯到 1989 年。

两个要点:

  • IO 是用 char 来完成的。
  • 确定字符序列化的宽度是语言环境的工作
  • 默认语言环境(名为“C”)非常小(我不记得标准的约束,这里它只能将 7 位 ASCII 处理为窄字符集和宽字符集)。
  • 有一个环境确定的语言环境,名为“”

所以要得到任何东西,你必须设置语言环境。

如果我使用简单的程序

#include <locale>
#include <fstream>
#include <ostream>
#include <iostream>

int main()
{
    wchar_t c = 0x00FF;
    std::locale::global(std::locale(""));
    std::wofstream os("test.dat");
    os << c << std::endl;
    if (!os) {
        std::cout << "Output failed\n";
    }
}

它使用环境语言环境并将代码 0x00FF 的宽字符输出到文件。如果我要求使用“C”语言环境,我会得到

$ env LC_ALL=C ./a.out
Output failed

语言环境无法处理宽字符,并且由于 IO 失败,我们会收到问题通知。如果我运行询问 UTF-8 语言环境,我会得到

$ env LC_ALL=en_US.utf8 ./a.out
$ od -t x1 test.dat
0000000 c3 bf 0a
0000003

(od -t x1 只是转储以十六进制表示的文件),这正是我对 UTF-8 编码文件的期望。

于 2009-10-02T15:10:10.237 回答
3

我不知道 wofstream。但是 C++0x 将包括新的 distict 字符类型(char16_t,char32_t),保证宽度和有符号(无符号),可移植用于 UTF-8、UTF-16 和 UTF-32。此外,还会有新的字符串文字(例如 u"Hello!" 用于 UTF-16 编码的字符串文字)

查看最新的C++0x 草案 (N2960)

于 2009-10-02T13:22:42.330 回答
2

对于你的第一个问题,这是我的猜测。

IOStreams 库是在几个关于编码的前提下构建的。例如,对于 Unicode 和其他不太常见的编码之间的转换,假设是这样的。

  • 在您的程序中,您应该使用(固定宽度)宽字符编码。
  • 只有外部存储应该使用(可变宽度)多字节编码。

我相信这就是 std::codecvt 的两个模板特化存在的原因。一种在 char 类型之间进行映射(也许您只是在使用 ASCII),另一种在 wchar_t(程序内部)和 char(外部设备)之间进行映射。因此,每当您需要执行转换为多字节编码时,您都应该逐字节进行。请注意,当您从/向多字节编码读取/写入每个字节时,您可以编写一个处理编码状态的方面。

以这种方式思考 C++ 标准的行为是可以理解的。毕竟,您使用的是宽字符 ASCII 编码(假设这是您平台上的默认设置并且您没有切换语言环境)字符串。“自然”转换是将每个宽字符 ASCII 字符转换为普通(在本例中为一个字符)ASCII 字符。(转换存在并且很简单。)

顺便说一句,我不确定您是否知道,但您可以通过创建一个为转换返回noconv的构面来避免这种情况。然后,您将拥有带有宽字符的文件。

于 2009-10-02T15:13:41.417 回答
2

看看这个: 类 basic_filebuf

您可以通过使用 pubsetbuf设置一个字符缓冲区来更改默认行为。一旦你这样做了,输出将是 wchar_t 而不是 char。

换句话说,对于您的示例,您将拥有:

wofstream file(L"Test.txt", ios_base::binary); //binary is important to set!  
wchar_t buffer[128];  
file.rdbuf()->pubsetbuf(buffer, 128);  
file.put(0xFEFF); //this is the BOM flag, UTF16 needs this, but mirosoft's UNICODE doesn't, so you can skip this line, if any.  
file << someString; // the output file will consist of unicode characters! without the call to pubsetbuf, the out file will be ANSI (current regional settings)  
于 2010-08-12T14:08:25.517 回答