STL原始碼剖析(三)
演算法
從語言的角度看:
- 容器 Container 是一個class template
- 演算法 Algorithm 是一個function template
- 迭代器 Iterator 是一個class template
- 函式式 Functor 是一個class template
- 介面卡 Adapter 是一個class template
- 分配器 Allocator 是一個class template
Algorithm 和 Container 之間沒有直接的聯絡,Algorithm 無法得知 Container 都有什麼資訊,所以需要通過 Iterator 來獲取內部的資訊,所以 Iterator 就必須要與 Algorithm 之間有這一定的交接規則,這樣 Iterator 才能適配 Algorithm 的操作。
Algorithm的大概形式如下:
template<typename Iterator>
Algorithm(Iterator itr1, Iterator itr2)
{
...
}
template<typename Iterator, typename Cmp>
Algorithm(Iterator itr1, Ilerator itr2, Cmp comp)
{
...
}
迭代器的分類
各種容器的 iterators 有5種 iterator_category
struct input_iterator_tag {}; struct output_iterator_tag {}; struct forward_iterator_tag : public input_iterator_tag{}; struct bidirectional_iterator_tag : public forward_iterator_tag{} struct random_access_iterator_tag : public bidirectional_iterator_tag{}
Input 迭代器只能向前移動,一次一步,客戶只有可讀取(不能塗寫)他們所指的東西,而且只能讀取一次。它們模仿指向輸入檔案的閱讀指標;C++ 程式庫中的istream_iterator是這一分類的代表。
Ouput 迭代器情況類似,但一切只為輸入:它們只能向前移動,一次一步,客戶只可塗寫它們所指的東西,而且只能塗寫一次。它們模仿指向輸出檔案的塗寫指標;ostream_iterator是這一分類的代表。
Forward 迭代器,這種迭代器可以做前兩種迭代器的每一件事,而且可以讀或寫其所指物一次以上。這使得他們可施行於多次性操作演算法。STL並未提供單向linked list,但某些程式庫有(slist),而這種容器的迭代器就屬於 forward 迭代器。
Bidirectional 迭代器,除了可以向前移動,還可以向後移動。STL的list迭代器就屬於這一分類,set, multiset,map 和 multimap 的迭代器也都是這一分類。
random_access 迭代器可以在常量時間內向前或者向後跳躍任意距離。這樣的算術很類似指標算術。
迭代分類對演算法的影響
首先是一個distance迭代器之間距離的演算法,distance演算法通過對迭代器分類的判斷分別呼叫不同的實現函式。其中,因為迭代器的分類有繼承關係,再根據函式匹配的原則,不同分類的迭代器會自動選擇適合的實現方法。
template <class InputIterator>
inline iterator_traits<InputIterator>::difference_type
distance(InputIterator first, InputIterator last){
typedef typename iterator_traits<InputIterator>::iterator_category category;
return __distance(first, last, category());
}
template<class InputIterator>
inline iterator_traits<InputIterator>::difference_type __distance(InputIterator first, InputIterator last, input_iterator_tag){
iterator_traits<InputIterator>::difference_type n =0;
while(first != last){
++first;
++n;
}
return n;
}
template <class RandomAccessIterator>
inline iterator_traits<RandomAccessIterator>::difference_type __distance(RandomAccessIterator first, RandomAccessIterator last, random_access_iterator_tag){
return last - first;
}
與distance類似的演算法舉例:
template<class InputIterator, class Distance>
inline void advance(InputIterator& i, Distance n){
__advance(i, n, iterator_category(i));
}
//此方法與iterator_traits<InputIterator>::iterator_category一致
template <class Iterator>
inline typename iterator_traits<Iterator>::iterator_category iterator_category(const Iterator&){
typedef typename iterator_traits<Iterator>::iterator_category category;
return category();
}
template<class InputIterator, class Distance>
inline void __advance(InputIterator& i, Distance n, input_iterator_tag){
while(n--) ++i;
}
template<class BidirectionalIterator, class Distance>
inline void __advance(BidirectionalIterator& i, Distance n, bidirectional_iterator_tag){
if(n >= 0)
while(n--) ++i;
else
while(n++) --i;
}
template<class RandomAccessIterator, class Distance>
inline void __advance(RandomAccessIterator& i, Distance n, random_accrss_iterator_tag){
i += n;
}
演算法無法強制要求傳入指定型別的迭代器,因為演算法是一種模板,所以理論上可以傳入所有型別的引數。為了儘可能保證演算法正常工作,演算法會“暗示”使用者出入怎樣型別的迭代器。具體的實現是通過在 template<class RandomAccessIterator, class Distance>
這其中的 RandomAccessIterator
就是對傳入型別的“暗示”。
部分演算法原始碼剖析
accumulate
template <class InputIterator,
class T>
T accumulate(InputIterator first,
InputIterator last,
T init)
{
for(;first != last; ++first)
init = init + *first;//將元素累加至初值init身上
return init;
}
template <class InputIterator,
class T,
class BinaryOperation>
T accumulate(InputIterator first,
InputIterator last,
T init,
BinaryOperation binary_op)
{
for(;first != last; ++first)
init = binary_op(init, *first);
return init;
}
通常演算法會有兩個版本,一個版本適用預設的規則,另一個版本可以讓使用者傳入一個自定義的“規則”。
accumulate 使用的例子:
#include <iostream> //std::out
#include <functional> //std::minus
#include <numeric> //std::accumulate
int myfunc(int x, int y) { return x + 2 * y;}
//function object
struct myclass{
int operator()(int x, int y) { return x + 3 * y;}
} myobj;
int main()
{
int init = 100;
int nums[] = {10, 20, 30};
cout << "using default accumulate:";
cout << accumulate(nuyms, nums+3, init); //160
cout << "\n";
cout << "using functional's minus:";
cout << accumulate(nuyms, nums+3, init, minus<int>()); //40
cout << "\n";
cout << "using custom function:";
cout << accumulate(nuyms, nums+3, init, mufunc); //220
cout << "\n";
cout << "using custom object:";
cout << accumulate(nuyms, nums+3, init, myobj); //280
cout << "\n";
}
for_each
針對容器中的每一個元素都進行一次操作
template <class InputIterator,
class Function>
Function for_each(InputIterator first,
InputIterator last,
Function f)
{
for(; first != last; ++first)
f(*first);
return f;
}
replace, replace_if, replace_copy
replace:範圍內所有等於old_value
的元素都以new_value
替換
template <class ForwardIterator,
class T>
void replace(ForwardIterator first,
ForwardIterator last,
const T& old_value,
const T& new_value){
for(;first != last; ++first)
if(*first == old_value)
*first = new_value;
}
replace_if:範圍內所有滿足pred()
的元素都以new_value
替換
template <class ForwardIterator,
class Predicate,
class T>
void replace_if(ForwardIterator first,
ForwardIterator last,
Predicate pred,
const T& new_value){
for(;first != last; ++first)
if(pred(*first))
*first = new_value;
}
replace_copy:範圍內所有等於old_value
的元素都以new_value
放入新的空間內
template <class ForwardIterator,
class T>
void replace(ForwardIterator first,
ForwardIterator last,
OutputIterator result,
const T& old_value,
const T& new_value){
for(;first != last; ++first, ++result)
*result = *first == old_value ? new_value : *first;
return result;
}
count, count_if
count:統計等於value
的元素個數
template <class InputIterator, class T>
typename iterator_traits<InputIterator>::difference_type
count(InputIterator first,
InputIterator last,
const T& value){
typename iterator_traits<InputIterator>::difference_type n = 0;
for(; first != last; ++first)
if(*first == value)
++n;
return n;
}
count_if:統計滿足pred()
的value
的元素個數
template <class InputIterator, class Predicate>
typename iterator_traits<InputIterator>::difference_type
count_if(InputIterator first,
InputIterator last,
Predicate pred){
typename iterator_traits<InputIterator>::difference_type n = 0;
for(; first != last; ++first)
if(pred(*first))
++n;
return n;
}
以上顯示的標準庫中的演算法,有些容器使用標準庫的演算法效果不是很好或者效率不夠高,那麼這些容器會在成員函式中加入同名的函式。不帶成員函式count()
的容器有:array, vector, list, forward_list, deque
;帶成員函式count()
的容器有:set/multiset, map/multimap, unordered_set/unordered_multiset, unordered_map/unordered_multimap
。通過分析發現自帶成員函式的這些容器都是關聯性容器,可以依據key
快速查詢到value
,所以實現自己特有的函式速度會更快。
find, find_if
find:迴圈遍歷查詢
template <class InputIterator, class T>
InputIterator find(InputIterator first,
InputIterator last,
const T& value)
{
while(first != last && *first != value)
++first;
return first;
}
find_if:根據條件迴圈遍歷查詢
template <class InputIterator, class T>
InputIterator find(InputIterator first,
InputIterator last,
Predicate pred)
{
while(first != last && !pred(*first))
++first;
return first;
}
與count()
一樣,關聯性容器有自己特有的find()
成員函式。
binary_search
二分查詢,前提是已經是有序序列!
template <class ForwardIterator,
class T>
bool binary_search(ForwardIterator first,
ForwardIterator last,
const T& val)
{
first = std::lower_bound(first, last, val);
return (first != last && !(val < *first>));
}
lower_bound: 在不影響原因順序的前提下,找到可以插入的第一個位置。例如序列{10, 10, 10, 20, 20, 20, 30, 30, 30}
,現在需要插入20
,則lower_bound返回指向第一個20的位置。同理,upper_bound指向最後一個20的後面。
template <class ForwardIterator,
class T>
ForwardIterator lower_bound(ForwardIterator first,
ForwardIterator last,
const T& val)
{
ForwardIterator it;
iterator_traits<ForwardIterator>::difference_type count, step;
count = distance(first, last);
while(count>0)
{
it = first;
step = count/2;
advance(it, step);
if(*it < val)//或者可以是 if(comp (*it, val))
{
first = ++it;
count -= step + 1;
}
else count = step;
}
return first;
}