簡體   English   中英

如何迭代unicode字符並使用printf在C屏幕上打印它們?

[英]How to iterate through unicode characters and print them on the screen with printf in C?

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

我知道有關於SO的相關問題,但是他們沒有用printf解決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代碼點是一個小問題,在這里\□ 我知道它本身是不可能的,因為像\這樣的字符是不可打印的,編譯器說不。 但除此之外,我怎么能從十六進制0000增加到ffff並用printf打印字符。

如果定義了__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;
}

在C99中,您可以使用寬字符到多字節字符轉換函數wctomb()wcrtomb() ,使用當前字符集將每個代碼點轉換為本地表示。 (代碼點在當前字符集中,而不是Unicode。)請記住使用setlocale()來確保轉換函數知道用戶區域設置(最重要的是,使用當前字符集)。 轉換函數使用LC_CTYPE類別,但您仍應使用setlocale(LC_ALL, ""); 至於任何其他語言環境感知程序。

(並非所有系統都安裝了C.UTF-8語言環境,因此我不建議嘗試使用setlocale(LC_ALL, "C.UTF-8");C.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 <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 */

它並不快,但它應該易於理解,並且在所有具有至少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 <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,那么你必須符合Microsoft的愚蠢,並添加一個特殊的“字節順序標記” - printf("\\xEF\\xBB\\xBF"); - 到輸出的開頭,以便像Notepad這樣的實用程序將文件識別為UTF-8。 這是一個僅限Windows的疣,並將其視為這樣。

有問題嗎?

將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不可用,您將需要使用特定於平台的設施。

我會選擇這樣的東西(使用原始的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,並在每個循環中遞增它

當然它可以優化為:

  • 許多字符將無法顯示
  • cast char* - > uint16_t不是很優雅(觸發警告)
  • 由於UTF-8編碼有2個字節,因此它實際上將瀏覽11位代碼點。 要獲得16位,您可能希望實際使用uint32_t並定義一個5字節的char*緩沖區

[編輯]如評論中所述,此循環實際上會生成許多無效的UTF-8序列。 實際上,從U+007FU+0080的代碼點為+1,但在UTF-8中,您從0x7F跳到0xC280 :您需要在循環中排除某些范圍。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM