至少到C++ 23,标准库中都没有提供string的split函数,于是就给了各显神通自造轮子的空间。boost中的实现,是分解成vector<string>&,这样处理,split函数中至少发生了一次全串级别的复制。作者在使用C++ 11写一个从巨量日志文件中提取特定字段的程序时,自造了一个返回所有分隔符位置的“轮子”,在函数体中不发生字符串复制:
template <class T>
std::vector<size_t> NSplit(const std::string& src, T delimiters)
{
size_t nfrom, nfind;
std::vector<size_t> result;
nfrom=0;
do {
result.push_back(nfrom = nfind = src.find_first_of(delimiters, nfrom));
nfrom++;
} while(nfind != std::string::npos);
return result;
}
template <class T>
std::vector<size_t>& NSplit(std::vector<size_t>& result, const std::string& src, T delimiters)
{
size_t nfrom, nfind;
result.clear();
nfrom=0;
do {
result.push_back(nfrom = nfind = src.find_first_of(delimiters, nfrom));
nfrom++;
} while(nfind != std::string::npos);
return result;
}
//调用方式示例1:
string src = "Str0\tStr1@Str2\tStr3";
size_t idx=0;
for( auto n : NSplit(src,"@\t") ) {
cout << src.substr(idx, n-idx) << endl;
idx = ++n;
}
//调用方式示例2:
#define NSplit_s(s,v,i) ((i)==0? s.substr(0,v[0]) : s.substr(v[(i)-1]+1,v[i]-v[(i)-1]-1))
string src = "Str0\tStr1@Str2\tStr3";
vector<size_t> v = NSplit(src, "@\t");
for( size_t i=0; i<v.size(); i++ )
cout << NSplit_s(src,v,i) << endl;
可以看出,如果要在主程序中使用所有的子串,由于调用了substr,那么也会发生一次全串级别的复制,效率并不比boost的实现高。但是如果仅仅使用有限的几个子串,则效率通常要高许多。
C++ 17引入了string_view,效率问题就迎刃而解了:
template <class T>
std::vector<std::string_view> VSplit(const std::string& src, T delimiters)
{
size_t nfrom, nfind;
std::vector<std::string_view> result_views;
if( !src.empty() ) {
for( nfrom=0; (nfind = src.find_first_of(delimiters, nfrom)) != std::string::npos; nfrom=nfind+1 )
result_views.emplace_back(src.data()+nfrom, nfind-nfrom);
result_views.emplace_back(src.data()+nfrom, src.size()-nfrom);
}
return result_views;
}
template <class T>
std::vector<std::string_view>& VSplit(std::vector<std::string_view>& result_views, const std::string& src, T delimiters)
{
size_t nfrom, nfind;
result_views.clear();
if( !src.empty() ) {
for( nfrom=0; (nfind = src.find_first_of(delimiters, nfrom)) != std::string::npos; nfrom=nfind+1 )
result_views.emplace_back(src.data()+nfrom, nfind-nfrom);
result_views.emplace_back(src.data()+nfrom, src.size()-nfrom);
}
return result_views;
}
//调用方式示例:
string src = "Str0\tStr1@Str2\tStr3";
for( auto s : VSplit(src, "@\t") )
cout << s << endl;
上述C++ 11和C++ 17的实现中,两个重载函数的效率差别属于C语言的基本常识,在此不做讨论。