C++字符串字面值仍然令人困惑。

9

我一直在阅读关于Unicode的文章,但意识到我仍然不知道该怎么做。

作为Windows平台上的C++程序员,我的老师们教给我的规则大多都是相同的:始终使用Unicode字符集;如果可能的话,使用模板化或TCHAR;优先使用wchar_t、std::wstring而不是char和std::string。

#include <tchar.h>
#include <string>
typedef std::basic_string<TCHAR> tstring;
 // ...
static const char* const s_hello = "핼로"; // bad
static const wchar_t* const s_wchar_hello = L"핼로" // better
static LPCTSTR s_tchar_hello = TEXT("핼로") // even better
static const tstring s_tstring_hello( TEXT("핼로") ); // best

不知怎么地,我搞混了,我自己认为如果我说“something”,那么它就是ASCII格式,如果我说L“something”,那么它就是Unicode格式。然后我读到这个:
wchar_t类型是一个独特的类型,它的值可以代表所支持语言环境中最大扩展字符集的所有成员的不同代码(22.3.1)。 wchar_t类型应该具有与其他整数类型之一相同的大小、符号和对齐要求(3.11),称为其基础类型。char16_t和char32_t分别表示具有与uint_least16_t和uint_least32_t相同的大小、符号和对齐要求的不同类型,在《C ++标准库》中称为基础类型。
那又怎样呢?如果我的语言环境从代码页949开始,wchar_t的扩展范围是从949 + 2^(sizeof(wchar_t)*8)?而且它的表述方式听起来像“我不关心你的C++实现使用UTF编码还是什么”。
至少,我能理解一切都取决于应用程序所在的语言环境。因此,我进行了测试:
#define TEST_OSTREAM_PRINT(x) \
std::cout << "----" << std::endl; \
std::cout << "cout : " << x << std::endl; \
std::wcout << "wcout : " << L##x << std::endl;

int main()
{
    std::ostream& os = std::cout;

    std::cout << " * Info : " << std::endl
              << "     sizeof(char) : " << sizeof(char) << std::endl
              << "     sizeof(wchar_t) : " << sizeof(wchar_t) << std::endl
              << "     littel endian? : " << IsLittelEndian() << std::endl;
    std::cout << " - LC_ALL: " << setlocale(LC_ALL, NULL) << std::endl;
    std::cout << " - LC_CTYPE: " << setlocale(LC_CTYPE, NULL) << std::endl;

    TEST_OSTREAM_PRINT("핼로");
    TEST_OSTREAM_PRINT("おはよう。");
    TEST_OSTREAM_PRINT("你好");
    TEST_OSTREAM_PRINT("resume");
    TEST_OSTREAM_PRINT("résumé");

    return 0;
}

那么输出结果是:

Info
 sizeof(char) = 1
 sizeof(wchar_t) = 2
 LC_ALL = C
 LC_CTYPE = C
----
cout : 핼로
wcout : ----
cout : おはよう。
wcout : ----
cout : ?好
wcout : ----
cout : resume
wcout : resume
----
cout : r?sum?
wcout : r?um

使用韩国语环境的另一个输出:

Info
 sizeof(char) = 1
 sizeof(wchar_t) = 2
 LC_ALL = Korean_Korea.949
 LC_CTYPE = Korean_Korea.949
----
cout : 핼로
wcout : 핼로
----
cout : おはよう。
wcout : おはよう。
----
cout : ?好
wcout : ----
cout : resume
wcout : resume
----
cout : r?sum?
wcout : resume

另一个输出:


Info
 sizeof(char) = 1
 sizeof(wchar_t) = 2
 LC_ALL = fr-FR
 LC_CTYPE = fr-FR
----
cout : CU·I
wcout : ----
cout : ªªªIªeª|¡£
wcout : ----
cout : ?u¿
wcout : ----
cout : resume
wcout : resume
----
cout : r?sum?
wcout : resume

事实证明,如果我没有提供正确的区域设置,无论我使用char还是wchar_t,应用程序都无法处理某些字符范围。这不仅是一个问题。Visual Studio会发出警告:

warning C4566: character represented by universal-character-name '\u4F60' cannot be represented in the current code page (949)

我不确定这是否描述了我所获得的输出或其他内容。

问题:什么是最佳实践,为什么?如何使应用程序平台/实现/国家独立?源代码中的字符串文字会发生什么?应用程序如何解释字符串值?


1
为了最大兼容性:请将源代码中的字符保持为基本的ASCII字符。使用UTF8编码,使用字符串中定义的\x字节。最新版本的C++中的新u8"\u1234"功能将使此过程更加容易,但Visual Studio尚不支持它。另请参阅此问题:https://dev59.com/GW865IYBdhLWcg3wivGi - JCx
1
TCHAR是一个巨大的麻烦。除非你想支持旧版本的Windows,否则只需使用Windows宽字符串。为了与其他内容兼容,您可以使用UTF-8存储字符串,并在使用Windows API函数时进行转换。 - chris
2个回答

3

C++不具备正常的Unicode支持。如果不使用第三方库,你就无法在C++中编写正常的全球化应用。请阅读这篇深入的SO答案。如果你真的需要编写一个使用Unicode的应用程序,我建议使用ICU库。


3
在Windows系统中,Microsoft 保证 wchar_t 支持Unicode,因此L"핼로"是生成UTF-16字符串字面量的正确方式,作为const wchar_t*。在其他平台上,这并不一定成立,如果您需要使代码可移植,应使用C++11 Unicode字符串字面量(u8"..."u"..."U"...")——例如,使用u8"핼로"生成UTF-8编码的const char*(自Visual Studio 2015起)。
您遇到的另一个问题与Visual Studio如何解释源文件的编码有关。例如,在EUC-KR(代码页949)中,被编码为0xAA 0xAA,这是代码页1252(fr-FR)中ªª的编码方式。也就是说,如果您在EUC-KR中保存了包含的源文件,但在fr-FR语言环境中编译它,您的文字将会被编码为ªª
如果您需要在源代码中包含非ASCII字符,您应该使用UTF(即UTF-8/16/32)并添加显式BOM进行保存,详情请参见此问题的答案

你说得对。我本来以为微软会将宽字符字符串字面值(L"Something")转换为UCS-2,而超出BMP范围的字符会让它们出错。然而,额外的测试告诉我它们实际上被正确地解释为UTF-16。谢谢。 - user2883715

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接