从文本文件中处理C中的UTF-8字符

时间:2014-11-14 12:34:12

标签: c file input utf-8

我需要从文本文件中读取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中识别那些特殊字符吗?

P.S。使用普通字符(不是wchar_t)会产生同样的问题,但是这次打印输入字符的十进制等效值将为这些特殊字符打印5个不同的负数。问题就在于此。

4 个答案:

答案 0 :(得分:11)

现代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个单词"字符"),您描述的用例也不会造成任何问题,因为您的所有字符都可以使用{{1形式。

答案 1 :(得分:4)

您可以创建自己的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);
}

答案 2 :(得分:2)

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

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将产生win_t的值 - 这是一个整合类型wchar_t(本身大小为16或32位),而utf8字符可能在普通char *中占用1到4个字节(因此为8到32位)。由于您直接获得wchar_t,因此您实际上不必担心utf8编码。

答案 3 :(得分:1)

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

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

  

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

     

Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6
0xxxxxxx
110xxxxx 10xxxxxx
1110xxxx 10xxxxxx 10xxxxxx
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
1111110x 10xxxxxx 10xxxxxx 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++;
    }
}