使用 printf 打印 UTF-8 字符串 - 宽与多字节字符串文字

Posted

技术标签:

【中文标题】使用 printf 打印 UTF-8 字符串 - 宽与多字节字符串文字【英文标题】:Printing UTF-8 strings with printf - wide vs. multibyte string literals 【发布时间】:2013-03-09 19:57:19 【问题描述】:

在这样的语句中,两者都以相同的编码(UTF-8)输入到源代码中,并且语言环境设置正确,它们之间有什么实际区别吗?

printf("ο Δικαιοπολις εν αγρω εστιν\n");
printf("%ls", L"ο Δικαιοπολις εν αγρω εστιν\n");

因此,在输出时是否有任何理由偏爱其中一个?我想第二个的表现要差一些,但它比多字节文字有什么优势(或劣势)吗?

编辑:这些字符串打印没有问题。但我没有使用宽字符串函数,因为我也希望能够使用printf 等。所以问题是这些打印方式有什么不同(鉴于上述情况),如果是这样,第二种有什么优势吗?

EDIT2:按照下面的 cmets,我现在知道这个程序可以工作——我认为这是不可能的:

int main()

    setlocale(LC_ALL, "");
    wprintf(L"ο Δικαιοπολις εν αγρω εστιν\n");  // wide output
    freopen(NULL, "w", stdout);                 // lets me switch
    printf("ο Δικαιοπολις εν αγρω εστιν\n");    // byte output


EDIT3:我通过查看这两种类型的情况做了一些进一步的研究。取一个更简单的字符串:

wchar_t *wides = L"£100 π";
char *mbs = "£100 π";

编译器正在生成不同的代码。宽字符串是:

.string "\243"
.string ""
.string ""
.string "1"
.string ""
.string ""
.string "0"
.string ""
.string ""
.string "0"
.string ""
.string ""
.string " "
.string ""
.string ""
.string "\300\003"
.string ""
.string ""
.string ""
.string ""
.string ""

而第二个是:

.string "\302\243100 \317\200"

看看 Unicode 编码,第二种是纯 UTF-8。宽字符表示是 UTF-32。我意识到这将取决于实现。

所以也许文字的宽字符表示更便携?我的系统不会直接打印 UTF-16/UTF-32 编码,所以会自动转换成 UTF-8 输出。

【问题讨论】:

你说这两个例子都是用 UTF-8 输入的。在第二个示例行中,如果该文本实际上是 UTF-8 而不是宽编码,那么您可能不应该有 L 前缀,因此您只需使用 %s 而不是 %ls。或者我仍然误解了这个问题。 @AdrianMcCarthy - 源代码中的两个字符串都是 UTF-8,是的。但是字符串文字总是多字节的——“字符串文字是用双引号括起来的零个或多个多字节字符的序列,如“xyz”。宽字符串文字是相同的,除了以字母 L 为前缀。 "从标准。 AFAIR,任何不在基本源字符集中的字符(它是 US-ASCII-7 的 子集)都会调用实现定义的行为,即这里讨论的所有内容都是有效的取决于使用的编译器。如果你真的想安全地玩它(和便携),你将不得不求助于 \u... 和 \U... 很可能在实施领域。我想要做的是一直切换到宽字符表示,但坚持使用常规的 stdio 函数进行输出,以免破坏与所有期望它们工作的东西的兼容性。我真的只是想知道我是否应该单独使用多字节文字(如上所述),或者是否有理由使用宽文字。这很难解释,而且我做得不是很好! utf8everywhere.org 几乎说服了不鼓励使用 L"",尤其是在 Windows 平台上。 【参考方案1】:
printf("ο Δικαιοπολις εν αγρω εστιν\n");

打印字符串文字(const char*,特殊字符表示为多字节字符)。尽管您可能会看到正确的输出,但在使用此类非 ASCII 字符时可能会遇到其他问题。例如:

char str[] = "αγρω";
printf("%d %d\n", sizeof(str), strlen(str));

输出 9 8,因为这些特殊字符中的每一个都由 2 个chars 表示。

使用 L 前缀时,您的文字由宽字符 (const wchar_t*) 和 %ls 格式说明符组成,会导致这些宽字符转换为 多字节字符 (UTF-8 )。请注意,在这种情况下,应适当设置语言环境,否则此转换可能会导致输出无效:

#include <stdio.h>
#include <wchar.h>
#include <locale.h>

int main(void)

    setlocale(LC_ALL, "");
    printf("%ls", L"ο Δικαιοπολις εν αγρω εστιν");
    return 0;

但是,当使用宽字符时,有些事情可能会变得更加复杂,而其他事情可能会变得更加简单和直接。例如:

wchar_t str[] = L"αγρω";
printf("%d %d", sizeof(str) / sizeof(wchar_t), wcslen(str));

将输出5 4,正如人们自然期望的那样。

一旦您决定使用宽字符串,wprintf 可用于直接打印宽字符。这里还需要注意的是,在 Windows 控制台的情况下,stdout 的翻译模式应通过调用 _setmode 显式设置为 Unicode 模式之一:

#include <stdio.h>
#include <wchar.h>

#include <io.h>
#include <fcntl.h>
#ifndef _O_U16TEXT
  #define _O_U16TEXT 0x20000
#endif

int main()

    _setmode(_fileno(stdout), _O_U16TEXT);
    wprintf(L"%s\n", L"ο Δικαιοπολις εν αγρω εστιν");
    return 0;

【讨论】:

就是我 :) wprintf 也转换为多字节,但我对标准函数感兴趣。 UTF-16 不是“宽”的,很遗憾这个神话仍然存在。有超过 2^16 个 Unicode 字符,并且 UTF-16 使用一个或两个 16 位代码单元的 可变 宽度对它们进行编码。如果你想要“宽”,你必须求助于 UTF-32。我们不要陷入那种认为n 位应该对每个人都足够的陷阱,再次 谢谢。我正在专业地研究与 Unicode 相关的东西,看到有多少关于这个主题的半生不熟的知识真是太可悲了。 UTF-16 就是一个完美的例子:实际上是一种多字节编码,嵌入了零字节。令人惊讶的是,有多少“Unicode 感知”软件可以用一点古希腊语、一些扩展的 CJK 或一两个象形文字来吐槽。更不用说组合字符和其他类似的细节了。 ;-) @DevSolar - 我很高兴你认识到它是古希腊人(除非是巧合):) @DevSolar - fwide 只能用于初始设置流,一旦定向就无法更改它,很遗憾。

以上是关于使用 printf 打印 UTF-8 字符串 - 宽与多字节字符串文字的主要内容,如果未能解决你的问题,请参考以下文章

atom编辑器中编辑C代码调用printf函数打印中文字符出现乱码

打印UTF-8会产生不同的输出[重复]

如何使用 printf 打印“-”

使用带有 printf 的格式字符串打印可变字节数

STM32中使用printf打印字符串为何字符串第一个字符无法打印?

在 C 中打印 UTF-8 后未显示文本