關(guān)于C++遍歷中文字符串的問(wèn)題
今天來(lái)介紹一個(gè)C++中的基礎(chǔ)問(wèn)題:中文字符串的遍歷問(wèn)題。可就是這么的一個(gè)基礎(chǔ)問(wèn)題,也坑了我不少時(shí)間,真是應(yīng)了那句話基礎(chǔ)不牢,地動(dòng)山搖。
小試牛刀
首先我們來(lái)一個(gè)demo,假如要使用std::string遍歷"你好,世界123"這個(gè)字符串,你會(huì)怎么寫?
當(dāng)時(shí)筆者是這么想的:
于是大手一揮,Ctrl C + Ctrl V寫下了一下代碼:
using namespace std;
int main() {
std::string text = "你好,世界123";
for (const auto c:text) {
std::cout << "c:" << c << std::endl;
}
return 0;
}
運(yùn)行起來(lái)一看,我都懵逼了,居然是亂碼...
一看到亂碼,筆者首先想到的可能編碼不是utf-8的,于是我改了一行代碼:
std::string text = u8"你好,世界123";
結(jié)果還是于事無(wú)補(bǔ),還是亂碼的,我開始有點(diǎn)慌了...
在這里說(shuō)明一下當(dāng)在C++中使用字符串字面值時(shí),可以使用前綴u8來(lái)表示使用UTF-8編碼。這意味著該字符串會(huì)以UTF-8編碼的格式存儲(chǔ)在內(nèi)存中。
面對(duì)這些亂碼,我不得不拿出CV工程師的殺手锏,趕緊上stackoverflow求助...
不負(fù)眾望,果然被我找到了答案。。。
馬上復(fù)制粘貼來(lái)驗(yàn)證一波...
using namespace std;
int main() {
std::string text = u8"你好,世界123";
for(size_t i = 0; i < text.length();)
{
int cplen = 1;
if((text[i] & 0xf8) == 0xf0) cplen = 4;
else if((text[i] & 0xf0) == 0xe0) cplen = 3;
else if((text[i] & 0xe0) == 0xc0) cplen = 2;
if((i + cplen) > text.length()) cplen = 1;
cout << text.substr(i, cplen) << endl;
i += cplen;
}
return 0;
}
運(yùn)行起來(lái),果然是想要的結(jié)果。666,憑實(shí)力攻克了一個(gè)技術(shù)難題,帶領(lǐng)公司往前跨了一大步,這回升級(jí)加薪穩(wěn)了吧!!!
尋根問(wèn)底
本著舉一反三的學(xué)習(xí)態(tài)度,我想知道為什么中文字符串的遍歷要特殊處理,我找到了這個(gè):https://en.wikipedia.org/wiki/UTF-8#Description
原來(lái)一個(gè)中文字符不一定是和英文一樣占用一個(gè)字符,它們可能會(huì)占用幾個(gè)字符,但它們的長(zhǎng)度其實(shí)可以從字符的頭中讀取出來(lái)的。
我簡(jiǎn)單地用瀏覽器翻譯了一下,大家將就這看一下大概意思
當(dāng)然如果你不想自己寫獲取中文字符長(zhǎng)度的邏輯代碼,也可以用別人寫好的開源庫(kù)。這里給大家推薦一個(gè)輕量級(jí)的,只有一個(gè)utf8.h文件的開源庫(kù):https://github.com/sheredom/utf8.h
那么我們的代碼就變成了這樣:
int main() {
std::string text = u8"你好,世界123";
for (size_t i = 0; i < text.size();)
{
auto cplen = utf8codepointcalcsize(&text[i]);
std::cout << text.substr(i, cplen) << std::endl;
i += cplen;
}
return 0;
}
其實(shí)我們查看下utf8.h這個(gè)庫(kù)的utf8codepointcalcsize函數(shù)內(nèi)部實(shí)現(xiàn),和我們上面說(shuō)的是一樣的。
這么一個(gè)簡(jiǎn)單的坑,以前怎么沒(méi)發(fā)現(xiàn)這個(gè)問(wèn)題?一個(gè)是沒(méi)遇到過(guò)這樣的需求,二是就算用到了也不是用C++實(shí)現(xiàn)的,例如在QT上直接使用QString就沒(méi)有這些問(wèn)題。