2

我想为以下程序获得 5 而不是 10。有人知道如何修复代码来计算多字节字符的数量吗?谢谢。

/* vim: set noexpandtab tabstop=2 shiftwidth=2 softtabstop=-1 fileencoding=utf-8: */
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <locale.h>

size_t nchars(const char *s) {   
    size_t charlen, chars;
    mbstate_t mbs;

    chars = 0;
    memset(&mbs, 0, sizeof(mbs));
    while (
            (charlen = mbrlen(s, MB_CUR_MAX, &mbs)) != 0
            && charlen != (size_t)-1
            && charlen != (size_t)-2
            ) {
        s += charlen;
        chars++;
    }   

    return (chars);
}   

int main() {
    setlocale(LC_CTYPE, "en_US.utf8");
    char * text = "öçşğü";

    printf("%zu\n", nchars (text));

    return 0;
}
$ ./main.exe 
10
4

1 回答 1

2

mbstate_t次要问题:您应该通过函数初始化类型对象mbsinit,而不是memcpy. 不保证全字节为零mbsinit表示初始移位状态,甚至不保证任何有效移位状态。

您的代码的主要问题在于它正在分析字符串文字,其表示是在编译时根据源文件中这些字符的实际编码、它们在编译器的源字符集中的表示以及编译器选择的执行字符集。您不能LC_CTYPE随意选择——它必须与数据相匹配,mb 转换函数才能按预期工作。

C 没有为程序定义一种机制来识别LC_TYPE与执行字符集相对应的语言环境,甚至不需要存在这样的语言环境。您的编译器文档应该描述源字符和执行字符之间的映射,但是,可能根据语言环境或众所周知的编码,它甚至可以描述一种让您指定的方式。您的编译器的文档还可能描述了一种方法,您可以指定它应该为源文件采用的编码。

此外,您还有一个 Unicode 潜在问题,即您(人类)认为的“字符”与表示它的 Unicode 字符之间可能存在不匹配。通常,这涉及带有变音符号(例如重音)的字符。其中许多更常用的具有单字符“组合”表示,但也可以表示为基本字符加上一个或多个组合字符的序列。

mbrlen()不太可能区分基本字符和组合字符,因此即使没有任何编码混淆,您观察到的结果也可能来自源文件中以分解形式表示的字符,或者由编译器转换为该形式。

底线是您的程序取决于标准未指定的环境和实现特征,因此它可能在不同的实现中表现不同,这似乎确实是观察结果。例如,您的特定观察可能来自以 UTF-8 编码的源文件,编译器假设它以单字节编码(例如 ISO-8859-1)进行编码,但编译器使用 UTF-8为其执行字符集。

如果您确保编译器根据该文件的实际编码解释源文件,并且它使用 UTF-8 作为其执行字符集,则您的方法可能无需更改即可工作。或者,在 C11 或更高版本中,您可以使用 UTF-8 文字确保该特定字符串的运行时编码为 UTF-8,如下所示:

char * text = u8"öçşğü";

但是,这仅处理执行端编码。您仍然需要将源文件编码与编译器预期的实际编码相匹配,并且您仍然会受到预组合字符和分解字符之间差异的影响。

于 2019-02-08T16:49:23.977 回答