最近在建詞典,使用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,就可以計算出每個字占多少字節了,當時怎么沒發現呢?

