如果我想要遍历一个字符串中以空格分隔的单词,那么显而易见的解决方案就是:
std::istringstream s(myString);
std::string word;
while (s >> word)
do things
然而,这种方法效率很低。在初始化字符串流时,整个字符串都被复制了一遍,然后每次从中提取一个单词时,该单词都会逐个字符地复制到 word
变量中(几乎等于再次复制整个字符串)。有没有一种方法可以在不手动迭代每个字符的情况下改善这种情况呢?
const std::string s = "quick \t\t brown \t fox jumps over the\nlazy dog";
const std::string ws = " \t\r\n";
std::size_t pos = 0;
while (pos != s.size()) {
std::size_t from = s.find_first_not_of(ws, pos);
if (from == std::string::npos) {
break;
}
std::size_t to = s.find_first_of(ws, from+1);
if (to == std::string::npos) {
to = s.size();
}
// If you want an individual word, copy it with substr.
// The code below simply prints it character-by-character:
std::cout << "'";
for (std::size_t i = from ; i != to ; i++) {
std::cout << s[i];
}
std::cout << "'" << std::endl;
pos = to;
}
不幸的是,代码变得更难阅读,所以应该避免这种改变,或者至少推迟到需要时再进行。
#include <string>
#include <iostream>
#include <boost/algorithm/string.hpp>
int main()
{
std::string s = "stack over flow";
auto it = boost::make_split_iterator( s, boost::token_finder(
boost::is_any_of( " " ), boost::algorithm::token_compress_on ) );
decltype( it ) end;
for( ; it != end; ++it )
{
std::cout << "word: '" << *it << "'\n";
}
return 0;
}
使其具有C++11的特性
由于迭代器对现在来说已经很老派了,因此我们可以使用boost.range来定义一些通用的辅助函数。这些函数最终允许我们使用range-for循环遍历单词:
#include <string>
#include <iostream>
#include <boost/algorithm/string.hpp>
#include <boost/range/iterator_range_core.hpp>
template< typename Range >
using SplitRange = boost::iterator_range< boost::split_iterator< typename Range::const_iterator > >;
template< typename Range, typename Finder >
SplitRange< Range > make_split_range( const Range& rng, const Finder& finder )
{
auto first = boost::make_split_iterator( rng, finder );
decltype( first ) last;
return { first, last };
}
template< typename Range, typename Predicate >
SplitRange< Range > make_token_range( const Range& rng, const Predicate& pred )
{
return make_split_range( rng, boost::token_finder( pred, boost::algorithm::token_compress_on ) );
}
int main()
{
std::string str = "stack \tover\r\n flow";
for( const auto& substr : make_token_range( str, boost::is_any_of( " \t\r\n" ) ) )
{
std::cout << "word: '" << substr << "'\n";
}
return 0;
}
演示:
如果你想让它尽可能快,你需要回到老牌的C函数strtok()
(或其线程安全的伴侣strtok_r()
):
const char* kWhiteSpace = " \t\v\n\r"; //whatever you call white space
char* token = std::strtok(myString.data(), kWhiteSpace);
while(token) {
//do things with token
token = std::strtok(nullptr, kWhiteSpace));
}
请注意,这将覆盖myString
的内容:它通过用终止空字节替换每个标记后的第一个分隔符字符,并依次返回标记的起始指针来工作。毕竟,这是一个传统的C函数。
然而,这个弱点也是它的优势:它不执行任何复制,也不分配任何动态内存(这可能是您示例代码中最耗时的事情)。因此,您不会找到比strtok()
更快的本地C++方法。
char*
传递给std::strtok()
,它要求作为第一个参数的是一个非const指针。所以,没有UB。显然,我同意您更喜欢strtok_r()
,但是a)它似乎不可通过std::
命名空间使用(当然这不会阻止我实际导入和使用C函数),并且b)如果您没有多个线程,则不必要(是的,有些实际软件设计为使用多进程而非多线程或者多线程根本毫无意义)。 - cmaster - reinstate monica字符串分割怎么样?您可以查看post获取更多信息。
在这篇文章中,有一个详细的答案关于如何将字符串分割成标记。在这个答案中,您可以查看第二种使用迭代器和复制算法的方法。
istringstream ::operator>>
所做的(在将结果复制到“word”参数之上)。 - barak manos