![](/img/trans.png)
[英]What is the most efficient way to iterate over array and preprocess it before copy in C++?
[英]Most efficient way to iterate over words in a string
如果我想迭代字符串中的單個單詞(由空格分隔),那么顯而易見的解決方案是:
std::istringstream s(myString);
std::string word;
while (s >> word)
do things
然而,這是非常低效的。 在初始化字符串流時復制整個字符串,然后將每個提取的單詞一次一個地復制到word
變量中(這幾乎是第二次復制整個字符串)。 有沒有辦法改進這個,而無需手動迭代每個字符?
在大多數情況下,復制只占整體成本的很小一部分,因此擁有干凈,高度可讀的代碼變得更加重要。 在極少數情況下,當時間分析器告訴您復制會產生瓶頸時,您可以在標准庫的幫助下迭代字符串中的字符。
您可以采用的一種方法是使用std::string::find_first_of
和std::string::find_first_not_of
成員函數進行迭代,如下所示:
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;
}
不幸的是,代碼變得更難以閱讀,所以你應該避免這種改變,或至少推遲它,直到它被要求。
使用boost字符串算法,我們可以按如下方式編寫它。 該循環不涉及任何字符串的復制。
#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-ish
由於迭代器對現在如此古老 ,我們可以使用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 ++方法。
拆分字符串怎么樣? 您可以查看此帖子以獲取更多信息。
在這篇文章中,有一個關於如何在標記中拆分字符串的詳細答案。 在這個答案中,您可以使用迭代器和復制算法檢查第二種方式。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.