无法使用wchar.h比较UTF-8字符

时间:2012-09-07 18:08:48

标签: c utf-8 widechar

我正在制作一个小程序,它读取一个包含UTF-8元素的文件,char by char。读完一个字符后,它会将它与其他几个字符进行比较,如果匹配,则用一个下划线'_'替换文件中的字符。

(好吧,它实际上复制了该文件,并用下划线替换了特定的字母。)

我不确定我到底在哪里弄乱,但最有可能到处都是。

这是我的代码:

   FILE *fpi;
   FILE *fpo;
   char ifilename[FILENAME_MAX];
   char ofilename[FILENAME_MAX];
   wint_t sample;


   fpi = fopen(ifilename, "rb");
   fpo = fopen(ofilename, "wb");

   while (!feof(fpi)) {
     fread(&sample, sizeof(wchar_t*), 1, fpi);

     if ((wcscmp(L"ά", &sample) == 0) || (wcscmp(L"ε", &sample) == 0)  ) {
   fwrite(L"_", sizeof(wchar_t*), 1, fpo);

     } else {
       fwrite(&sample, sizeof(wchar_t*), 1, fpo);

     }
   } 

我省略了与文件名生成有关的代码,因为它没有提供给案例。这只是字符串操作。

如果我向该程序提供包含单词γειά σου κόσμε.的文件,我希望它返回: γει_ σου κόσμ_.

搜索互联网并没有多大帮助,因为大多数结果非常笼统或谈论有关UTF-8的完全不同的事情。这就像没有人因某种原因需要操纵单个字符。

任何指向我正确方向的东西都是最受欢迎的。 我不一定在寻找我提交的代码的直接修复版本,我将非常感谢任何有见识的评论,帮助我理解wchar机制的确切运作方式。整个wbyte,wchar,L,no-L,对我来说是一团糟。

提前感谢您的帮助。

2 个答案:

答案 0 :(得分:6)

C有两种不同的字符:多字节字符宽字符

多字节字符可以使用不同数量的字节。例如,在UTF-8(Unicode的可变长度编码)中,a占用1个字节,而α占用2个字节。

宽字符总是占用相同的字节数。此外,wchar_t必须能够保存执行字符集中的任何单个字符。因此,当使用UTF-32时,aα各占4个字节。不幸的是,有些平台的wchar_t为16位宽:这些平台无法使用wchar_t正确支持BMP之外的字符。如果定义了__STDC_ISO_10646__,则wchar_t保存Unicode代码点,因此必须(至少)4个字节长(从技术上讲,它必须至少为21位长)。

因此,在使用UTF-8时,您应该使用多字节字符,这些字符存储在普通char变量中(但要注意strlen(),这会计算 bytes ,而不是多字节字符)。

不幸的是,Unicode还有更多。

ά可以表示为单个Unicode代码点,也可以表示为两个单独的代码点:

  • U+03AC GREEK SMALL LETTER ALPHA WITH TONOS←1 codepoint←1多字节字符←2字节(0xCE 0xAC)= 2 char的。
  • U+03B1 GREEK SMALL LETTER ALPHA U+0301 COMBINING ACUTE ACCENT←2个代码点←2个多字节字符←4个字节(0xCE 0xB1 0xCC 0x81)= 4 char的。
  • U+1F71 GREEK SMALL LETTER ALPHA WITH OXIA←1个代码点←1个多字节字符←3个字节(0xE1 0xBD 0xB1)= 3个char的。

以上所有都是规范的等价物,这意味着它们应该被视为对所有目的都是相同的。因此,您应该使用Unicode规范化算法(有4种:NFC,NFD,NFKC,NFKD)对输入/输出上的字符串进行规范化。

答案 1 :(得分:3)

首先,请花点时间阅读这篇很棒的文章,它解释了UTF8与Unicode以及许多关于字符串和编码的重要事项:http://www.joelonsoftware.com/articles/Unicode.html

您在代码中尝试执行的操作是逐个字符地读取 unicode ,并与之进行比较。如果输入流是UTF8,那将无法工作,并且实际上不可能使用这种结构。

简而言之:完全unicode字符串可以用多种方式编码。其中一个是使用一系列同样大小的“宽”字符,每个字符一个。这就是wchar_t类型(有时是WCHAR)的用途。另一种方法是UTF8,它使用变量个原始字节来编码每个字符,具体取决于字符的值。

UTF8只是一个字节流,可以编码unicode字符串,通常用在文件中。它与一串WCHAR不同,后者是更常见的内存中表示。您无法可靠地戳穿UTF8流,并直接在其中进行字符替换。您需要读取整个内容并对其进行解码,然后循环遍历WCHAR,以便进行比较和替换,然后将结果映射回UTF8以写入输出文件。

在Win32上,使用MultiByteToWideChar进行解码,您可以使用相应的WideCharToMultiByte返回。

当您使用带有常规引号的"string literal"时,您将创建一个以空字符结尾的ASCII字符串(char*),它不支持Unicode。带有L"string literal"前缀的L将创建一个以空字符结尾的WCHAR字符串(wchar_t *),您可以在字符串或字符比较中使用它。 L前缀也适用于单引号字符文字,如下所示:L'ε'


正如评论者所指出的,当你使用fread / fwrite时,你应该使用sizeof(wchar_t)而不是它的指针类型,因为你试图读/写的数量是实际的wchar,而不是a的大小。指向一个。这个建议只是独立于上面的代码反馈 - 你不想一直按字符读取输入字符。

另请注意,当您进行字符串比较(wcscmp)时,您应该使用实际的宽字符串(以nul宽字符结尾) - 不要在内存中使用单个字符作为输入。如果(何时)你想进行字符到字符的比较,你甚至不需要使用字符串函数。由于WCHAR只是一个值,因此您可以直接进行比较:if (sample == L'ά') {}