老实说,我在C ++标准库中没有得到以下设计决定。将宽字符写入文件时,wofstream
会将wchar_t
转换为char
个字符:
#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
有关。 Boost
中的codecvt
utf8
。另外,codecvt
utf16
standard codecvt
unicode streams
。问题是为什么 {{1}}转换宽字符?为什么不按原样写下角色呢!
另外,我们是否会使用C ++ 0x获得真正的{{1}}或者我在这里遗漏了什么?
答案 0 :(得分:13)
第一个问题的一个非常局部的答案:文件 是一个字节序列,所以在处理wchar_t
时,至少一些转换必须在wchar_t
和char
之间发生。 “智能地”进行这种转换需要知道字符编码,因此这就是为什么允许这种转换依赖于语言环境,因为在流的语言环境中使用了一个方面。
然后,问题是如何在标准所要求的唯一区域设置中进行转换:“经典”转换。对此没有“正确”的答案,因此标准对此非常模糊。我从你的问题中了解到你认为在wchar_t []和char []之间盲目地进行(或memcpy() - ing)会是一个好方法。这不是不合理的,事实上在某些实现中是(或至少是)完成的。
另一个POV是,因为codecvt是一个语言环境方面,所以有理由期望使用“locale的编码”进行转换(我在这里手写,因为概念非常模糊)。例如,可以预期土耳其语语言环境使用ISO-8859-9或日语使用Shift JIS。通过相似性,“经典”语言环境将转换为此“语言环境的编码”。显然,微软选择简单修剪(如果我们假设wchar_t
代表UTF-16并且我们保持基本的多语言平面,则导致IS-8859-1),而我知道的Linux实现决定坚持ASCII。
关于你的第二个问题:
另外,我们是否会使用C ++ 0x获得真正的unicode流,或者我在这里遗漏了什么?
在n2857的[locale.codecvt]部分(我手头的最新C ++ 0x草案)中,可以读到:
专门化
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]部分,我们找到:
对于方面
codecvt_utf8
: - 方面应在程序内转换UTF-8多字节序列和UCS2或UCS4(取决于Elem的大小)。 [...]对于方面
codecvt_utf16
: - 方面应在程序内转换UTF-16多字节序列和UCS2或UCS4(取决于Elem的大小)。 [...]对于方面
codecvt_utf8_utf16
: - 方面应在程序内转换UTF-8多字节序列和UTF-16(一个或两个16位代码)。
所以我猜这意味着“是”,但你必须更准确地确定“真正的unicode流”的意思。
答案 1 :(得分:7)
C ++用于charsets的模型继承自C,因此可以追溯到至少1989年。
两个要点:
为了得到任何东西,你必须设置语言环境。
如果我使用简单程序
#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编码文件的期望。
答案 2 :(得分:3)
我不知道wofstream。但是C ++ 0x将包括保证宽度和签名(unsigned)的新distict字符类型(char16_t,char32_t),它可以便携地用于UTF-8,UTF-16和UTF-32。此外,还会有新的字符串文字(例如,对于UTF-16编码的字符串文字,你是“Hello!”)
查看最新的C++0x draft (N2960)。
答案 3 :(得分:2)
对于你的第一个问题,这是我的猜测。
IOStreams库是在几个有关编码的前提下构建的。例如,为了在Unicode和其他不常见的编码之间进行转换,假设它是。
我认为这就是std :: codecvt存在两个模板特化的原因。一个在char类型之间映射(可能你只是使用ASCII)和另一个映射wchar_t(程序内部)和char(外部设备)之间的映射。因此,无论何时需要执行到多字节编码的转换,都应该逐字节进行。请注意,当您从/向多字节编码读取/写入每个字节时,您可以编写一个处理编码状态的构面。
以这种方式思考C ++标准的行为是可以理解的。毕竟,您使用的是宽字符ASCII编码(假设这是您平台上的默认设置,并且您没有切换区域设置)字符串。 “自然”转换是将每个宽字符ASCII字符转换为普通(在本例中为一个字符)ASCII字符。 (转换存在并且很简单。)
顺便说一句,我不确定你是否知道,但你可以通过创建一个返回 noconv 转换的方面来避免这种情况。然后,您的文件将包含宽字符。
答案 4 :(得分:2)
检查一下: Class basic_filebuf
您可以使用pubsetbuf通过设置 wide char缓冲区来更改默认行为。 一旦你这样做,输出将是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)