C++ 的 string 为什么不提供 split 函数?

发布时间:
2024-05-08 21:48
阅读量:
27

string split 是一个既简单又复杂的东西。

说它简单,是因为实现一个string split本身代码量很少,逻辑不复杂,这个问题甚至常见于编程教材的课后习题,很适合用来给编程的初学者拿来练手,一般几行或者十几行代码就能搞定。

说它复杂,是因为需求很不统一,很难“标准化”。比如举几个例子:

1、字符串"aa,bb,cc",用","来分割,答案很明确,结果为三段,分别为"aa","bb"和"cc"。

2、字符串",aa,,bb,",还是用","来分割,结果应该是什么呢?有时候我们需要它的结构是分为两段即"aa"和"bb",但有时候我们希望它的结果是五段,即"\0","aa","\0","\0","bb"和"\0",你看不同的需求就出来了。

3、字符串"abababa",用"ab"来分割,你希望结果是什么呢?用"aba"来分割呢?

4、更极端的例子,字符串"abababa",同时用"a","b","ab","ba"来分割,结果应该是什么呢?

5、空字符串"\0"被分割,结果应该如何?

也许你觉得这些都是corner case,业务中遇不到,但是如果你要写成一个通用的库,这些corner case就不得不考虑。那就会出现矛盾,如果覆盖所有的corner case,那无疑就会“过度设计”,而如果不“过度设计”就很难“通用”。

比如谷歌的abseil库,设计了AllowEmpty,SkipEmpty和SkipWhitespace等分割模式,boost也有类似的设计,可是即使这样,也不能覆盖所有的场景。

既然需求多样难以满足,同时又逻辑简单谁都会写,那不如交给程序员自己去写好了。

我是很鼓励程序员造轮子的,几行代码满足自己的需求,同时达到更快的速度,有什么不好呢?

这里吐槽一下一些大厂第三方库的string split,它们真的没有好好写。这么一个简单而常用的轮子,按说谁写运行速度都不会相差多大,没有多少优化的空间,可实际情况是,它们为啥把string split搞得这么慢?一个测试:

#include "morn_util.h" #include <string> #include <vector> #include <boost/algorithm/string.hpp> #include <boost/algorithm/string_regex.hpp> #include <absl/strings/str_split.h> #include <folly/String.h> void test1() { printf("\nsplit with \"\\n\":\n"); MString *text=mStringCreate(); mText(text,"../data/a_Q_zheng_zhuan.txt"); std::string str(text->string); std::vector<std::string> vec1; mTimerBegin("boost"); for(int i=0;i<1000;i++) boost::split(vec1,str,boost::is_any_of("\n"),boost::token_compress_off); mTimerEnd("boost"); printf("vec.size()=%ld\n",vec1.size()); std::vector<std::string> vec2; mTimerBegin("absl"); for(int i=0;i<1000;i++) vec2=absl::StrSplit(str,'\n'); mTimerEnd("absl"); printf("vec.size()=%ld\n",vec2.size()); std::vector<std::string> vec3; mTimerBegin("folly"); for(int i=0;i<1000;i++) { vec3.clear(); folly::split('\n',str,vec3); } mTimerEnd("folly"); printf("vec.size()=%ld\n",vec3.size()); MList *list; mTimerBegin("Morn"); for(int i=0;i<1000;i++) list=mStringSplit(text,"\n"); mTimerEnd("Morn"); printf("list->num=%d\n",list->num); mStringRelease(text); } void test2() { printf("\nsplit with \"\":\n"); MString *text=mStringCreate(); mText(text,"../data/a_Q_zheng_zhuan.txt"); std::string str(text->string); std::vector<std::string> vec1; mTimerBegin("boost"); for(int i=0;i<1000;i++) boost::algorithm::split_regex(vec1,str,boost::regex("。")); mTimerEnd("boost"); printf("vec.size()=%ld\n",vec1.size()); std::vector<std::string> vec2; mTimerBegin("absl"); for(int i=0;i<1000;i++) vec2=absl::StrSplit(str,"。"); mTimerEnd("absl"); printf("vec.size()=%ld\n",vec2.size()); std::vector<std::string> vec3; mTimerBegin("folly"); for(int i=0;i<1000;i++) { vec3.clear(); folly::split("。",str,vec3); } mTimerEnd("folly"); printf("vec.size()=%ld\n",vec3.size()); MList *list; mTimerBegin("Morn"); for(int i=0;i<1000;i++) list=mStringSplit(text,"。"); mTimerEnd("Morn"); printf("list->num=%d\n",list->num); mStringRelease(text); } int main() { test1(); test2(); return 0; }

以上,使用鲁迅先生的中篇小说《阿Q正传》为例子进行测试,分别对字符串进行:1、段分割(单字符分割,分割符为换行符"\n"),2、句分割(字符串分割,分割符为汉语句号"。")。测试对象是三个很著名的库,boost,abseil和folly,以及自己写的C语言基础库Morn,分别测试1000次。测试结果为:

运算的结果是一样的,但自己写的程序比其他三个库快了几倍到几十倍。所以,string split这种简单的程序,完全不需要到处去找第三方库。

END