【问题标题】:How to printf a unicode string with '%s' specifier?如何使用“%s”说明符打印 unicode 字符串?
【发布时间】:2018-10-08 14:04:09
【问题描述】:

我正在尝试调用 printf() 以使用 %s 输出 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 数据放进去吗?

【问题讨论】:

  • 代码示例使用小写s。建议编辑您的文本/标题以反映s 而不是S - 如果这确实是您正在做的事情。 C 中的案例很重要。
  • printf("<%s>\n", u8"\xE2\x98\xA0");(这需要 C11 编译器)或printf("<%s>\n", "\xE2\x98\xA0"); printf 为你做什么?
  • 你需要在 printf 的末尾添加一个\n
  • 您使用的是什么操作系统?源文件保存为什么编码?这些细节在处理控制台 I/O 时很重要。

标签: c unicode printf


【解决方案1】:

您将 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 字符串。

【讨论】:

  • 好的,现在我明白了 3 之间的区别。我成功地用 %ls 打印了一个多字节字符,但我现在不明白为什么 %s 也可以打印一个多字节字符.例如 wchar_t x[] = L"a\xEF\xB7\xB0z"; printf("%ls\n", x);打印同 printf("%s\n", "a\xEF\xB7\xB0z");
  • 但是打印出来的字符是3bytes编码的:ﷰ = 11101111 10110111 10110000,那为什么%s可以打印呢?
  • 因为 C 将它们视为普通字符串。您不应该使用字符串长度来计算字符数,但许多属性是相似的。 C 字符串是一个字符数组(相当于当前时期的字节),以\0 结尾。 UTF-8 的构造(因此设计上如此)与 C 字符串兼容。所以\0 只能在 UTF-8 字符串的末尾找到。 UTF-16 终止是 \0\0\0 被发现例如在每个 ASCII 和 latin1 字符中。所以它们不兼容,所以有wchar_t。已经注意到 ASCII(在控制字符中),有字符序列作为单个实体。
  • 通常编辑器不使用 wchar_t 和编辑器没有任何关系。如果它是基于 Unix 的系统上的新编辑器/编译器,它很可能理解 wchar_t buf[] = L"☠",但它不支持打印它。
  • 在这些代码中,您输出 3 个字节,并且您的终端或显示环境将其解释为 UTF-8。
【解决方案2】:

此答案假设您在 MS Windows 中工作


2018 年了,但这些东西仍然不能正常工作,真是令人难过。但情况是这样的:

printf("\xE2\x98\xA0");(与printf("%s", "\xE2\x98\xA0"); 相同)有效,因为您只是将 3 个字符输出到输出流。 C 语言中不存在 Unicode 或特殊字符处理。您的终端环境会在输出中查找 UTF-8 字符串并相应地选择显示字形。

同样,如果您将输出写入文件(使用 fprintf 或流重定向),您会看到该文件包含 0xE2, 0x98, 0xA0,然后您可以选择使用将 UTF-8 转换为显示的文本文件查看器字形。

这部分一切都很好,您可以(并且可能应该)编写程序,只将 UTF-8 编码字符写入FILE 流。


当我们想要输出wchar_t 字符时,问题就开始了。理论上这应该可行:

printf("%ls", L"\u2620");   

应该发生的是调用wcstombs 将unicode 代码点序列转换为多字节序列。但是使用哪种多字节格式? UTF-8 现在已经无处不在,但过去也有其他格式,如 ShiftJIS、Big-5 等。

您必须使用setlocale 指定多字节格式。语言环境的细节是实现定义的。

这是踢球者。 Windows 不支持用于一般 UTF-8 输出的 C 语言环境。如果你尝试setlocale(LC_CTYPE, ".65001");,它就是行不通的。

您可以使用支持的语言环境输出某些 Unicode 子集。例如 the MSDN example 使用 Japanese_Japan.932 可以工作,将 Unicode 输入输出为 Shift-JIS。 (不是 UTF-8)。

更糟糕的是,如果您使用 Windows API 函数 WideStringToMultiByte,它确实接受 CP_UTF8 的“区域设置”。您可以使用此函数将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 运行时实际上并没有对fwide 做任何事情;它不支持面向广泛的流。 wprintf 系列的 Microsoft 实现实际上只是输出窄字符,而不是宽字符,并且它们使用与窄 printf 系列相同的 wcstombs 方法。

因此,该代码不起作用,来自日语 wcstombs 示例的代码 fwprintf(fp, L"\u3603");(带有 .932 CP 集)输出多字节序列而不是原始宽字符。

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

【讨论】:

  • 您可以使用wprintf(L"%s", L"☠"),但只能在Visual Studio 中使用,您首先调用_setmode。在其他编译器中,您必须使用WriteConsoleW。默认控制台字体不支持'☠' 等字符,因此您也必须更改字体。这在 Visual Studio 中是可行的,但在其他编译器中需要更多的摆弄。如果您以二进制模式打开文件,fwprintf 将起作用(BOM 将非常有用)。虽然我更喜欢将文件保存为 UTF8。使用 Linux printf("%s", "☠")printf("%s", u8"☠") 可以,Mac 可能相同。
【解决方案3】:

适用于 Windows 的代码

(我用过)

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

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

在 W7 默认 Windows 控制台中不起作用,即使是 CP65001。

#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 编译器都没有给我带来预期的结果。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-08-23
    • 1970-01-01
    • 2014-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-16
    相关资源
    最近更新 更多