简单的字符串分割

你值得拥有

        在实际工作过程中,字符串分割绝对是一个必要技能,因为很多场合下都会用到这个,例如说经典csv文件读取,其中需要用“,”进行分割。另外“.”,“-”等等各种各样,都会出现,还要考虑出现多次等情况,一段小小的处理代码都可能包含着复杂无比的逻辑,Bug也随时可能出现。

        既然来到了C++的世界,自然要简化方法,站在更高的层次来指挥代码,下面就来分析一下伟大航路的“新世界”,Are you ready?!

 

原始方法

        在“新世界”之前,看一下之前C语言的时代,是如何进行字符串分割的呢?那自然就是遍历字符串,找到对应的“分隔符”,然后将分隔符之前的内容进行保存等等。

        如果“分隔符”有多种,例如说空格,逗号等等;“分隔符”有多个等场景,要适配这么一个负责的算法,实在是很麻烦,并且要保证无Bug,那还要考虑各种字符串相关的问题。虽然程序员们都比较喜欢挑战,但是费时费力写这么个函数实在是效率太低。

        相关例子我就不浪费智商了,主要还是因为我太懒,有兴趣的可以百度一下,相信会有很多代码。

 

新的方法

        第一种方法:Boost库中的boost::split。

        优点:快捷方便,不需要考虑各种情况,根据函数说明传入各种参数即可出结果。

        缺点:需要加入Boost库。

        接下来就是我喜欢的方法了,用“流”来分割字符串,一种高大尚的感觉油然而生,废话有点多,老样子,先来看一段代码:

int main(void)
{
    std::string rawStr{"Welcome to the new world"};
    std::stringstreamss{rawStr};
    std::vector<std::string> strVec{std::istream_iterator<std::string>{ss},
       std::istream_iterator<std::string>{}};
    std::copy(strVec.begin(),strVec.end(),
       std::ostream_iterator<std::string>{std::cout, "\n"});
 
    return 0;
}

        好嘞,简单几行,代码已按照空格分割完毕,相关代码只有两行,是不是很犀利呐?

 

继续深入

        正如《如何阅读一本书》里面提到的一样,不要浅尝辄止,随时多问自己几个问题。如果现在不是安装空格进行字符串分割呢?上面的代码还是有效的吗?

        答案自然是:No!那如何解决?

        方案一,流读入时进行判断,其实本质上又回到了之前的问题,不推荐这种方式。还是把例子写出来,大家可以参考一下:

int main(void)
{
    std::string rawStr{"Welcome,to,the,new,world"};
    std::stringstreamss{rawStr};
    std::vector<std::string> strVec;
    std::string splitStr;
    while (ss)
    {
        if (!std::getline(ss,splitStr, ',')) break;
        strVec.push_back(splitStr);
    }
 
    std::copy(strVec.begin(),strVec.end(),
       std::ostream_iterator<std::string>{std::cout, "\n"});
 
    return 0;
}

        缺点很明显,如果分隔符有多种时,逻辑又会复杂的让人崩溃。

        方案二,重设locale,locale中设置分隔符是哪些,再一次,高大尚的气息迎面扑来。知识获取的快感压抑不住,废话总是有点多,代码如下:

class StringSplitLocal : public std::ctype<char>
{
public:
    StringSplitLocal() :std::ctype<char>(GetSplitTable()) {}
 
    staticstd::ctype_base::mask const *GetSplitTable()
    {
        staticstd::ctype_base::mask *masks = nullptr;
        if (nullptr == masks)
        {
            masks = newstd::ctype_base::mask[std::ctype<char>::table_size];
            std::fill_n(masks,std::ctype<char>::table_size, std::ctype_base::mask());
            masks[' '] =std::ctype_base::space;
            masks[','] =std::ctype_base::space;
        }
        return masks;
    }
};
 
int main(void)
{
    std::string rawStr{"Welcome to the,new,world"};
    std::stringstreamss{rawStr};
    ss.imbue(std::locale{std::locale{},new StringSplitLocal{}});
    std::vector<std::string>strVec{std::istream_iterator<std::string>{ss},
        std::istream_iterator<std::string>{}};
    std::copy(strVec.begin(),strVec.end(),
        std::ostream_iterator<std::string>{std::cout,"\n"});
 
    return 0;
}

        设置一个字符串分割本地化,当然进一步可以把需要分割的符号作为参数传入,这样就可以随心所欲的控制了。看这样,任意字符,任意数量,任意分割,代码短小精悍,简直是太美了!Perfect!

        需要注意的是,main中new的StringSplitLocal不要自己释放,因为stringstream析构的时候会做这件事情。如果自己释放掉,会导致继承中的纯虚函数调用失败,导致程序崩溃。


继续继续深入

        不得不说,我是一个麻烦不断的人。今天解决宽字节字符串时,自然又陷入泥潭之中。说到这,不得不感叹一下标准库的模板特化,您能保持接口一致么?虽说效率会略低一点,可是,可是……模板库不就是求高效么?

        对于宽字节,遇到了一个问题,本以为稍微修改一下字符串分割的本地化,就万事大吉了,修改代码如下:

class StringSplitLocal : public std::ctype<TCHAR>
{
public:
    StringSplitLocal() :std::ctype<TCHAR>(GetSplitTable()) {}
 
    staticstd::ctype_base::mask const *GetSplitTable()
    {
        staticstd::ctype_base::mask *masks = nullptr;
        if (nullptr == masks)
        {
            masks = newstd::ctype_base::mask[std::ctype<TCHAR>::table_size];
            std::fill_n(masks,std::ctype<TCHAR>::table_size, std::ctype_base::mask());
            masks[_T(' ')] =std::ctype_base::space;
            masks[_T(',')] =std::ctype_base::space;
        }
        return masks;
    }
};

        结果让我大跌眼镜,std::ctype<wchar_t>根本没有table_size,心中顿时万匹草泥马在奔腾啊。是啊,char的时候,table_size很好办呀,不就是8bit吗,256而已,可是wchar_t是16bit,如果全部列出来,确实有点过大。所以,std::ctype对char和wchar_t分别进行了模板特化。

        当然,这些都是小问题(大问题在后面),于是乎,看了一下ctype<wchar_t>的特化版,发现,只要重写虚函数do_is,就可以解决流进行字符判断问题,于是写下了以下版本:

class StringSplitLocal : public std::ctype<TCHAR>
{
public:
    bool do_is(mask maskVal,TCHAR ch) const
    {
        returnstd::ctype<wchar_t>::do_is(maskVal, ch) || (ch == _T(','));
    }
};

        心想,这代码越写越精短了,可把我给乐坏了。可是大问题来了,切到char版本,测试一下,心中的草泥马又疯狂的奔过,居然结果不对?!

        好吧,看了一下std::ctype对char版本的特化,居然根本没有do_is的虚函数,一切都在is函数中使用256的表进行处理。

        最终结果,字符串分割本地化只好也分别针对char和wchar_t进行特化了。不过和写一个字符串分割函数相比,简单是轻松无比!

 

进一步思考

        第一条,还是想标准库把std::ctype保持统一化,虽然我没有针对性能效率兼容性等等各个方面进行测试,但是分别特化实在是让人很不爽。对于我来说,有时候有些东西就应该痛痛快快的砍掉,例如说std::vector<bool>,残留了这样一些不三不四的东西,终究会阻碍C++的发展。

        第二条,越来越发现C++的地大物博,像是满天的繁星,而我却想将它数清。坚定信念,继续努力,发现更多更多,学习更多更多,让激动不断充满心中!

        第三条,没有充分测试,有问题的小伙伴要随时反馈啊~~~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值