泛型算法的思想是将算法操作与容器分离开来,容器提供一个迭代器区间,算法对迭代器区间进行操作而非容器本身。这样的好处是一些通用算法不用针对每种容器每种数据类型都实现一遍,即所谓的“genetic”。
// e.g
template<typename Iterator, typename T>
Iterator find(Iterator first, Iterator last, const T& val){
while(first != last && !(*first == val){
++first;
}
return first;
}
// 使用这个find算法要求容器中数据类型定义了==操作符,这样才能比较
// 若未定义==,则可用另一个版本的find算法,增加一个参数接收一个比较二者是否相等的函数(的指针)
-
泛型算法只对迭代器进行加减、比较相等、提领等,而不使用任何容器自身的操作,以此确保通用性。使用STL的泛型算法需要
#include<algorithm>
,还有一些数值算法是#include<numeric>
。 -
绝大部分泛型算法都是对一个范围的容器元素进行操作,头两个参数就标明了这个范围。
vector<int> vecI;
int startVal = 5;
accumulate(vecI.begin(), vecI.end(), startVal); // startVal is necessary, its type is
// the return type of the algorithm, so the elements in the container must match or be
// convertible to this type
vector<double> vecD;
double retD = accumulate(vecD.begin(), vecD.end(), 0); // compilable but loss of precision
vector<string> vecS;
accumulate(vecS.begin(), vecS.end(), ""); // error, the third argument should be string("")
vector<int> vec;
fill_n(vec.begin(), 10, 0); // error, attempting to write to 10 nonexistent elements in vec
fill_n(back_inserter(vec), 10, 0); // ok, remember to include the iterator header
replace(ilist.begin(), ilist.end(), 0, 9); // replace any 0 by 9
replace_copy(ilist.begin(), ilist.end(), back_inserter(vec), 0, 9); // do not change the
// original container, use vec as the destination
-
有一类迭代器叫做 insert iterator,可用于向容器中添加元素。
back_inserter
函数返回back_inserter_iterator
。b = back_inserter(vec); *b = x
等价于vec.push_back(x)
。 -
unique
函数保留连续相同值中的第一个,如果两个相同值不相邻不会被覆盖。不改变大小,仅做覆盖,将那些独特值移到容器前面部分。返回值为范围后一位迭代器。
// 数一串单词中长度大于5的单词数,重复出现只算一次
bool GT5(const string& word){
return word.size() > 5;
}
vector<string> words;
// init words
sort(words.begin(), words.end());
vector<string>::iterator end_unique = unique(words.begin(), words.end());
words.erase(end_unique. words.end()); // remove duplicated words
vector<string>::size_type ret = count_if(words.begin(), words.end(), GT5);
-
stable_sort
稳定排序,在排序时对“相等”的元素不会打乱原来的相对位置。因此它适合用来对多重key排序,比如先按年龄排序,同龄再按身高排序 。 -
在排序或是其他需要用到比较的算法(如
count_if
)的参数中,有时候需要用到 predicate,借用书上的定义:A predicate is a function that performs some test and returns a type that can be used in a condition to indicate success or failure。 如上例中的GT5
。具体参数个数根据具体情况。 -
注意使用迭代器的时候范围不可越界,即必须在
[container.begin(), container.end()]
之间,否则运行时会出错。所以在迭代器递减的时候要注意越界问题,实在想用递减的话得用下例中的第二种或者用反向迭代器:
for (vector<string>::iterator it = tmp.end() - 1; it >= tmp.begin(); --it) { cout << *it << endl; } // error, vector iterator not decrementable to begin() - 1 for (vector<string>::iterator it = tmp.end(); it != tmp.begin(); --it) { cout << *(it - 1) << endl; } // ok // or for (vector<string>::reverse_iterator it = tmp.rbegin(); it != tmp.rend(); ++it) { cout << *it << endl; }
-
Insert Iterators:
back_inserter(container)
,front_inserter(container)
,inserter(container, iterator)
,要求所对应的容器相对应的支持push_back
或push_front
,inserter
插入位置为给定迭代器之前。此外注意下例,front_inserter
每次都插入到目标容器的最前面;而inserter
在插入一个后原来指定的位置就不再是容器的首位了,也可以简单理解为整块区间插入到指定位置。注意这类迭代器会改变容器的size
。list<int> iList {1, 2, 3}, iList1, iList2; copy(iList.begin(), iList.end(), front_inserter(iList1)); // 3 2 1 copy(iList.begin(), iList.end(), inserter(iList2, iList2.begin())); // 1 2 3
-
iostream Iterators:读入写出流,对应要求其操作类型定义了操作符
>>
,<<
istream_iterator<int> cin_it(cin); // 从 cin 中读入类型为 int 的流 istream_iterator<int> eof; // 无参数表示输入流的终止迭代器,输出流无此迭代器 while(cin_it != eof){ iVec.push_back(*cin_it++); } // or vector<int> iVec(cin_it, eof); // two iterators indicating the input range ofstream outfile("***.txt"); ostream_iterator<int> fout_it(outfile, "\n"); for(vector<int>::iterator it = iVec.begin(); it != iVec.end(); ++it){ *fout_it++ = *it; // 实测不加++也是正常输出,一旦赋值即写出,不可撤回 } // 将它应用到泛型算法需要用到iterator的地方,如 sort(iVec.begin(), iVec.end()); unique_copy(iVec.begin(), iVec.end(), fout_it);
-
Reverse Iterators:反向迭代器,默认遍历方向为反向
sort(vec.rbegin(), vec.rend()); // 从大到小排 // assume a string of words separated by comma called words // to get the first word string::iterator it1 = find(words.begin(), words.end(), ','); cout << string(words.begin(), it1); // construct the first word // to get the last word string::reverse_iterator it2 = find(words.rbegin(), words.rend(), ','); cout << string(words.rbegin(), it2); // wrong, word in reverse order cout << string(it2.base(), words.end()); // right, base() transforms it2 to the // element after it and change the traverse direction // [words.rbegin(), it2) and [it2.base(), words.end()) // refer to the same elements // copy(***, ***, dest.rbegin()); // 逆序复制,注意要留够足够的空间
-
用于标记范围的迭代器类型必须完全相同,包括是否
const
。有时候会用到const_iterator
。 -
迭代器按读写遍历功能强弱分为几个等级:
- Input iterator : 读,不写;仅增;区间内仅能有一个迭代器
- Output iterator : 写,不读;仅增;区间内仅能有一个迭代器
- Forward iterator : 读写;仅增;区间内可以有多个迭代器
- Bidirectional iterator : 读写;增减;所有STL容器至少支持此级别
- Random access iterator : 读写;所有迭代器数学操作(
map set list
不支持,对此容器使用sort
等需要随机访问的算法会报很难看懂的错,因此需要特别注意;string vector deque
支持)
不同算法对迭代器级别要求不同,使用时需要注意。高等级迭代器可以满足低要求算法。
-
Algorithm parameter Patterns
alg(beg, end, other parms); // input range alg(beg, end, dest, other parms); // dest usually uses inserter iteration alg(beg, end, beg2, other parms); // two input ranges alg(beg, end, beg2, end2, other parms);
-
Algorithm naming conventions
sort(beg, end); sort(beg, end, comp); // overloaded // find first element matched find(beg, end, val); find_if(beg, end, pred); // not overload to get rid of overloading ambiguities(same num of parms) reverse(beg, end); reverse_copy(beg, end, dest); // not change original container
Reference : C++ Primer 4th edition(评注版)