【问题标题】:How to iterate through unicode characters and print them on the screen with printf in C?如何在 C 中使用 printf 遍历 unicode 字符并在屏幕上打印它们?
【发布时间】:2014-09-21 21:28:21
【问题描述】:

我想遍历所有(至少 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 这样的一些字符是不可打印的,编译器说不。但除此之外,我怎么能从十六进制 0000 递增到 ffff 并用printf 打印字符。

【问题讨论】:

  • \u0000 不能被标准 C 字符串函数打印,因为它是 0 字符。你用的是什么编译器? char str[] = "\u25A1\n"; 看起来不像标准 C。
  • @Jongware Unicode 转义序列已在 C11 中添加。
  • 到底什么是角色? अ是人物吗? ऄ是人物吗?你想要程序员感知的还是用户感知的角色?你想要位置字符的初始/中间/最终/隔离形式吗?您希望组合标记的表示形式是什么?
  • @ninjalj 你可以找到所有unicode字符的列表here
  • 该图表中出现的内容可能与实际文本中上下文中出现的内容几乎没有相似之处。某些字符具有位置形式,具体取决于它们在单词中的位置。其他字符连字,例如:印度文字中的音节字符可能是几个辅音的连字,加上依赖元音和各种标记,......还有影响文本方向性的字符(LRI,RLI,PDI,...... )。

标签: c unicode printf


【解决方案1】:

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

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

#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;
}

【讨论】:

  • 这就是我想要的,它甚至可以在我的 linux 机器上没有宏的情况下工作。
【解决方案2】:

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

(并非所有系统都安装了C.UTF-8 语言环境,所以我不建议尝试使用setlocale(LC_ALL, "C.UTF-8"); 使用UTF-8 将语言环境覆盖为标准C。它适用于某些系统,但不是全部。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 <stdlib.h>
#include <errno.h>

#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 */

它并不快,但应该很容易理解,并且支持所有可能的 Unicode 代码点(U+0000 到 U+10FFFF,包括在内),在所有具有至少 32 位无符号整数的系统上。在具有 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 <stdlib.h>
#include <stdio.h>
#include <errno.h>
#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("<!DOCTYPE html>\n");
    printf("<html>\n");

    /* Header part. */
    printf(" <head>\n");
    printf("  <title> Unicode character list </title>\n");
    printf("  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n");
    printf("  <style type=\"text/css\">\n");
    /* with internal CSS stylesheet: */
    printf("   html {\n");
    printf("    font-family: \"DejaVu Mono\", \"Courier New\", \"Courier\", monospace;\n");
    printf("    font-weight: normal;\n");
    printf("    font-size: 100%%;\n");
    printf("    text-decoration: none;\n");
    printf("    background: #f7f7f7;\n");
    printf("    color: #000000;\n");
    printf("    padding: 0 0 0 0;\n");
    printf("    border: 0 none;\n");
    printf("    margin: 0 0 0 0\n");
    printf("   }\n");

    printf("   body {\n");
    printf("    background: #ffffff;\n");
    printf("    padding: 0.5em 1em 0.5em 1em;\n");
    printf("    border: 1px solid #cccccc;\n");
    printf("    margin: 0 auto auto auto;\n");
    printf("    width: 12em;\n");
    printf("    text-align: center;\n");
    printf("   }\n");

    printf("   p {\n");
    printf("    padding: 0 0 0 0;\n");
    printf("    border: 0 none;\n");
    printf("    margin: 0 0 0 0;\n");
    printf("    outline: 0 none;\n");
    printf("    text-align: center;\n");
    printf("   }\n");

    printf("   p.odd {\n");
    printf("    background: #efefef;\n");
    printf("   }\n");

    printf("   p.even {\n");
    printf("    background: #f7f7f7;\n");
    printf("   }\n");

    printf("   span.code {\n");
    printf("    width: 8em;\n");
    printf("    text-align: right;\n");
    printf("   }\n");

    printf("   span.char {\n");
    printf("    width: 4em;\n");
    printf("    text-align: left;\n");
    printf("   }\n");

    printf("  </style>\n");
    printf(" </head>\n");

    /* Body part. */
    printf(" <body>\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 = "&nbsp;";
            else
            if (i == 34U)
                str = "&#34;";
            else
            if (i == 38U)
                str = "&amp;";
            else
            if (i == 60U)
                str = "&lt;";
            else
            if (i == 62U)
                str = "&gt;";
            else
                str = (const char *)ch;

            if (n & 1) {
            printf("  <p class=\"odd\" title=\"%u in decimal, &amp;#%u; = %s\">", i, i, str);
                printf("<span class=\"code\">U+%04X</span>", i);
                printf(" <span class=\"char\">%s</span>", str);
                printf("</p>\n");
            } else {
                printf("  <p class=\"even\" title=\"%u in decimal, &amp;#%u; = %s\">", i, i, str);
                printf("<span class=\"code\">U+%04X</span>", i);
                printf(" <span class=\"char\">%s</span>", str);
                printf("</p>\n");
            }

            n++;
        }
    }

    printf(" </body>\n");
    printf("</html>\n");

    return EXIT_SUCCESS;
}

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

(如果您在 U+00A0 之后看到每个代码点有多个字符,则您的浏览器已确定由于该文件是本地文件,因此它使用了它明确声明使用的不同字符集。如果发生这种情况,请切换到正常浏览器,或覆盖字符集选择。)

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

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#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,那么你必须遵守微软的愚蠢,并在输出的开头添加一个特殊的“字节顺序标记”——printf("\xEF\xBB\xBF");——以便它的实用程序(如记事本)将文件识别为 UTF -8。这是一个仅限于 Windows 的疣,请照此处理。

问题?

【讨论】:

  • 很好的答案,谢谢。虽然对我的目的来说有点太多了,但确实非常整洁。
【解决方案3】:

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

#include <uchar.h>

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 不可用,您将需要使用特定于平台的工具。

【讨论】:

    【解决方案4】:

    我会选择这样的东西(使用原始 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 个字节上定义一个字符串,最后一个是 NULL 终止字节,允许通过 printf 显示
    • 将前两个字节视为 16 位 unicode,并在每个循环中递增

    当然可以优化为:

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

    [EDIT] 正如评论中所说,这个循环实际上会生成很多无效的 UTF-8 序列。 实际上,从U+007FU+0080 是代码点的+1,但在UTF-8 中你从0x7F 跳转到0xC280:你需要在循环中排除一些范围。

    【讨论】:

    • 无法保证unicodeuint16_t 正确对齐,因此分配code = &amp;unicode[0] 和随后使用code 会导致未定义的行为。在某些实现中,程序可能会因总线错误而崩溃。
    • UTF8 不是用短裤编码的,你想的是 UTF16。 (而且它无论如何也不会使用多字节类 UTF 编码。)
    • @NisseEngström “正确对齐 uint16_t 是什么意思?你不能保证修复循环中的限制吗?@Jongware 确实不,它是以字节编码但要得到代码点 > 0x80 你需要 UTF-8 上的多字节,对吗?你可能需要 UTF-16 中的多字节来处理 unicode > 0xFFFF 否?
    • 是的,对于高于 0x7F 的值,您需要多字节代码。您只是在 short 上加 1,而这本身与多字节编码无关。
    • 我终于明白了^^问题是它只能在某些范围内工作,我实际上会生成很多无效的UTF-8序列...
    猜你喜欢
    • 2017-07-18
    • 1970-01-01
    • 1970-01-01
    • 2013-05-28
    • 2021-12-26
    相关资源
    最近更新 更多