如何使用'%s'格式说明符打印Unicode字符串?

6

我想使用%s调用printf()输出Unicode字符/字符串,但却没有打印任何内容。

如果我像下面这样调用printf()

 printf("\xE2\x98\xA0")

我得到了一个

但是,如果我使用%ls像这样:

printf("%ls", "☠")  /* or */
printf("%ls", L"☠") /* or */
printf("%ls", L"\xE2\x98\xA0")

我什么也没有打印出来;

另外,我如何声明一个带有Unicode字符的 wchar_t字符串?wchar_t wstro [50] = L"☠"不起作用。

我需要用malloc()分配一个wchar_t ,然后在其中放入Unicode数据吗?


1
你使用的是哪个操作系统?源文件保存为什么编码格式?在处理控制台I/O时,这些细节很重要。 - Mark Tolonen
@ArndtJonasson 你确定使用 printf("%ls", "☠") 成功了吗?格式 %ls 是用于 wchar_t* 的,也许你应该尝试 printf("%s", "☠") - Barmak Shemirani
@RemyLebeau 我在 ideone.com 上无法成功使用 wchar_t - 它期望 UTF8。编译器应该能够理解 wchar_t* 字符串,但非 Windows 系统可能不知道该怎么做。 - Barmak Shemirani
是的,问题仍然存在。当我使用wstro[2] = "0xC9"并printf("%ls\n", wstro)时,在标准输出上会出现错误消息“printf:无效或不完整的多字节或宽字符”。我正在使用Clion和Windows 10。在学校我使用Mac,结果相同。 - user9185695
警告:Microsoft在格式字符串中不遵循ISO C标准的%s%ls。请说明您是否使用Microsoft实现。 - M.M
显示剩余9条评论
3个回答

5

这个答案假设你正在使用MS Windows操作系统


很遗憾,我们已经进入2018年,但这些东西仍然不能正常工作。但是事情的现状是这样的: printf("\xE2\x98\xA0");(与printf("%s", "\xE2\x98\xA0");相同)可以工作,因为你只是将3个字符输出到输出流中。在C语言中没有Unicode或特殊字符处理。是终端环境寻找输出中的UTF-8字符串,并选择显示字形。 同样,如果你将输出写入文件(使用fprintf或流重定向),你会看到文件包含0xE2, 0x98, 0xA0,然后你可以选择使用文本文件查看器将UTF-8转换为显示字形。 这部分内容都很好,你可以(也应该)编写程序,只向FILE流写入UTF-8编码字符。
当我们想要输出字符时,问题就开始了。理论上,这应该是可行的:
printf("%ls", L"\u2620");   

预期的操作是调用wcstombs将Unicode代码点序列转换为多字节序列。但要使用哪种多字节格式?现在UTF-8已经普及,但过去还有其他格式,如ShiftJIS、Big-5等。您必须使用setlocale指定多字节格式。而区域设置的详细信息是实现定义的。
这就是问题所在。Windows不支持用于一般UTF-8输出的C语言区域设置。如果尝试使用setlocale(LC_CTYPE, ".65001");,它将无法工作。您可以通过使用受支持的区域设置来输出某些Unicode子集。例如,使用Japanese_Japan.932MSDN示例可行,将Unicode输入输出为Shift-JIS(而非UTF-8)。
更糟糕的是,如果您使用Windows API函数WideStringToMultiByte,它确实接受CP_UTF8的“locale”。您可以使用此函数将L"\u2620";转换为char缓冲区,并printf,从而产生UTF-8输出。
但是,您当然无法将其“插入”到FILE流处理中,该处理仅调用wcstombs而不是WideStringToMultiByte
为什么他们没有允许".UTF-8"作为wcstombs的语言环境呢?恶意行为?谁知道。
理论上下一步应该可以工作的是:

中的应该可以工作
FILE *fp = fopen("a.txt", "w");
fwide(fp, 1);
fwprintf(fp, L"\u2620");

然而实际上,MS运行时不会对进行任何操作;它不支持面向宽字符的流。Microsoft的系列实现实际上只输出窄字符,而不是宽字符,并且它们使用与窄printf系列相同的方法。

所以,那段代码不起作用,而来自日语wcstombs示例的代码(使用.932 CP集)输出多字节序列而不是原始宽字符。

要通过 API编写UTF-16文件,您实际上别无选择,只能使用窄字符并将其视为二进制文件。


你可以使用 wprintf(L"%s", L"☠"),但只能在调用 _setmode 的 Visual Studio 中使用。在其他编译器中,您必须使用 WriteConsoleW。像 '☠' 这样的字符不受默认控制台字体的支持,因此您还需要更改字体。在 Visual Studio 中可以做到这一点,但在其他编译器中需要更多的调整。如果您以二进制模式打开文件(BOM 将非常有用),则 fwprintf 将起作用。虽然我更喜欢将文件保存为 UTF8。在 Linux 中,printf("%s", "☠")printf("%s", u8"☠") 可能也可以使用,Mac 也是如此。 - Barmak Shemirani
自2018年起,Windows中有UTF-8语言环境。 - phuclv

5
你混淆了Unicode、UTF-8和wchar_t。
Unicode是抽象的,有代码点、组合字符和其他属性。
UTF-8是一种常见的编码Unicode的方式,它与ASCII兼容(对于只包含ASCII的字符串),并且与C字符串兼容(因此以零终止,字符串中不会出现其他0字节)。"\xE2\x98\xA0"是UTF-8表示。
字符 "☠" 也可能是用UTF-8编码的。这取决于你的编辑器,但通常编辑器不使用wchar_t。
所以:使用UTF-8时应该只使用"%s"而不是"%ls"。所以你的3个尝试都是错误的。
一般来说,使用UTF-8和"char*"以及正常的字符串函数(只需不随机地打断字符串即可,但这也意味着如果后面跟有一些组合码点,不要在随机的UTF-8码点之后打断字符串)。
你可以使用wchar_t,但通常是在使用wchar_t的协议中使用,但特别是在这种情况下,你应该格外小心,因为wchar_t的大小可能与所需字符大小不兼容(例如,你的系统和wchar_t可能只有2个字节,因此你可以使用UCS2,但不能使用UTF-32,或者相反,如果系统将wchar_t定义为4个字节,则相反)。
所以保持简单,尽量只使用UTF-8,并将其用作正常的C字符串。

1
现在我更理解这三者之间的区别了。 我成功地用%ls打印了一个多字节字符,但我现在不明白为什么%s也可以打印多字节字符。 例如, wchar_t x[] = L"a\xEF\xB7\xB0z"; printf("%ls\n", x); 与 printf("%s\n", "a\xEF\xB7\xB0z"); 打印出相同的结果。 - user9185695
通常编辑器不使用wchar_t,这与编辑器无关。如果在基于Unix的新编辑器/编译器上,它很可能理解wchar_t buf[] = L"☠",但不支持打印它。 - Barmak Shemirani
在这些代码中,您正在输出3个字节,而您的终端或显示环境将其解释为UTF-8。 - M.M
很抱歉,我仍然不明白如何使用printf的%ls。wchar_t *wstr = L"é"; printf("%ls\n", wstr);无法工作。stderr上的错误消息是“printf:无效或不完整的多字节或宽字符”。我尝试将Clion的文件编码从UTB8更改为UTB16,但情况更糟。 - user9185695
%s中不要使用l标志。UTF-8是字符的字符串,而不是wchar_t的字符串。 - Giacomo Catenazzi
显示剩余2条评论

0

适用于Windows的代码

(我使用它)

环境:W7/64带有ConEmu控制台,W10终端或ConEmu,CP始终设置为65001。

编译器:gcc版本11.2.0(MinGW-W64 x86_64-posix-seh,由Brecht Sanders构建)

即使使用CP65001,在W7默认的Windows控制台中也无法正常工作。

#include <stdio.h>
#include <stdlib.h>                     /* malloc */
#include <string.h>                     /* strlen... */
#include <locale.h>
#include <wchar.h>
//
main( int argc , char *argv[])
{
  // .... code here
  printf("%s", "\u25BA");  /* right triangle */
  // ....
}

结果:

C:\Users\gm\C>gets John MARTHA william
►martha
C:\Users\gm\C>

正如其他人所述,地区设置似乎无法帮助我在我尝试的任何控制台上使其正常工作。

使用其他语法(L"\u25BA"printf("%ls",...)等),在W7和W10下使用此gcc编译器在我尝试的任何控制台上都没有带来预期的结果。


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