关于C ++:UTF-8,sprintf,strlen等

UTF-8, sprintf, strlen, etc

我试图了解如何在C ++中处理基本的UTF-8操作。

假设有这种情况:用户输入一个名称,该名称最多存储10个字母(用用户语言的符号表示,而不是字节)。

可以用ASCII方式完成。

1
2
3
4
5
// ASCII
char * input; // user's input
char buf[11] // 10 letters + zero
snprintf(buf,11,"%s",input); buf[10]=0;
int len= strlen(buf); // return 10 (correct)

现在,如何在UTF-8中执行此操作? 假设它最多为4个字节的字符集(如中文)。

1
2
3
4
5
// UTF-8
char * input; // user's input
char buf[41] // 10 letters * 4 bytes + zero
snprintf(buf,41,"%s",input); //?? makes no sense, it limits by number of bytes not letters
int len= strlen(buf); // return number of bytes not letters (incorrect)

可以用标准的sprintf / strlen完成吗? 是否可以用UTF-8替换那些函数(PHP中有IIRC这样的函数的mb_前缀)? 如果没有,我需要自己写那些吗? 还是我需要以其他方式处理它?

注意:我宁愿避免宽字符解决方案...

编辑:让我们将其限制为仅基本多语言平面。


I would prefer to avoid wide characters solution...

宽字符是不够的,因为如果单个字形需要4个字节,则该字形可能在基本多语言平面之外,并且不会由单个16位wchar_t字符表示(假设wchar_t为16位宽(仅是普通大小)。

您将必须使用真正的unicode库将输入转换为标准格式C(规范组成)或兼容等效项(NFKC)(*)的unicode字符列表,具体取决于例如要计算一还是两个连字?(U + FB00)的字符。 AFAIK,您最好选择ICU。

(*)Unicode允许对同一字形进行多种表示,特别是普通组合形式(NFC)和普通分解形式(NFD)。例如,法语é字符可以在NFC中表示为U + 00E9或带有ACUTE的拉丁文小写字母E,或者表示为U + 0065 U + 0301或拉丁文小写字母E,后跟COMBINING ACUTE ACCENT(也显示为e?) 。

有关Unicode等价的参考和其他示例


strlen仅对输入字符串中的字节计数,直到终止NUL

另一方面,您似乎对字形计数(您所说的"用户语言符号")感兴趣。

由于UTF-8是可变长度编码(在某种程度上也是UTF-16),因此该过程变得很复杂,因此代码点可以使用最多四个字节进行编码。并且还考虑了Unicode组合字符。

据我所知,标准C ++库中没有类似的东西。但是,使用ICU等第三方库可能会更好。


如果您不想自己计算utf-8个字符,则可以使用临时转换为widechar来削减输入字符串。您不需要存储中间值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <codecvt>
#include <string>
#include <locale>

std::string cutString(const std::string& in, size_t len)
{
    std::wstring_convert<std::codecvt_utf8<wchar_t>> cvt;
    auto wstring = cvt.from_bytes(in);
    if(len < wstring.length())
    {
        wstring = wstring.substr(0,len);
        return cvt.to_bytes(wstring);
    }    
    return in;
}
int main(){
    std::string test ="你好世界這是演示樣本";

    std::string res = cutString(test,5);
    std::cout << test << '\
'
<< res << '\
'
;

    return 0;
}

/****************
Output
$ ./test
你好世界這是演示樣本
你好世界這
*/


std::strlen实际上仅考虑一个字节字符。要计算以Unicode终止的Unicode字符串的长度,可以使用std::wcslen代替。

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <cwchar>
#include <clocale>

int main()
{
    const wchar_t* str = L"爆ぜろリアル!弾けろシナプス!パニッシュメントディス、ワールド!";

    std::setlocale(LC_ALL,"en_US.utf8");
    std::wcout.imbue(std::locale("en_US.utf8"));
    std::wcout <<"The length of \"" << str <<"\" is" << std::wcslen(str) << '\
'
;
}