如何在C语言中将Unicode代码点打印为字符?

5
我有一个 uint32_t 元素的数组,每个元素都存储着一个非拉丁语言的 Unicode 字符编码。我该如何将它们以 UTF-8 编码的字符形式打印在控制台上或者存储到文件中?我知道它们可能无法在控制台上正确显示,但是如果我在兼容的编辑器中打开它们,它们应该可以正常显示。
我尝试过使用 wprintf(L"%lc", UINT32_T_VARIABLE)fwprintf(FILE_STREAM, L"%lc", UINT32_T_VARIABLE),但都没有成功。

你确定 stdout 不是面向字节的吗? - EOF
@EOF:实际上,“stdout”必须以字节为单位,并且必须选择适当的区域设置才能进行此项工作。 - chqrlie
@chqrlie:如果像 OP 明显所做的那样使用 wprintf(),就不能这么做。 - EOF
@EOF:OP尝试使用wprintf()来实现他的目的,但他并不一定想使用宽字符串版本,他只是想将Unicode代码点转换为UTF-8。 - chqrlie
2个回答

2

您必须首先使用以下命令选择正确的区域设置:

#include <locale.h>

setlocale(LC_ALL, "C.UTF-8");

或者
setlocale(LC_ALL, "en_US.UTF-8");

然后使用printffprintf%lc格式:

printf("%lc", UINT32_T_VARIABLE);

这将仅适用于Unicode代码点小到足以适应wchar_t的情况。为了获得更完整和便携的解决方案,您可能需要自己实现Unicode到UTF-8的转换,这并不是很困难。


设置本地化不起作用。fprintf()wprintf() 只打印/存储空格,而 fwprintf() 在文件中存储错误字符。 - hazrmard
你的平台和编译器是什么? - chqrlie
Win10,gcc 5.3,Cygwin - hazrmard
本C库支持区域设置和多字节编码。在Windows上,您可以使用MSVC运行时库或基于Glibc的Cygwin特定C库,我认为后者具有更好的支持。为避免此问题,请使用自己的UTF-8转换器。 - chqrlie

2
最好在可用时使用现有代码。
自己编写 Unicode 代码点转换为 UTF8 简单,但容易出错。答案花了 2 次编辑才修复。@Jonathan Leffler @chqrlie,因此建议对任何自编码的解决方案进行严格测试。以下是轻度测试的代码,用于将代码点转换为数组。
请注意,结果不是一个字符串
// Populate utf8 with 0-4 bytes
// Return length used in utf8[]
// 0 implies bad codepoint
unsigned Unicode_CodepointToUTF8(uint8_t *utf8, uint32_t codepoint) {
  if (codepoint <= 0x7F) {
    utf8[0] = codepoint;
    return 1;
  }
  if (codepoint <= 0x7FF) {
    utf8[0] = 0xC0 | (codepoint >> 6);
    utf8[1] = 0x80 | (codepoint & 0x3F);
    return 2;
  }
  if (codepoint <= 0xFFFF) {
    // detect surrogates
    if (codepoint >= 0xD800 && codepoint <= 0xDFFF) return 0;
    utf8[0] = 0xE0 | (codepoint >> 12);
    utf8[1] = 0x80 | ((codepoint >> 6) & 0x3F);
    utf8[2] = 0x80 | (codepoint & 0x3F);
    return 3;
  }
  if (codepoint <= 0x10FFFF) {
    utf8[0] = 0xF0 | (codepoint >> 18);
    utf8[1] = 0x80 | ((codepoint >> 12) & 0x3F);
    utf8[2] = 0x80 | ((codepoint >> 6) & 0x3F);
    utf8[3] = 0x80 | (codepoint & 0x3F);
    return 4;
  }
  return 0;
}

// Sample usage
uint32_t cp = foo();
uint8_t utf8[4];
unsigned len = Unicode_CodepointToUTF8(utf8, cp);
if (len == 0) Handle_BadCodePoint();
size_t y = fwrite(utf8, 1, len, stream_opened_in_binary_mode);

1
高代理项和低代理项的正确范围是U+D800 .. U+DFFF(分为高代理项U+D800 .. U+DBFF和低代理项U+DC00 .. U+DFFF),而不是答案中某个版本中的U+D000 .. U+DFFF。[U+AC00 .. U+D7AF](http://www.unicode.org/charts/PDF/UAC00.pdf)的图表涵盖了韩文音节。正如您所说,很容易搞砸。 - Jonathan Leffler
1
在3字节和4字节情况下,中间序列字节的代码不正确:在移位“codepoint”之后,您必须屏蔽“0x40”位...确实简单易错 - chqrlie
@chqrlie 是的,需要清除位(在2字节情况下也是如此)- 代码已经修改。 - chux - Reinstate Monica

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