如何迭代unicode字符并用C中的printf在屏幕上打印?

我想迭代所有(至少16位)unicode字符并用C在屏幕上打印它们。

我知道有关于SO的相关问题,但它们没有解决C中printf的问题,但这是我想要实现的,如果它可能毕竟是可能的。 我认为应该可能有一个我不知道的技巧。

既然我想使用printf,我想到了这样的事情:

 for (int i = 0x0000; i <= 0xffff; i++) { //then somehow increment the string char str[] = "\u25A1\n"; printf("%s", str); char str[] = "\u25A2\n"; printf("%s", str); char str[] = "\u25A3\n"; printf("%s", str); ... } 

但是增加unicode代码点有点问题,在这里\u25A1 。 我知道它本身是不可能的,因为像\u0000这样的字符是不可打印的,编译器说不。 但除此之外,我怎么能从hex0000增加到ffff并用printf打印字符。

如果定义了__STDC_ISO_10646__宏,则宽字符对应于Unicode代码点。 因此,假设一个可以表示您感兴趣的字符的区域设置,您可以通过%lc格式转换printf()宽字符:

 #include  #include  #ifndef __STDC_ISO_10646__ #error "Oops, our wide chars are not Unicode codepoints, sorry!" #endif int main() { int i; setlocale(LC_ALL, ""); for (i = 0; i < 0xffff; i++) { printf("%x - %lc\n", i, i); } return 0; } 

在C99中,您可以使用宽字符到多字节字符转换函数wctomb()wcrtomb() ,使用当前字符集将每个代码点转换为本地表示。 (代码点在当前字符集中,而不是Unicode。)请记住使用setlocale()来确保转换函数知道用户区域设置(最重要的是,使用当前字符集)。 转换函数使用LC_CTYPE类别,但您仍应使用setlocale(LC_ALL, ""); 至于任何其他语言环境感知程序。

(并非所有系统都安装了C.UTF-8语言环境,因此我不建议尝试使用setlocale(LC_ALL, "C.UTF-8");来尝试使用UTF-8将语言环境覆盖到标准C setlocale(LC_ALL, "C.UTF-8");一些系统,但不是全部。例如,AFAIK在基于Fedora的Linux发行版中不起作用。)

因为你想输出所有Unicode代码点,我建议采用不同的方法:使用通用字符集转换格式之一,即UTF-8 ,UTF-16(UCS-2在1996年被UTF-16取代)或UTF -32(也称为UCS-4)。 UTF-8是Web上最常用的 – 特别是在您正在查看的这个网页上 – 并且非常易于使用。

有关为什么您更喜欢UTF-8而非“原生宽字符串”的进一步阅读,请参阅utf8everywhere.org 。

如果你想要真正的可移植代码,可以使用这个头文件utf8.h将UTF-8转换为unicode代码点( utf8_to_code() ),将Unicode代码转换为UTF-8( code_to_utf8() ):

 #ifndef UTF8_H #define UTF8_H #include  #include  #define UTF8_MAXLEN 6 static size_t utf8_to_code(const unsigned char *const buffer, unsigned int *const codeptr) { if (!buffer) { errno = EINVAL; return 0; } if (*buffer == 0U) { errno = 0; return 0; } if (*buffer < 128U) { if (codeptr) *codeptr = buffer[0]; return 1; } if (*buffer < 192U) { errno = EILSEQ; return 0; } if (*buffer < 224U) { if (buffer[1] >= 128U && buffer[1] < 192U) return ((buffer[0] - 192U) << 6U) | (buffer[1] - 128U); errno = EILSEQ; return 0; } if (*buffer < 240U) { if (buffer[1] >= 128U && buffer[1] < 192U && buffer[2] >= 128U && buffer[2] < 192U) return ((buffer[0] - 224U) << 12U) | ((buffer[1] - 128U) << 6U) | (buffer[2] - 128U); errno = EILSEQ; return 0; } if (*buffer < 248U) { if (buffer[1] >= 128U && buffer[1] < 192U && buffer[2] >= 128U && buffer[2] < 192U && buffer[3] >= 128U && buffer[3] < 192U) return ((buffer[0] - 240U) << 18U) | ((buffer[1] - 128U) << 12U) | ((buffer[2] - 128U) << 6U) | (buffer[3] - 128U); errno = EILSEQ; return 0; } if (*buffer < 252U) { if (buffer[1] >= 128U && buffer[1] < 192U && buffer[2] >= 128U && buffer[2] < 192U && buffer[3] >= 128U && buffer[3] < 192U && buffer[4] >= 128U && buffer[4] < 192U) return ((buffer[0] - 248U) << 24U) | ((buffer[1] - 128U) << 18U) | ((buffer[2] - 128U) << 12U) | ((buffer[3] - 128U) << 6U) | (buffer[4] - 128U); errno = EILSEQ; return 0; } if (*buffer < 254U) { if (buffer[1] >= 128U && buffer[1] < 192U && buffer[2] >= 128U && buffer[2] < 192U && buffer[3] >= 128U && buffer[3] < 192U && buffer[4] >= 128U && buffer[4] < 192U && buffer[5] >= 128U && buffer[5] < 192U) return ((buffer[0] - 252U) << 30U) | ((buffer[1] - 128U) << 24U) | ((buffer[2] - 128U) << 18U) | ((buffer[3] - 128U) << 12U) | ((buffer[4] - 128U) << 6U) | (buffer[5] - 128U); errno = EILSEQ; return 0; } errno = EILSEQ; return 0; } static size_t code_to_utf8(unsigned char *const buffer, const unsigned int code) { if (code < 128U) { buffer[0] = code; return 1; } if (code < 2048U) { buffer[0] = 0xC0U | (code >> 6U); buffer[1] = 0x80U | (code & 0x3FU); return 2; } if (code < 65536) { buffer[0] = 0xE0U | (code >> 12U); buffer[1] = 0x80U | ((code >> 6U) & 0x3FU); buffer[2] = 0x80U | (code & 0x3FU); return 3; } if (code < 2097152U) { buffer[0] = 0xF0U | (code >> 18U); buffer[1] = 0x80U | ((code >> 12U) & 0x3FU); buffer[2] = 0x80U | ((code >> 6U) & 0x3FU); buffer[3] = 0x80U | (code & 0x3FU); return 4; } if (code < 67108864U) { buffer[0] = 0xF8U | (code >> 24U); buffer[1] = 0x80U | ((code >> 18U) & 0x3FU); buffer[2] = 0x80U | ((code >> 12U) & 0x3FU); buffer[3] = 0x80U | ((code >> 6U) & 0x3FU); buffer[4] = 0x80U | (code & 0x3FU); return 5; } if (code <= 2147483647U) { buffer[0] = 0xFCU | (code >> 30U); buffer[1] = 0x80U | ((code >> 24U) & 0x3FU); buffer[2] = 0x80U | ((code >> 18U) & 0x3FU); buffer[3] = 0x80U | ((code >> 12U) & 0x3FU); buffer[4] = 0x80U | ((code >> 6U) & 0x3FU); buffer[5] = 0x80U | (code & 0x3FU); return 6; } errno = EINVAL; return 0; } #endif /* UTF8_H */ 

它并不快,但它应该易于理解,并且在所有具有至少32位无符号整数的系统上支持所有可能的Unicode代码点(U + 0000到U + 10FFFF,包括)。 在具有16位无符号整数的系统上,编译器可能会警告无法访问的代码,并且它仅支持前65536个代码点(U + 0000到U + FFFF)。

使用上面的utf8.h ,您可以轻松编写一个C程序,输出包含您想要的Unicode字符的HTML页面(不包括控制字符U + 0000-U + 001F和U + 007F-U + 00BF,包括无效代码点) U + D800-U + DFFF,含)。 例如, page.c

 #include  #include  #include  #include "utf8.h" int main(void) { unsigned char ch[UTF8_MAXLEN + 1]; unsigned int i; const char *str; size_t n, len; /* HTML5 DOCTYPE */ printf("\n"); printf("\n"); /* Header part. */ printf(" \n"); printf("  Unicode character list \n"); printf(" \n"); printf(" \n"); printf(" \n"); /* Body part. */ printf(" \n"); n = 0; for (i = 0U; i <= 0xFFFFU; i++) { /* Skip Unicode control characters. */ if ((i >= 0U && i <= 31U) || (i >= 127U && i <= 159U)) continue; /* Skip invalid Unicode code points. */ if (i >= 0xD800U && i <= 0xDFFFU) continue; len = code_to_utf8(ch, i); if (len > 0) { ch[len] = '\0'; /* HTML does not like " & < > */ if (i == 32U) str = " "; else if (i == 34U) str = """; else if (i == 38U) str = "&"; else if (i == 60U) str = "<"; else if (i == 62U) str = ">"; else str = (const char *)ch; if (n & 1) { printf(" 

", i, i, str); printf("U+%04X", i); printf(" %s", str); printf("

\n"); } else { printf("

", i, i, str); printf("U+%04X", i); printf(" %s", str); printf("

\n"); } n++; } } printf(" \n"); printf("\n"); return EXIT_SUCCESS; }

将输出重定向到文件,您可以在任何您喜欢的浏览器中打开该文件。 如果您的浏览器是理智的,并且不处理与从Web服务器获得的文件不同的本地文件,那么您应该看到正确的输出。

(如果你在U + 00A0之后看到每个代码点有多个字符,那么你的浏览器已经决定,因为该文件是本地文件,它使用的是一个明确表明它使用的不同字符集。如果发生这种情况,请切换到一个理智的浏览器,或覆盖字符集选择。)

如果需要,您可以将代码打印为UTF-8文本,例如使用text.c

 #include  #include  #include  #include "utf8.h" int main(void) { unsigned char ch[UTF8_MAXLEN + 1]; unsigned int i; size_t len; for (i = 0U; i <= 0xFFFFU; i++) { /* Skip Unicode control characters. */ if ((i >= 0U && i <= 31U) || (i >= 127U && i <= 159U)) continue; /* Skip invalid Unicode code points. */ if (i >= 0xD800U && i <= 0xDFFFU) continue; len = code_to_utf8(ch, i); if (len > 0) { ch[len] = '\0'; printf("U+%04X %s \n", i, ch); } } return EXIT_SUCCESS; } 

但是你必须确保您的终端或终端仿真器支持UTF-8并使用UTF-8语言环境,或者您将输出重定向到文本文件并在编辑器中打开该文件,该编辑器假定文件使用UTF-8或允许您显式选择UTF-8字符集。

请注意,每个字符前后都有一个空格。 因为某些代码点是组合字符,所以它们可能根本不显示,除非它们可以与另一个字符组合,并且大多数(全部?)与空间结合得很好。

如果您使用Windows,那么您必须符合Microsoft愚蠢,并添加一个特殊的“字节顺序标记” – printf("\xEF\xBB\xBF"); – 到输出的开头,以便像Notepad这样的实用程序将文件识别为UTF-8。 这是一个仅限Windows的疣,并将其视为这样。

有问题吗?

将16位Unicode代码点转换为多字节字符序列的函数是c16rtomb ; 如果你想处理32位代码点,还有c32rtomb

 #include  mbstate_t ps; char buf[MB_CUR_MAX]; size_t bytes = c16rtomb(buf, i, &ps); if (bytes != (size_t) -1) { printf("%.*s\n", bytes, buf); } 

如果c16rtomb不可用,您将需要使用特定于平台的设施。

我会选择这样的东西(使用原始的UTF-8编码):

 char unicode[3] = { 0x00, 0x00, 0x00 }; for(size_t i=0; i<0xffff; i++) { printf("%s\n", unicode); uint16_t * code = &unicode[0]; *code = *code +1; } 
  • 在3个字节上定义一个字符串,最后一个是允许通过printf显示的NULL终止字节
  • 将两个第一个字节视为16位unicode,并在每个循环中递增它

当然它可以优化为:

  • 许多字符将无法显示
  • cast char* - > uint16_t不是很优雅(触发警告)
  • 由于UTF-8编码有2个字节,因此它实际上将浏览11位代码点。 要获得16位,您可能希望实际使用uint32_t并定义一个5字节的char*缓冲区

[编辑]如评论中所述,此循环实际上会生成许多无效的UTF-8序列。 实际上,从U+007FU+0080的代码点为+1,但在UTF-8中,您从0x7F跳到0xC280 :您需要在循环中排除某些范围。