[C++]string中文的匹配問題

*何謂匹配*

根據string定義的find函數,可以實現查找子串:

string str("abc");
if ((int)str.find("bc") > 0)
    printf("okay\n"); // 匹配成功
else
    printf("none\n"); // 匹配失敗
// 輸出 okay

[注] 使用printf的原因是<cstdio>庫編譯速度更快一些。

運行,使用大量數據,程序運行狀況良好。

然而,如果使用其處理中文,會發現:

string str("人類");
if ((int)str.find("死") > 0)
    printf("okay\n"); // 匹配成功
else
    printf("none\n"); // 匹配失敗
// 輸出 okay

竟然匹配了!?

*探求原因*

爲什麼會產生這種輸出呢?
我們來想一下string.find是怎樣查找的。
按字符查找!
那麼,“人類”有幾個字符(’\0’排外)呢?

printf("%d\n",string("人類").length()); // 4

咦?奇怪,分明是2個,怎麼有4個呢?

原來,Windows中1,中文(簡體)通常使用GBK來編碼的。

我們來看看“人類”是怎麼編碼的:

printf("%X ", "人類"[0]);
printf("%X ", "人類"[1]);
printf("%X ", "人類"[2]);
printf("%X ", "人類"[3]);
// FFFFFFC8 FFFFFFCB FFFFFFC0 FFFFFFE0

請看!
“人類”的GBK編碼是C8CB C0E0。

printf("%X ", "死"[0]);
printf("%X ", "死"[1]);
// FFFFFFCB FFFFFFC0

而“死”的編碼是CBC0!
這樣一來,使用string的find函數進行搜索時,”人類”能夠匹配”死”的問題就瞬間搞懂了。

*問題求解*

那麼,怎麼解決呢?

經查詢資料:
GBK分兩段:ASCII段和中文段。ASCII段使用單字節,和ASCII編碼保持一致;中文(及特殊符號)段使用雙字節編碼。在雙字節段中,第一字節的範圍是81–FE(也就是不含80和FF),第二字節的一部分領域在40–7E,其他領域在80–FE2
也就是說,在GBK編碼中,中文有兩個字節,首字節範圍81~FE,尾字節範圍40~7E。
這麼說來,判斷一個字符(或者中文的一個字)是否爲中文,只要判斷其是否大於0x80即可。在判斷其大於時,獲取兩個字節,同時向下跳一字節,進行後續判斷;如果不大於,則爲ASCII字符,獲取一個字節。

根據以上所述,我們利用union將字符化爲int型。

代碼如下:

std::vector<int> stringToVecInt(const std::string &str)
{
    union {
        char c[2];
        int  i;
    } convert;

    // 段位清零
    convert.i = 0;

    std::vector<int> vec;

    for (unsigned i = 0; i < str.length(); i++) {
        // GBK編碼首字符大於0x80
        if ((unsigned)str[i] > 0x80) {
            // 利用union進行轉化,注意是大端序
            convert.c[1] = str[i];
            convert.c[0] = str[i + 1];
            vec.push_back(convert.i);
            i++;
        } else
            // 小於0x80,爲ASCII編碼,一個字節
            vec.push_back(str[i]);
    }
    return vec;
}

我們使用<algorithm>裏的search函數,進行子串的搜索。

bool include(const std::string &str, const std::string &msg)
{
    auto sour = stringToVecInt(str);
    auto find = stringToVecInt(msg);
    return std::search(sour.begin(), sour.end(), find.begin(), find.end()) != sour.end();
}

測試下效果:

if (include("人類","死"))
    printf("okay\n"); // 匹配成功
else
    printf("none\n"); // 匹配失敗
// none

成功!

*後續思考*

其實,GBK的編碼可以直接寫在字符串裏:
比如”人類”就是”\xC8\xCB\xC0\xE0”。

編碼問題,是相當複雜的問題,凡是有字符串處理,多少都會遇到編碼的問題。
本文問題的提出和解決是基於GBK編碼的,而在互聯網中,UTF-8、Unicode等編碼也是經常使用的。在處理字符串時,一定要留心各種可能發生的編碼問題,畢竟問題是永遠解決不完的。

2016.1.30


  1. 筆者使用Windows下的Visual Studio 2015作爲開發環境。
  2. 詳見維基百科:漢字內碼擴展規範
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章