如何在控制台中制作动态字符串以使用 UTF-8?

How can I make dynamic strings to work with UTF-8 in console?

提问人:Tomáš Zato 提问时间:6/19/2014 最后编辑:CommunityTomáš Zato 更新时间:6/19/2014 访问量:2079

问:

这里关于 SO 的大多数答案问题都放在任何 UTF-8 字符串之前。我没有找到它是什么的解释,在源代码中,根据我的 IDE,常量在 .Lwinnt.h

这就是我使用它的方式,不知道它是什么:

std::wcout<<L"\"Přetečení zásobníku\" is Stack overflow in Czech.";

显然,常量串联不能应用于变量:

void printUTF8(const char* str) {
  //Does not make the slightest bit of sense
  std::wcout<<L str; 
}

那么它是什么以及如何将其添加到动态字符串中呢?

C++ Windows UTF-8 控制台-应用程序 IOSTREAM

评论

0赞 carveone 6/19/2014
L 是一个 16 位的指示符(大多数情况下,它可以是理论上的任何内容),而 UTF-8 字符串不是 16 位。
0赞 Tomáš Zato 6/19/2014
哦,好吧,实际上我可能正在使用 UTF-16......
0赞 carveone 6/19/2014
这里还有另一个问题,那就是控制台。我会更新我的帖子,但你能告诉我们你在控制台上得到了什么吗?
0赞 Tomáš Zato 6/19/2014
目前我的代码可以工作 - 那是因为我正在使用您已经建议的函数。但是,我会重新考虑使用 UTF-8。
0赞 carveone 6/19/2014
我可能回答了一个当时没有被问到的问题!如果您有一个 UTF-8 字符串,则可以使用 MultiByteToWideChar() 函数将其转换为 wcout 想要的内容。这将使一个普通的字符串变成一个“L”字符串。

答:

1赞 carveone 6/19/2014 #1

L 是向 C 编译器指示字符串由“宽字符”组成的。在 Windows 中,这些是 UTF-16 - 您放入字符串中的每个字符都是 16 位或两个字节宽:

L"This is a wide string"

相比之下,UTF-8 字符串始终是由字节组成的字符串。ASCII 字符(A-Z、0-9 等)的编码方式一直如此 - 在 0x00 到 0x7F(或 0 到 127)的范围内。国际字符(如ř)使用0x80到0xFF范围内的多个字节进行编码 - 维基百科上有一个很好的解释。优点是可以使用普通的 C 字符串来表示。

"This is an ordinary string, but also a UTF-8 string"

"This is a C cedilla in UTF-8: \xc3\x87"

但是,如果您在实际代码中键入这些国际字符,则编辑器需要知道您键入的是 UTF-8,以便它可以正确编码字符 - 就像上面的 C cedilla 一样。然后,字符串将正确地传递给您的函数。

在您的情况下,您的注释表明您正在使用 UTF-16。在这种情况下,还有另外两个问题:

  • 默认情况下,控制台将无法正确输出 Unicode 字符。您需要将字体更改为 truetype 字体,例如 Lucida Console

  • 您还需要将输出模式更改为 Unicode UTF-16 模式。您可以通过以下方式执行此操作:

    _setmode(_fileno(stdout), _O_U16TEXT);

代码示例:

#include <iostream>
#include <io.h>
#include <fcntl.h>

int wmain(int argc, wchar_t* argv[])
{
    _setmode(_fileno(stdout), _O_U16TEXT);
    std::wcout << L"Přetečení zásobníku is Stack overflow in Czech." << std::endl;
}

评论

0赞 Mark Ransom 6/19/2014
为什么在使用时是必需的?这似乎不对。_setmodewcout
0赞 carveone 6/19/2014
我相信(但不确定)否则控制台将假设 ANSI 并将 UTF-16 转换为垃圾!
0赞 Cheers and hth. - Alf 6/19/2014
@MarkRansom:宽流的目的是与外部面向字节的编码相互转换。好吧,除了我所看到的将 8 个标准流对象映射到 3 个操作系统字节流的设计,其中没有办法检查某处的任何特定用途是否无效(C 级“方向”,宽或窄),很糟糕。;-)
1赞 MSalters 6/19/2014 #2

L""是一个 WIDE 字符串。也就是说,它是一个.UTF-8 字符串不能很宽,因为它们是多字节(可变长度)的。VC++ 略有错误,使宽字符串可变长度,准确地说是 UTF-16。但通常它们是 UTF-32。wchar_t[1]

多字节字符串的问题在于有许多不同的编码,UTF-8 只是其中之一。事实上,Windows 本身并不支持 UTF-8 编码。 例如,可以使用除 UTF-8 以外的任何编码。只有一个例外,那就是你在这里需要的。MessageBoxA()MultiByteToWideChar(CP_UTF8, ...)

评论

0赞 Cheers and hth. - Alf 6/19/2014
re “VC++ 略有错误,使宽字符串可变长度”,这对我来说没有意义。你想写什么?您可能指的是 C99/C++11 规则,我猜是政治/狂热的结果,这是在 Windows 和视觉 C++ 之后很久才出现的?
0赞 MSalters 6/19/2014
窄字符串可以是多字节的(例如 可以小于 )。宽字符串不可能,每个字符都应该适合一个,而且只有 .当然,Windows 定义了 ,这在历史上映射到 。映射到是事情变得复杂的地方。mbclenstrlenwchar_twcslenWCHARunsigned shortWCHARwchar_t
0赞 Cheers and hth. - Alf 6/19/2014
我基本上同意这一点,但它在两个方面具有误导性。首先,概念字符(显示为单个图形)必须表示为单个 32 位编码值的概念。一般来说,情况并非如此。也就是说,C++ 标准库也用 UTF-32 搞砸了:这只是一个程度问题,对于政治来说,这是一个可以让无知的群众相信什么的问题。第二种方式有点误导,因为它暗示 Windows WCHAR 在某个时候映射到 wchar_t。我不记得从来没有这样过。即,C99 有错。
0赞 Cheers and hth. - Alf 6/19/2014
更新:我发现我认为在 C99 中引入的措辞已经在 C90 中存在。所以,Microsoft错了,而不是 C99 和 Unix 土地政治,正如我所坚持的那样。我是罪魁祸首!
0赞 Harry Johnston 6/20/2014
Windows NT 的第一个版本于 1993 年发布;当时,编码整个 Unicode 字符集只需要 16 位。字符集在 1996 年引入的 Unicode 2.0 中进行了扩展。到那时,我认为Microsoft更改其定义为时已晚,他们能做的最好的事情就是将编码从UCS-2更改为UTF-16(从Windows 2000开始)。wchar_t
0赞 Cheers and hth. - Alf 6/19/2014 #3

回复您的实际问题

什么是[前缀]以及如何将其添加到动态字符串中?L

这与我写这篇文章时的问题标题非常不同,即“如何制作动态字符串以在控制台中使用 UTF-8?

简而言之,UTF-8 是 Unicode 的一种编码,其中基本编码单元是 8 位,通常称为字节(更准确地说是八位字节),而前缀形成字符或字符串文字,其中编码单元通常为 16 或 32 位——在 Windows 中它是 16 位,就像原始 Unicode 一样。L

宽字符或字符串文本基于类型而不是 。wchar_tchar

在 Windows 中,宽字符串编码为 UTF-16。最常见的六万个左右的Unicode字符用单个值表示,但一些很少使用的中文表意文字等需要两个连续的值,称为代理项对wchar_twchar_t

在 Windows 中使用 16 位编码单元是在 1992 年左右建立的。我不确定 UTF-16 是什么时候被采用的(作为当时的 UCS-2 编码的扩展),只是晚了一点。因此,早在 C99 要求宽字符集的所有字符都应用单个wchar_t值表示之前,就已经确立了这一点。这一要求似乎是一种纯粹的政治策略,确保没有Windows C编译器可以正式符合,这是一种仅适用于Unix领域的通用ISO编程语言标准。不幸的是,由于 C++ 11 基于 C99,我们现在在 C++11 中也有它,确保没有 Windows C++ 编译器可以完全符合。纯粹的白痴。如果你问我。

勘误表,重新删除了上面的文字:根据维基百科关于它的文章,关于单个字符足以满足“扩展字符集”中任何字符的措辞在 C90 中已经存在。这使得Windows与C和C++标准之间的不兼容是Microsoft的错,而不是C委员会的错。它似乎仍然是政治性的,而且相当愚蠢,但(开明)其他人应该受到指责,而不是我最初坚持的......wchar_t


使用宽动态字符串的一种方法是使用 ,from the header。std::wstring<string>

使用 Visual C++,您可以使用 wmain 函数而不是标准函数,作为获取宽命令行参数的简单方法。main

wmainMinGW64 (IIRC) g++ 也支持,尽管普通 MinGW g++ 还不支持,但从 g++ 4.8.something 开始。但是,就 Windows API 而言,它很容易实现。除非你需要严格的符合标准的代码来提供特殊的主要功能特性,例如能够用或不带参数来声明它,但是嘿,让我们务实一点。


使用 Visual C++ 12.0 和 g++ 4.8.2 正常编译的示例:

// Source encoding: UTF-8 with BOM.

#include <io.h>         // _setmode
#include <fcntl.h>      // _O_WTEXT

#include <iostream>     // std::wcout, std::endl
#include <string>       // std::wstring
using namespace std;

auto main()
    -> int
{
    _setmode( _fileno( stdin ), _O_WTEXT );
    _setmode( _fileno( stdout ), _O_WTEXT );

    wcout << L"Hi, what’s your name? ";
    wstring username;
    getline( wcin, username );
    wcout << L"Welcome to Windows C++, " << username << "!" << endl;
}

请注意,使用 Windows ANSI 源时,除非使用适当的编译器选项指定源编码,否则不会使用 g++ 进行编译。