在C语言中处理多字节(非ASCII)字符

5
我正在尝试制作自己的wc(Unix过滤器),但在处理非ASCII字符时遇到了问题。我对文本文件进行了HEX转储,发现这些字符占用多个字节,因此无法适应char类型。是否有办法在C中从文件中读取这些字符并像单个字符一样处理它们(以便计算文件中的字符数量)?
我已经进行了一些谷歌搜索,并找到了一些wchar_t类型,但没有任何简单的示例说明如何将其与文件一起使用。

2
你需要了解 Unicode 和特别是编码。你目前知道这些术语的含义吗? - David Heffernan
此外,您可能想了解非ASCII单字节编码,例如各种ISO编码,Windows 1252等。正如您所知,ASCII实际上是一种7位宽的编码。 - David Heffernan
@Joey 谢谢,我太习惯使用 ASCII、ISO、ANSI 等编码了,养成了一个坏习惯! - David Heffernan
5个回答

9
我在谷歌上搜索了一下,找到了一些wchar_t类型,但是没有任何简单的例子说明如何与文件一起使用它。很高兴见到你。不幸的是,正确的字符集支持并不简单。顺便说一句,在理想的世界里,每个人都会使用UTF-8(一种Unicode编码,它具有内存效率、健壮性和向后兼容ASCII的特点),标准C库将包括UTF-8编码解码支持,处理文本(包括回答这个问题)就会变得简单而直接。
问题“C语言中最好的Unicode库是什么?”的答案是使用ICU库。您可能需要查看ustdio.h,因为它有一个u_fgetc函数,并且将Unicode支持添加到程序中可能只需要多输入几次u_
此外,如果您可以抽出一些时间来轻松阅读,您可能想要阅读Joel On Software 的 The Absolute Minimum Every Software Developer Absolutely, Positively Must Know about Unicode and Character Sets(无借口!)

个人而言,我从未使用过ICU,但从现在开始我可能会用它了 :-)


我真的很喜欢你的文章:“每个软件开发人员绝对必须知道的Unicode和字符集的绝对最低限度(没有借口!)”。太棒了! - JosEduSol

6

如果你想编写一个符合当前语言设置的标准C版本的wc实用程序,那么你确实可以使用stdio函数的wchar_t版本。在程序启动时,你应该调用setlocale()

setlocale(LC_CTYPE, "");

这将使宽字符函数使用环境定义的适当字符集,例如在类Unix系统中,使用 LANG 环境变量。例如,这意味着如果您的 LANG 变量设置为 UTF8 区域设置,则宽字符函数将处理 UTF8 的输入和输出。(这是 POSIX wc 工具的规定操作方式)。
接下来,您可以使用标准函数的宽字符版本。例如,如果您有以下代码:
long words = 0;
int in_word = 0;
int c;

while ((c = getchar()) != EOF)
{
    if (isspace(c))
    {
        if (in_word)
        {
            in_word = 0;
            words++;
        }
    }
    else
    {
        in_word = 1;
    }
}

如果您想将其转换为宽字符版本,则需要将c更改为wint_t,将getchar()更改为getwchar(),将EOF更改为WEOF,将isspace()更改为iswspace()

long words = 0;
int in_word = 0;
wint_t c;

while ((c = getwchar()) != WEOF)
{
    if (iswspace(c))
    {
        if (in_word)
        {
            in_word = 0;
            words++;
        }
    }
    else
    {
        in_word = 1;
    }
}

5
没有解释的情况下进行负评是不礼貌的。 - caf

2

请去看看ICU。那个库是您处理所有问题所需的。


1

到目前为止,大多数答案都有其优点,但使用哪个取决于您想要的语义:

  • 如果您想在配置的区域设置编码中处理文本,并且不关心在遇到无效序列时完全失败,则使用getwchar()就可以了。
  • 如果您想在配置的区域设置编码中处理文本,但需要检测和恢复无效序列,则需要读取字节并手动使用mbrtowc
  • 如果您始终希望将文本处理为UTF-8,则需要读取字节并将其提供给自己的解码器。如果您事先知道文件将是有效的UTF-8,则可以仅计算范围内的字节00-7FC2-F4并跳过计算所有其他字节,但这可能会在存在无效序列的情况下产生错误结果。更可靠的方法是将字节流解码为Unicode代码点并计算成功解码的数量。

希望这可以帮助您。


0

你确定你真的需要字符数量吗?wc会计算字节数量。

~$ echo 'דניאל' > hebrew.txt
~$ wc hebrew.txt 
 1  1 11 hebrew.txt

(11 = 5个双字节字符 + 1个字节的 '\n')

然而,如果你真的想要计算字符而不是字节,并且可以假设你的文本文件是以 UTF-8 编码的,那么最简单的方法是计算所有不是尾随字节(即在 0x80 到 0xBF 范围内)的字节。

如果你不能假设 UTF-8 但可以假设任何非 UTF-8 文件都是单字节编码,则对数据执行 UTF-8 验证检查。如果通过,则返回 UTF-8 前导字节的数量。如果失败,则返回总字节数。

(请注意,上述方法特定于 wc。如果你实际上正在处理字符而不仅仅是计数它们,则需要知道编码方式。)


3
请注意,wc -m 确实计算的是字符数,而不是字节数 - wc -m hebrew.txt 的输出结果为 6 hebrew.txt - caf

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