最近在建词典,使用Trie字典树,需要把字符串分解成单个字。由于传入的字符串中可能包含中文或者英文,它们的字节数并不相同。一开始天真地认为中文就是两个字节,于是很happy地直接判断当前位置的字符的ASCII码是否处于0~127之间,如果是就提取一个字符,否则提取两个。在测试分字效果的时候,这种方法出了问题。比如我传一个“abcde一二三四五”进去,abcde可以正常分解成 a b c d e,而后面的“一二三四五”则成了乱码。
于是我开启了谷歌之旅,搜索“如何在C++中将string中的中文分解成单个字”云云,搜索到的方法大多与我之前的方法雷同,把代码copy下来直接运行也是会出现乱码。我突然想到,linux下可能会出现中文乱码的原因之一就是编码问题,于是我打开了vim的配置文件,发现我确实是把中文设置成了utf-8。
发现了这点之后,我专门搜索了utf-8,得知它是一种变长编码,具体规则如下:
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
如表:
| 1字节 | 0xxxxxxx |
| 2字节 | 110xxxxx 10xxxxxx |
| 3字节 | 1110xxxx 10xxxxxx 10xxxxxx |
| 4字节 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
| 5字节 | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
| 6字节 | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
有了这个,思路就清晰了:首先,我要判断之后一个字是几个字节的,然后截取相应的字节数。于是有了如下代码:

1 void Dictionary::splitWord(const string & word, vector<string> & characters)
2 {
3 int num = word.size();
4 int i = 0;
5 while(i < num)
6 {
7 int size;
8 if(word[i] & 0x80)
9 {
10 if(word[i] & 0x20)
11 {
12 if(word[i] & 0x10)
13 {
14 if(word[i] & 0x08)
15 {
16 if(word[i] & 0x04)
17 {
18 size = 6;
19 }else{
20 size = 5;
21 }
22 }else{
23 size = 4;
24 }
25 }else{
26 size = 3;
27 }
28 }else{
29 size = 2;
30 }
31 }else{
32 size = 1;
33 }
34 string subWord;
35 subWord = word.substr(i, size);
36 characters.push_back(subWord);
37 i += size;
38 }
39 }

if之中嵌套if,虽然过程很清晰,但是代码行数也太多了,于是对其进行修改,得到如下代码:

1 void Dictionary::splitWord(const string & word, vector<string> & characters)
2 {
3 int num = word.size();
4 int i = 0;
5 while(i < num)
6 {
7 int size = 1;
8 if(word[i] & 0x80)
9 {
10 char temp = word[i];
11 temp <<= 1;
12 do{
13 temp <<= 1;
14 ++size;
15 }while(temp & 0x80);
16 }
17 string subWord;
18 subWord = word.substr(i, size);
19 characters.push_back(subWord);
20 i += size;
21 }
22 }

少了一半左右。
分解出来的结果是存在vector容器中的,这个可以根据具体需要进行更改。
最后发现,中文在utf-8编码中是三个字节的
其实,只需要手动打印出对应string的size,就可以计算出每个字占多少字节了,当时怎么没发现呢?
转自:https://www.cnblogs.com/chinxi/p/6129774.html
bool is_str_utf8(const char* str)
{
unsigned int nBytes = 0;//UFT8可用1-6个字节编码,ASCII用一个字节
unsigned char chr = *str;
bool bAllAscii = true;
for (unsigned int i = 0; str[i] != '\0'; ++i){
chr = *(str + i);
//判断是否ASCII编码,如果不是,说明有可能是UTF8,ASCII用7位编码,最高位标记为0,0xxxxxxx
if (nBytes == 0 && (chr & 0x80) != 0){
bAllAscii = false;
}
if (nBytes == 0) {
//如果不是ASCII码,应该是多字节符,计算字节数
if (chr >= 0x80) {
if (chr >= 0xFC && chr <= 0xFD){
nBytes = 6;
}
else if (chr >= 0xF8){
nBytes = 5;
}
else if (chr >= 0xF0){
nBytes = 4;
}
else if (chr >= 0xE0){
nBytes = 3;
}
else if (chr >= 0xC0){
nBytes = 2;
}
else{
return false;
}
nBytes--;
}
}
else{
//多字节符的非首字节,应为 10xxxxxx
if ((chr & 0xC0) != 0x80){
return false;
}
//减到为零为止
nBytes--;
}
}
//违返UTF8编码规则
if (nBytes != 0) {
return false;
}
if (bAllAscii){ //如果全部都是ASCII, 也是UTF8
return true;
}
return true;
}
std::string gb2312_to_utf8(std::string const &strGb2312)
{
std::vector<wchar_t> buff(strGb2312.size());
#ifdef _MSC_VER
std::locale loc("zh-CN");
#else
std::locale loc("zh_CN.GB18030");
#endif
wchar_t* pwszNext = nullptr;
const char* pszNext = nullptr;
mbstate_t state = {};
int res = std::use_facet<std::codecvt<wchar_t, char, mbstate_t> >
(loc).in(state,
strGb2312.data(), strGb2312.data() + strGb2312.size(), pszNext,
buff.data(), buff.data() + buff.size(), pwszNext);
if (std::codecvt_base::ok == res)
{
std::wstring_convert<std::codecvt_utf8<wchar_t>> cutf8;
return cutf8.to_bytes(std::wstring(buff.data(), pwszNext));
}
return "";
}
本文深入探讨了UTF-8编码规则,特别是在C++中处理中文字符的挑战。通过分析UTF-8编码特性,提供了一种有效的方法来将UTF-8编码的字符串分解成单个字符,适用于中文和英文混合的场景。文章还对比了两种不同的实现方式,优化了代码效率。
520

被折叠的 条评论
为什么被折叠?



