我正在开发一个跨平台C(C89标准)应用程序,它必须处理UTF8文本。我只需要基本的字符串操作函数,如substr
,first
,last
等。
问题1
是否有实现上述功能的UTF8库?我已经看过ICU了,这对我的要求来说太大了。我只需要支持UTF8。
我找到了一个UTF8解码器here。以下函数原型来自该代码。
void utf8_decode_init(char p[], int length);
int utf8_decode_next();
初始化函数采用字符数组,但utf8_decode_next()
返回int
。这是为什么?如何使用printf
等标准函数打印此函数返回的字符?该函数正在处理字符数据以及如何将其分配给整数?
如果上述解码器不适合生产代码,您有更好的推荐吗?
问题2
我也很困惑,阅读的文章说,对于unicode,你需要使用wchar_t
。根据我的理解,这不是必需的,因为普通的C字符串可以保存UTF8值。我通过查看SQLite和git的源代码验证了这一点。 SQLite具有以下typedef。
typedef unsigned char u8
我的理解是否正确?另外为什么需要unsigned char
?
答案 0 :(得分:4)
utf_decode_next()
函数返回下一个Unicode代码点。由于Unicode是一个21位字符集,因此它不能返回小于int
的任何内容,并且可以认为从技术上讲,它应该是long
,因为int
可能是16位数量。实际上,该函数会返回一个UTF-32字符。
您需要查看C89的C94宽字符扩展名以打印宽字符(wprintf()
,<wctype.h>
,<wchar.h>
)。但是,单独的宽字符不能保证是UTF-8甚至是Unicode。您很可能无法以utf8_decode_next()
方式打印字符,但这取决于您的可移植性要求。您必须移植的系统范围越广,所有工作的可能性就越小。如果您可以移植地编写UTF-8,您可以将UTF-8字符串(不是从utf8_decode_next()
获得的UTF-32字符数组)发送到常规打印功能之一。 UTF-8的优点之一是它可以被很大程度上无知的代码操纵。
您需要了解一个4字节wchar_t
可以在一个单元中保存任何Unicode代码点,但UTF-8可能需要一到四个8位字节(1-4个单位)存储)保存单个Unicode代码点。在某些系统上,我认为wchar_t
可以是16位(short
)整数。在这种情况下,您被迫使用UTF-16,它使用两个存储单元和代理编码基本多语言平面(BMP,代码点U + 0000 .. U + FFFF)之外的Unicode代码点。
使用unsigned char
让生活更轻松;普通char
经常签名。负数会让生活变得比我需要的更困难(而且,相信我,如果不增加复杂性就很难)。
答案 1 :(得分:4)
使用UTF-8进行字符或子字符串搜索时,不需要任何特殊的库例程。 strstr
可以完成您需要的一切。这就是UTF-8的全部要点以及它为满足而发明的设计要求。
答案 2 :(得分:2)
GLib具有quite a few相关功能,可以独立于GTK +使用。
答案 3 :(得分:1)
Unicode中有超过100,000个字符。在大多数C实现中,char
有256个可能的值。
因此,UTF-8使用多个char
来编码每个字符,解码器需要一个大于char
的返回类型。
wchar_t
是一个比char
更大的类型(嗯,它不会 更大,但它通常是)。它表示实现定义的宽字符集的字符。在某些实现中(最重要的是,Windows使用代理对用于“基本多语言平面”之外的字符),它仍然不足以表示任何Unicode字符,这可能是您引用的解码器使用{{1}的原因}。
您无法使用int
打印宽字符,因为它处理printf
。 char
处理wprintf
,因此如果宽字符集是unicode,并且系统上wchar_t
是wchar_t
(就像在linux上一样),那么{{1}和朋友将打印解码器输出而无需进一步处理。否则就不会。
在任何情况下,您都无法移植打印任意unicode字符,因为无法保证终端可以显示它们,甚至宽字符集与Unicode有任何关联。
SQLite可能使用了int
,因此:
wprintf
是否已签名。unsigned char
的行为比char
更为自由。答案 4 :(得分:0)
普通C字符串适用于存储utf8数据,但您无法在utf8字符串中轻松搜索子字符串。这是因为使用utf8编码编码为字节序列的字符可以是1到4个字节的任何位置,具体取决于字符。即“字符”不等同于utf8的“字节”,就像它是ASCII一样。
为了进行子字符串搜索等,您需要将其解码为用于表示Unicode字符的某种内部格式,然后对其进行子字符串搜索。由于远远超过Unicode 256个字符,因此字节(或字符)是不够的。这就是您找到的库使用整数的原因。
至于你的第二个问题,可能只是因为谈论负面字符没有意义,所以它们也可以被指定为“无符号”。
答案 5 :(得分:0)
我已经实施了substr
&amp;支持UTF8字符的length
个函数。此代码是SQLite使用的修改版本。
以下宏循环输入文本并跳过所有多字节序列字符。 if
条件检查这是一个多字节序列,并且其中的循环递增input
,直到它找到下一个头字节。
#define SKIP_MULTI_BYTE_SEQUENCE(input) { \
if( (*(input++)) >= 0xc0 ) { \
while( (*input & 0xc0) == 0x80 ){ input++; } \
} \
}
substr
和length
是使用此宏实现的。
typedef unsigned char utf8;
<强> SUBSTR 强>
void *substr(const utf8 *string,
int start,
int len,
utf8 **substring)
{
int bytes, i;
const utf8 *str2;
utf8 *output;
--start;
while( *string && start ) {
SKIP_MULTI_BYTE_SEQUENCE(string);
--start;
}
for(str2 = string; *str2 && len; len--) {
SKIP_MULTI_BYTE_SEQUENCE(str2);
}
bytes = (int) (str2 - string);
output = *substring;
for(i = 0; i < bytes; i++) {
*output++ = *string++;
}
*output = '\0';
}
<强>长度强>
int length(const utf8 *string)
{
int len;
len = 0;
while( *string ) {
++len;
SKIP_MULTI_BYTE_SEQUENCE(string);
}
return len;
}