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_前缀)? 如果没有,我需要自己写那些吗? 还是我需要以其他方式处理它?
注意:我宁愿避免宽字符解决方案...
编辑:让我们将其限制为仅基本多语言平面。
-
您可以使用boost :: locale吗? 您可以使用boost::locale::boundary::character(我认为它也可以在ICU中使用,但它使用wchar_t)。 如果没有boost(或其他库)选项,那么AFAIK必须自己滚动...
-
我想您需要使用ICU。
-
您可以计算字符数:stackoverflow.com/a/5117481/412080
-
您是说我不能进入吗? 作为我的用户名?
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等价的参考和其他示例
-
我看到了...好吧,让我们将问题限制在基本多语言平面上,因为那是我出于任何合理的实际目的所需要的。
-
即使在BMP中,此utf-8字符串:const char *dec_e_accute ="e\\xcc\\x81";也将算作2 wchar_t(0x65和0x301),而它是的NFD形式。您应该意识到这一点,即使您可以选择不处理它。
-
我知道...您提到的这个角色重要吗? 还有更多这样的人物吗? 总的来说,对于标准用途,您认为2字节以外的支持是否重要?
-
如果您仅打算支持欧洲字符集,那么BMP就足够了。 分解可用于大多数非英语语言。 法语,西班牙语也使用重音符号,德语和eszett()可以分解为ss,而不是北欧语言
-
我还要中文和日文。
-
@Chris:我不习惯CJK字符,所以我不知道BMP是否足以应付中文...
-
对于UTF-16,BMP中不包含许多CJK字符(不确定简体中文,但肯定繁体中文-然后是日语汉字字符)。 某些韩文字符甚至使用两个Unicode代码点进行编码(那么,即使使用4字节的UTF-32,您也需要多个wchar_t)
strlen仅对输入字符串中的字节计数,直到终止NUL。
另一方面,您似乎对字形计数(您所说的"用户语言符号")感兴趣。
由于UTF-8是可变长度编码(在某种程度上也是UTF-16),因此该过程变得很复杂,因此代码点可以使用最多四个字节进行编码。并且还考虑了Unicode组合字符。
据我所知,标准C ++库中没有类似的东西。但是,使用ICU等第三方库可能会更好。
-
如果std::mblen用于平台本机多字节编码,则无需使用ICU的所有通用性,就可以使用std::mblen轻松滚动自己的代码。
-
@TobySpeight感谢您的建议,我没有意识到此功能。我在Windows控制台应用程序中使用VS2015尝试了该示例代码,但它不起作用(打印"... only 10 characters"而不是4)。
-
@TobySpeight:但是,如果您想同时允许"拉丁小写字母A" +"组合DIAERESIS"和"拉丁小写字母A与DIAERESIS"作为单个"字符",则不能避免使用ICU。这也是某些汉字的问题吗?
-
@MartinBonner,非常正确-我可能会误解这个问题。
-
@MaximEgorushkin:我在回答中指出了Unicode组合字符的问题,Martin Bonner在此处的评论中再次指出了这一点。您的链接答案是否考虑了这一点?
-
不,不是。
-
@MaximEgorushkin感谢您的澄清。对于C ++标准库设计者来说,这可能是一个有趣的考虑点。
-
@ Mr.C64:在Python中,您有一个用于正常转换(编解码器)的模块,而有一个用于特定Unicode处理(unicodedata)的模块。现在,转换可以由标准C ++库正确地处理(即使不是很友好的程序员),但是完全不存在Unicode处理,并且需要使用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
你好世界這是演示樣本
你好世界這
*/ |
-
但是wstring使用wchar_t,在Windows中是2个字节,对吗?多数民众赞成在多达65,536个符号。 UNICODE总共包含136,000个符号(版本10.0)。因此wstring将无法存储吗?还是我错过了什么?
-
@克里斯:不是很多。在Windows上,wchar_t是UTF-16,它仍然是可变长度编码。此外,用户很可能会将两个Unicode代码点视为一个字符(例如,"拉丁文小写字母A" +"组合大写字母"-尽管该特定组合具有组合字符)。这是一个难题。 Unicode可能很糟糕,但是它仍然比我们以前更好。
-
如果您被Windows上的char16_t的wchar_t困扰-您可以将上述代码中的cvt更改为std::wstring_convert, char32_t> cvt并具有32位版本的中间数据...但是我承认-我在Linux上对其进行了测试以避免问题在Windows控制台上打印utf-8
-
哦,所以2个字节足以覆盖BMP,这是任何合理实际使用所需要的。我明白了,谢谢:)
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) << '\
';
} |
-
不用宽字符就能做到吗?我只想在数据结构中保留单字节字符(char *,const char *,std :: string等)。
-
不幸的是,wcslen仅计算"终止的空宽度字符之前的非空宽度字符数"。也就是说,它计数shots就像strlen计数bytes一样。对于使用一对替代的符号,例如U + 1D11E,将产生错误的结果。
-
@Chris您可以reinterpret缓冲为wchar_t*或从中构建wstring,然后使用它来调用wcslen。
-
建议将utf-8 chars的缓冲区强制转换为wchar_t*以将其传递给wcslen是史诗般的失败。
-
OP使用的是多字节字符,而不是宽字符。我认为您缺少代码转换步骤。