使用printf打印UTF-8字符串 - 宽字符与多字节字符串字面量的区别

27
在这样的语句中,如果两者都使用相同的编码方式(UTF-8)输入到源代码中,并且区域设置正确,它们之间是否有任何实际区别?
printf("ο Δικαιοπολις εν αγρω εστιν\n");
printf("%ls", L"ο Δικαιοπολις εν αγρω εστιν\n");
因此,在输出时是否有任何理由更喜欢其中一种?我想第二种性能会差得多,但它是否具有任何优势(或劣势)比使用多字节文字面量?
编辑:这些字符串打印没有问题。但我没有使用宽字符串函数,因为我也想能够使用printf等函数。因此,问题是在给定上述情况的情况下这两种打印方式是否有所不同,如果有,第二种方式是否具有任何优势?
编辑2:根据下面的评论,我现在知道这个程序可以工作 - 这是我原来认为不可能的:
int main()
{
    setlocale(LC_ALL, "");
    wprintf(L"ο Δικαιοπολις εν αγρω εστιν\n");  // wide output
    freopen(NULL, "w", stdout);                 // lets me switch
    printf("ο Δικαιοπολις εν αγρω εστιν\n");    // byte output
}

编辑3:通过查看这两种类型的操作,我进行了进一步的研究。我们来看一个更简单的字符串:

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进行输出。


1
你说这两个例子都是用UTF-8输入的。在第二个示例行中,如果该文本实际上是UTF-8而不是宽编码,则您可能不应该有L前缀,因此您将只使用%s而不是%ls。或者我仍然误解了问题。 - Adrian McCarthy
@AdrianMcCarthy - 源代码中的两个字符串都是UTF-8编码,没错。但是一个字符串字面量总是多字节的 -- "字符字符串字面量是由双引号括起来的零个或多个多字节字符序列,例如“xyz”。宽字符串字面量也是一样,只不过在前面加上字母L。",这是标准规定的。 - teppic
1
据我所知,任何不属于基本源字符集(它是US-ASCII-7的子集)的字符都会引发实现定义的行为,也就是说,这里讨论的一切都实际上取决于使用的编译器。如果你真的想要保险(并且具有可移植性),你将不得不使用 \u... 和 \U...。 - DevSolar
可能是在实现方面。我的目标是始终切换到宽字符表示,但仍然使用常规的stdio函数进行输出,以避免破坏所有期望它们正常工作的内容的兼容性。我只是想知道是否应该仅使用多字节文字(如上所示),还是有理由使用宽文字面值。很难解释,我做得不太好! - teppic
1个回答

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

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

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

输出是 9 8,因为每个特殊字符都由2个char表示。

使用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控制台,在调用_setmode设置stdout的转换模式时,应明确地将其设置为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;
}

10
UTF-16并不是“宽字符”,这个错误的观念还在流传真是遗憾。Unicode字符超过了2^16,UTF-16对它们进行编码时采用了可变长度,要么一组16位编码单元,要么两组。如果你需要“宽字符”,需要使用UTF-32。让我们不要再陷入认为“n”位应该足够每个人使用的误区中,再次强调这点 - DevSolar
5
谢谢。我专业地从事与Unicode密切相关的工作,看到周围有这么多半吊子的知识感到非常遗憾。UTF-16是一个完美的例子:实际上是一种多字节编码,并带有嵌入式零字节。令人惊讶的是,只需要一点古希腊语、一些扩展的CJK字符或一两个象形字符,就能让很多“Unicode感知”软件出现问题。更不用说组合字符和其他类似的小技巧了。;-) - DevSolar
1
@DevSolar - 我很惊讶你能认出那是古希腊文(除非这是巧合) :) - teppic
1
@DevSolar - fwide 只能用于初始设置流,一旦定向就无法更改,不幸的是。 - teppic
@teppic:所以我错过了C99标准的脚注287,而你错过了它的脚注232。;-) 我引用:“freopen函数的主要用途是更改与标准文本流(stderr、stdin或stdout)相关联的文件,因为这些标识符不需要是可修改的lvalue,可以将fopen函数返回的值分配给它们。”使用类似freopen("test", "r", stdin)的东西,您可以从文件中获取stdin以进行读取,这对于测试stdin读取函数非常有用。 - DevSolar
显示剩余9条评论

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