[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. 详见维基百科:汉字内码扩展规范
  • 16
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
C++中的string类是一个非常重要的字符串处理工具,其中包含了许多实用的函数。而其中的find函数也是使用频率非常高的一个函数,主要用于查找某个子串第一次出现的位置。那么,如何实现C++中的string find函数呢? 首先,需要明确string find函数的基本功能:查找一个字符串在另一个字符串中第一次出现的位置。因此,该函数至少需要两个参数,即目标字符串和被查找的字符串。在find函数实现的过程中,我们通常会遍历被查找的字符串,逐个比较每个字符是否匹配目标字符串。这可以使用循环语句来实现。 在实现find函数时,需要考虑一些边界情况。首先,如果被查找的字符串为空,则查找结果应该返回目标字符串的起始位置。其次,如果查找到目标字符串的结尾仍然没有找到,此时应该返回一个特定的指示值(例如-1)。 下面是一个简单的C++ string find函数的实现: ```c++ int find(string target, string pattern) { int n = target.length(), m = pattern.length(); for (int i = 0; i <= n - m; i++) { //i <= n - m是为了保证剩余字符数量不小于模式串长度 bool flag = true; for (int j = 0; j < m; j++) { if (target[i + j] != pattern[j]) { flag = false; break; } } if (flag) return i; } return -1; } ``` 上面的例子中,我们先计算出目标字符串和被查找的字符串的长度,接着开始一个循环。在循环中,我们从目标字符串的第一个位置开始匹配,直到目标字符串中剩余字符数量小于等于被查找的字符串的长度。然后,我们又开始一个内层循环,逐个字符进行比较。当某个字符不匹配时,将标志位flag设为false,并跳出内层循环。如果在内层循环中所有字符都匹配成功,则函数返回该子串在目标字符串中第一次出现的位置。 需要注意的是,这只是一个基本的实现,实际应用中还需要考虑更多的细节问题。比如,考虑匹配效率的问题,可以采用一些字符串匹配算法(如KMP算法、BM算法等)来优化匹配效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Chill_Magic

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值