4

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

(嗯,它实际上复制了那个文件,用下划线替换了特定的字母。)

我不确定我到底在哪里搞砸了,但它很可能无处不在。

这是我的代码:

   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,对我来说都是一团糟。

预先感谢您的帮助。

4

2 回答 2

6

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

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

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

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

不幸的是,Unicode 的意义远不止于此。

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

  • U+03AC GREEK SMALL LETTER ALPHA WITH TONOS← 1 个代码点 ← 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)对输入/输出字符串进行标准化。

于 2012-09-07T19:13:58.093 回答
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"带有常规引号的 a 时,您正在创建一个以 nul 结尾的 ASCII 字符串 ( char*),它不支持 Unicode。L"string literal"带有前缀的将L创建一个以 nul 结尾的 WCHAR (wchar_t *) 字符串,您可以在字符串或字符比较中使用它。L 前缀也适用于单引号字符文字,如下所示:L'ε'


正如评论者所指出的,当您使用 fread/fwrite 时,您应该使用sizeof(wchar_t)而不是其指针类型,因为您尝试读取/写入的量是实际的 wchar,而不是指向 wchar 的指针的大小。该建议只是独立于上述内容的代码反馈——无论如何,您不希望逐个字符地读取输入。

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

于 2012-09-07T18:10:55.160 回答