c - 如何计算多字节字符的数量?
问题描述
我想为以下程序获得 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
解决方案
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"öçşğü";
但是,这仅处理执行端编码。您仍然需要将源文件编码与编译器预期的实际编码相匹配,并且您仍然会受到预组合字符和分解字符之间差异的影响。
推荐阅读
- java - 如何在javafx中将线性颜色设置为形状?
- python - 用于访问股票市场信息的 Python API
- optimization - 日期时间到字符串,在 F# 中,这可以进一步优化吗?
- postgresql - PostgreSQL:更小的时间戳类型?
- html - 为什么ajax在html模板中返回循环的最新变量?
- javascript - Sequelize 包含嵌套列:“where 子句”中的未知列
- firebase - Dart firebase 和等价物
- batch-file - 在批处理文件中使用完整路径或相对路径
- azure - Azure:如何找到容器注册表的所有者或资源 ID?
- snowflake-cloud-data-platform - 我如何从多个表中复制到?雪花