【问题标题】:Process UTF-8 characters in C from a text file从文本文件处理 C 中的 UTF-8 字符
【发布时间】:2015-01-11 20:29:33
【问题描述】:

我需要从文本文件中读取 UTF-8 字符并进行处理。 例如计算某个字符的出现频率。普通角色就好了。像üğ 这样的字符会出现问题。 以下是我检查某个字符是否出现比较传入字符的 ascii 代码的代码:

FILE * fin;
FILE * fout;
wchar_t c;
fin=fopen ("input.txt","r");
fout=fopen("out.txt","w");
int frequency = 0;
while((c=fgetwc(fin))!=WEOF)
{
   if(c == SOME_NUMBER){ frequency++; }
}

SOME_NUMBER 是我想不通的那些字符。事实上,这些字符在尝试将其打印为小数时会打印出 5 个不同的数字。 而例如对于字符'a',我会这样做:if(c == 97){ frequency++; },因为'a' 的ascii 代码是97。 无论如何我可以识别C中的那些特殊字符吗?

附:使用普通字符(不是 wchar_t )会产生同样的问题,但是这次打印传入字符的十进制等效值将为这些特殊字符打印 5 个不同的负数。问题所在。

【问题讨论】:

  • 大多数字符占用超过一个字节。所以你应该读取并比较多个字节。关于wchar_t,我认为它是由实现定义的,像fgetwc 这样的字符编码函数假设,并且在许多系统上它不是UTF-8。
  • 根据所使用的字体,多个代码点可能具有相同或几乎相同的字形。它们仍然是 unicode 中的不同代码点。输入频谱是什么,您想将其映射到什么频谱(编码)?
  • @didierc 我将如何创建这样的表?你能在回复中给我一些提示吗?我应该为表格中的那些特殊字符分配什么?

标签: c file input utf-8


【解决方案1】:

现代 C 平台应该提供您完成此类任务所需的一切。

首先您必须确定您的程序在可以处理 utf8 的语言环境下运行。您的环境应该已经设置好了,您在代码中唯一需要做的就是

setlocale(LC_ALL, "");

"C" 语言环境切换到您的本地环境。

然后您可以像往常一样使用fgets 读取字符串,例如要对重音字符和内容进行比较,您必须将这样的字符串转换为您已经提到的宽字符串 (mbsrtowcs)。这种宽字符的编码是实现定义的,但你不需要知道编码来做检查。

通常,只要您编译和执行的平台没有完全搞砸,L'ä' 之类的东西就可以完美运行。如果您需要甚至无法在键盘上输入的代码,您可以使用来自 C11 的 L'\uXXXX' 符号,正如 didierc 在他的回答中提到的那样。 ('L'\uXXXX' 用于“基本”字符,如果你有一些非常奇怪的东西,你会使用L'\UXXXXXXXX',一个带有 8 个十六进制数字的大写 U)

如上所述,宽字符的编码是实现定义的,但很有可能它是 utf-16 或 utf-32,您可以使用 sizeof(wchar_t) 和预定义的宏 __STDC_ISO_10646__ 检查。即使您的平台仅支持 utf-16(可能有 2 个单词的“字符”),您描述的用例也不应该造成任何问题,因为您的所有字符都可以使用 L'\uXXXX' 形式编码。

【讨论】:

  • 对此不确定,但我相信如果wchar_t 有 16 位,它不能代表 BMP 之外的代码点。 (换句话说,在这种情况下,它是 UCS-2,而不是 UTF-16;没有两个单词的字符。)
  • @mafso,UCS-2 和 UTF-16 都可以,但现在 UCS-2 很少见。无论如何,OP似乎只对BMP感兴趣,所以这对他来说甚至都不重要,对于大多数人来说。 (__STDC_ISO_10646__ 的值也应该表明两者中的哪一个适用。)
  • UTF-16 不是wchar_t 的可能编码,因为宽字符的 C API 从根本上不允许从多字节到宽字符的转换以产生多个宽字符。只有 UTF-32 (UCS-2) 和 UCS-2 是可能的。
  • @R..,我也这么认为,但我不确定某些人,特别是 Windows 是否没有这样做。我依稀记得我发现他们的一些示例代码逐字节读取 UTF8(但使用 mbrtowc 函数左右)以生成两个字的 UTF-16,并且他们宣传他们的 wchar_t 字符串是UTF-16。无论如何,只要在 BMP 中假设字符,一切都很好。
【解决方案2】:

您可以创建自己的utf-8解码读取函数。

格式说明见https://en.wikipedia.org/wiki/UTF-8

这段代码不是很好也不是很健壮。但这是我所说的草图......

#include <stdio.h>
#include <stdlib.h>

#define INVALID (-2)

int fgetutf8c(FILE* f)
{
    int result = 0;
    int input[6] = {};

    input[0] = fgetc(f);
    printf("(i[0] = %d) ", input[0]);
    if (input[0] == EOF)
    {
        // The EOF was hit by the first character.
        result = EOF;
    }
    else if (input[0] < 0x80)
    {
        // the first character is the only 7 bit sequence...
        result = input[0];
    }
    else if ((input[0] & 0xC0) == 0x80)
    {
        // This is not the beginning of the multibyte sequence.
        return INVALID;
    }
    else if ((input[0] & 0xfe) == 0xfe)
    {
        // This is not a valid UTF-8 stream.
        return INVALID;
    }
    else
    {
        int sequence_length;
        for(sequence_length = 1; input[0] & (0x80 >> sequence_length); ++sequence_length);
        result = input[0] & ((1 << sequence_length) - 1);
        printf("squence length = %d ", sequence_length);
        int index;
        for(index = 1; index < sequence_length; ++index)
        {
            input[index] = fgetc(f);
            printf("(i[%d] = %d) ", index, input[index]);
            if (input[index] == EOF)
            {
                return EOF;
            }
            result = (result << 6) | (input[index] & 0x30);
        }
    }
    return result;
}

main(int argc, char **argv)
{
   printf("open(%s) ", argv[1]);
   FILE *f = fopen(argv[1], "r");
   int c = 0;
   while (c != EOF)
   {
       c = fgetutf8c(f);
       printf("* %d\n", c);
   }
   fclose(f);
}

【讨论】:

  • 您能否给我更多的建议?也许如何开始从文件中读取并识别字符
  • 不,当然不应该那样做,你的 C 库是为你准备的,不要重新发明轮子。
  • @JensGustedt 除非有人想学习如何发明轮子。最终所有的发明者都会死去,那我们会在哪里?
【解决方案3】:

如果您需要在代码中包含宽字符文字,您可以使用以下表示法:

whar_t c = L'\u0041'; // 'A'

但我相信你不应该需要那个,如果你想做的是保持字符的频率统计。 wchar_t 类型让您可以像任何其他整数类型一样轻松地比较值:

wchar_t c1 = L'\u0041', c2 = L'\u0030';
int r = c1 == c2; // 0

使用此比较运算符和从数据流中提取wchar_t 的函数,您应该能够仅使用输入字符构建从wchar_tunsigned int 的关联表(C 哈希表实现在网络上比比皆是) .

也许这里很重要的一点是宽字符和 utf8 字符是不同的类型:函数 fgetwc 将产生 wint_t(宽整数类型)的值 - 这是一个包含 wchar_t 的整数类型(本身大小为 16 或 32 位),而 utf8 字符在普通 char * 中可能占用 1 到 4 个字节(所以 8 到 32 位)。既然直接得到wchar_t,其实不用担心utf8编码的问题。

【讨论】:

  • 我刚刚推送了一个编辑;我什至不记得它说了什么(我很累),但我想也许win_tfgetwc() 返回一个wint_t(对于宽整数类型)。我在括号中添加了(wide integer type),因为编辑需要的字符比更改类型要多。请随意将其修改为您自己的措辞(通常我什至不喜欢编辑他人的作品,但是......)。干杯。
【解决方案4】:

这是一个不涉及宽字符的解决方案的建议:

来自维基百科:UTF-8 多字节序列的设计

第一个字节的前导“1”给出后续字节的计数 字节开头的“10”表示连续字节 “0”作为第一个字节表示单字节序列

Byte 1 Byte 2 Byte 3 Byte 4 0xxxxxxx
110xxxxx 10xxxxxx
1110xxxx 10xxxxxx 10xxxxxx
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

因此你必须首先通过测试知道你是否定位在一个多字节序列上:

char byte;
// ...
if((byte & 0xC0) == 0x80)
{
    // Handle multi-byte
}

然后你必须累积字节直到序列完成(计数前导1 知道你需要多少次迭代)最后你会得到你唯一的 unicode 字符并可以关联一个频率。

请注意,string.h API 适用于 UTF-8 多字节序列。例如,您可以在字符串str 中找到ü (0xC3 0xBC) 的出现:

char sequence[] = {0xC3, 0xBC};
size_t count = 0
for(;*str*;str++)
{
    str = strstr(str,sequence);
    if(str)
    {
        count++;
    }
}

【讨论】:

猜你喜欢
  • 2017-04-29
  • 2023-03-29
  • 1970-01-01
  • 1970-01-01
  • 2011-11-07
  • 2011-02-23
  • 2010-11-25
  • 2017-06-24
  • 1970-01-01
相关资源
最近更新 更多