我需要从文本文件中读取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个不同的负数。问题就在于此。
答案 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_t
到unsigned 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++;
}
}