在C语言中读取和输出Unicode

4
FILE * f = fopen("filename", "r");
int c;

while((c = fgetc(f)) != EOF) {
    printf("%c\n", c);
}

大家好,我已经搜索了整整一个小时,找到了许多关于Unicode的明智论文,但没有回答这个简单问题的答案:

使用gcc和bash在Linux上处理UTF8编码,有哪些等价于以下四行代码最短的代码?

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

谢谢。


你的终端支持UTF8/unicode码吗?如果不支持,你可能不会注意到任何区别。 - Zeta
UTF-8?UTF-16?UTF-32?宽字符?如果是宽字符,使用哪个操作系统和编译器? - Some programmer dude
你好,谢谢。我编辑了以回答你的问题。 - pouzzler
我建议您研究一下iconv库。 - Some programmer dude
你需要更多地了解UTF-8编码。一个UTF-8字符可以是从一个到六个字符的任何内容。因此,如果你读取的第一个字节具有特定的位模式,则需要读取另一个字符,以此类推。 - Some programmer dude
显示剩余2条评论
1个回答

6

如果您的系统支持,类似下面这样的内容应该可以正常工作:

#include <stdio.h>
#include <wchar.h>
#include <locale.h>


int main() {
   setlocale(LC_CTYPE, "en_GB.UTF-8");
   FILE * f = fopen("filename", "r");
   wint_t c;

   while((c = fgetwc(f)) != WEOF) {
      wprintf(L"%lc\n", c);
   }
}

你原来的代码问题在于C语言不知道(或者根本不关心)这些字符是多字节的,因此每个字节之间的换行符\n会破坏你的多字节字符。而在这个版本中,一个字符被视为UTF-8编码,因此%lc现在可以表示多达6个实际字节,并保证正确输出。如果输入中有任何ASCII字符,它将像以前一样使用每个字符一个字节(因为ASCII与UTF-8兼容)。 strace在调试类似问题时总是很有用的。例如,如果文件只包含££(英镑符号的UTF-8序列为\302\243)。那么你的版本将产生:
write(1, "\302\n\243\n\302\n\243\n\n\n", 10) = 10

And mine,

write(1, "\302\243\n\302\243\n", 6)     = 6

请注意,一旦您读取或写入流(包括stdout),它将设置为字节或宽字符方向,并且如果您想更改它,则需要重新打开流。因此,例如,如果您想读取UTF-8文件,但保留stdout作为字节方向,请用以下代码替换wprintf
  printf("%lc\n", c);

这需要在后台添加额外的代码(用于转换格式),但可以提供更好的兼容性,以满足其他期望字节流的代码。


非常感谢,这非常有帮助。 - pouzzler
还要再次感谢。我不会很快就把这些库推送到大众面前,但是这个程序现在可以工作了。 - pouzzler
@pouzzler - 很高兴它有帮助。Unicode处理在C语言中仍然相对比较新,而且周围没有很好的帮助。 - teppic
这将字符串分解为程序员感知的字符(代码点),它可以将用户感知的字符(例如:স্ক → স + 孟加拉语符号 VIRAMA + ক)分解为多个字符。要正确地将文本分成用户感知的字符,您需要像 ICU 的 BreakIterator 这样的工具。 - ninjalj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接