UTF-8 字符数

UTF-8 Character Count

我正在编写一些程序来计算文件中 UTF-8 字符的数量。我已经编写了基本代码,但现在,我被困在应该计算字符的部分。到目前为止,这些是我所拥有的:

文本文件中的内容:

黄埔炒蛋
你好
こんにちは
여보세요

到目前为止我编写的代码:

#include <stdio.h>

typedef unsigned char BYTE;

int main(int argc, char const *argv[])
{
    FILE *file = fopen("file.txt", "r");
    if (!file)
    {
        printf("Could not open file.\n");
        return 1;
    }
    int count = 0;

    while(1)
    {
        BYTE b;
        fread(&b, 1, 1, file);
        if (feof(file))
        {
            break;
        }
        count++;
    }
    printf("Number of characters: %i\n", count);

    fclose(file);

    return 0;
}

我的问题是,我将如何对计算 UTF-8 字符的部分进行编码?我试图在 GitHub 和 YouTube 中寻找灵感,但我还没有找到任何适合我的代码的东西。

编辑:最初,此代码打印文本文件有 48 个字符。但是考虑到UTF-8,应该只有18个字符。

在 C 中,与在 C++ 中一样,没有现成的计算 UTF-8 字符的解决方案。您可以使用 mbstowcs 将 UTF-8 转换为 UTF-16 并使用 wcslen 函数,但这不是提高性能的最佳方法(尤其是如果您只需要计算字符数即可。

我认为这里是您问题的一个很好的答案:counting unicode characters in c++

来自 link 的答案示例:

for (p; *p != 0; ++p)
    count += ((*p & 0xc0) != 0x80);

您可以查看规格:https://www.rfc-editor.org/rfc/rfc3629

第 3 章有这个 table:

   Char. number range  |        UTF-8 octet sequence
      (hexadecimal)    |              (binary)
   --------------------+---------------------------------------------
   0000 0000-0000 007F | 0xxxxxxx
   0000 0080-0000 07FF | 110xxxxx 10xxxxxx
   0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
   0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

您可以检查字节并构建 unicode 字符。

一个不同点是,你会把一个基本字符和它的重音符号(结合标记 cf. https://en.wikipedia.org/wiki/Combining_character)算作一个还是几个字符。

参见:https://en.wikipedia.org/wiki/UTF-8#Encoding

每个 UTF-8 序列包含一个起始字节和零个或多个额外字节。 额外的字节总是以 10 位开始,第一个字节从不以该序列开始。 您可以使用该信息仅计算每个 UTF-8 序列中的第一个字节。

    if((b&0xC0) != 0x80) {
        count++;
    }

请记住,如果文件包含无效的 UTF-8 序列,这将中断。 此外,“UTF-8 字符”可能意味着不同的东西。例如“”将被此方法计为两个字符。

您可以有多种选择:

  • 您可能取决于您的系统实现宽编码和多字节编码
    • 您可以将文件作为宽流读取并只计算字节数,这取决于系统自行将 UTF-8 多字节字符串转换为宽字符串(参见下面的 main1
    • 您可以按字节读取文件并将多字节字符串转换为宽字符串并计算字节数(请参阅下面的 main2
  • 您可以使用对 UTF-8 字符串进行操作并计算 unicode 字符的外部库(请参阅下面使用 libunistringmain3
  • 或者推出您自己的 utf8_strlen-ish 解决方案,它将适用于特定的 UTF-8 字符串 属性 并自己检查字节,如其他答案所示。

这是一个示例程序,必须在 linux 下使用 -lunistring 进行编译,并使用 assert:

进行基本错误检查
#include <stdio.h>
#include <wchar.h>
#include <locale.h>
#include <assert.h>
#include <stdlib.h>

void main1()
{
    // read the file as wide characters
    const char *l = setlocale(LC_ALL, "en_US.UTF-8");
    assert(l);
    FILE *file = fopen("file.txt", "r");
    assert(file);
    int count = 0;
    while(fgetwc(file) != WEOF) {
        count++;
    }
    fclose(file);
    printf("Number of characters: %i\n", count);
}

// just a helper function cause i'm lazy
char *file_to_buf(const char *filename, size_t *strlen) {
    FILE *file = fopen(filename, "r");
    assert(file);
    size_t n = 0;
    char *ret = malloc(1);
    assert(ret);
    for (int c; (c = fgetc(file)) != EOF;) {
        ret = realloc(ret, n + 2);
        assert(ret);
        ret[n++] = c;
    }
    ret[n] = '[=10=]';
    *strlen = n;
    fclose(file);
    return ret;
}

void main2() {
    const char *l = setlocale(LC_ALL, "en_US.UTF-8");
    assert(l);
    size_t strlen = 0;
    char *str = file_to_buf("file.txt", &strlen);
    assert(str);
    // convert multibye string to wide string
    // assuming multibytes are in UTF-8
    // this may also be done in a streaming fashion when reading byte by byte from a file
    // and calling with `mbtowc` and checking errno for EILSEQ and managing some buffer
    mbstate_t ps = {0};
    const char *tmp = str;
    size_t count = mbsrtowcs(NULL, &tmp, 0, &ps);
    assert(count != (size_t)-1);
    printf("Number of characters: %zu\n", count);
    free(str);
}

#include <unistr.h> // u8_mbsnlen from libunistring

void main3() {
    size_t strlen = 0;
    char *str = file_to_buf("file.txt", &strlen);
    assert(str);
    // for simplicity I am assuming uint8_t is equal to unisgned char
    size_t count = u8_mbsnlen((const uint8_t *)str, strlen);
    printf("Number of characters: %zu\n", count);
    free(str);
}

int main() {
    main1();
    main2();
    main3();
}