C++string中文的匹配问题

问题引入

正常字符串匹配,全英文

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

如果其中包含中文

string str = "人类";
if ((int)str.find("死") > 0)
    printf("okay\n"); // 匹配成功
else
    printf("none\n"); // 匹配失败
// 输出 okay

问题: 为什么 人类 会和 死 匹配
原因:Windows中,中文(简体)通常使用GBK来编码的。一个中文占两个字节
我们来看看“人类”是怎么编码的:

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

因为中文只占两个字节,“人类”的GBK编码是C8CB C0E0
前面的F是扩展,原因看这里

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

而“死”的编码是CBC0!
这样一来,使用string的find函数进行搜索时,”人类”能够匹配”死”的问题就瞬间搞懂了。

问题求解

那么,怎么解决呢?

经查询资料:
GBK分两段:ASCII段和中文段。ASCII段使用单字节,和ASCII编码保持一致;中文(及特殊符号)段使用双字节编码。在双字节段中,第一字节的范围是81–FE(也就是不含80和FF),第二字节的一部分领域在40–7E,其他领域在80–FE2。
也就是说,在GBK编码中,中文有两个字节,首字节范围81FE,尾字节范围407E。
这么说来,判断一个字符(或者中文的一个字)是否为中文,只要判断其是否大于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;
}

我们使用里的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

代码用了union即为联合,它是一种特殊的类。通过关键字union进行定义,一个union可以有多个数据成员。
类中的成员共同占用一块内存,当赋值其中一个成员的值时,会覆盖内存(按照内存空间覆盖)

convert.c[1] = str[i];
convert.c[0] = str[i + 1];
vec.push_back(convert.i);

该文定义的union convert *四个字节(因为最大的int占用四个字节)
c[0],c[1]各占一个字节,给他们赋值之后,相当于i 也有了值,实现将中文转为数字

中文匹配的解释
union的解释

上一篇:SQL注入之基础知识及手工注入


下一篇:mysql03