10.1 概述
- 头文件<algorithm>,查找函数find(iterbegin,iterend,val)有三个参数,前两个表示查找范围的迭代器,如果范围中无匹配元素,则返回第二个参数表示搜索失败;count函数可以计算val出现的次数;
- 泛型算法本身不会执行容器的操作,只会运行于迭代器之上,执行迭代器的操作;算法可能改变容器中元素的值或者移动元素,但永远不会直接添加或删除元素;
10.2 初识泛型算法
- 一些算法只会读取其输入范围内的元素从不改变元素,比如find, count, accumulate;
- accumulate也接受三个参数(iterbegin,iterend, sum)使用时要加入头文件<numberic>; 第三个参数为求和的起点,蕴含着一个假定就是元素类型加到求和起点的类型上操作必须是可行的,即序列中的元素类型必须和第三个参数匹配或者能够转换为第三个参数的类型;
- 对string序列采用accumulate算法时第三个参数不可以直接写成“”,因为这是一个const char*类型,它没有定义+运算,所以要显示地创建一个string对象 string("");
- equal是比较两个序列的算法,由于equal利用迭代器完成操作,即使容器类型不同,只要容器类元素类型相同也可以进行比较;对于只接受一个单一迭代器来表示第二个序列的算法,都假定第二个序列至少与第一个序列一样长;
- fill函数支持向容器中写入值,同样接受三个参数;fill_n函数与fill函数区别在于第二个参数为一个size_t类型,即为unsigned表示填充的元素的个数;
- 试图往空的容器中使用fill_n会导致迭代器访问到容器末尾之外的位置,避免的方法可以使用resize()
vector<int>vec; cout << vec.size() << endl; vec.resize(10); cout << vec.size() << endl; fill_n(vec.begin(), 10, 2); for (int a : vec) cout << a << " ";
或者采用341页的back_inserter方法调用一个插入迭代器;
-
copy函数返回的是目的位置迭代器(递增后)的值;replace函数读入一个序列,并将其中所有等于给定值的元素都改为另一个新值,接受4个参数,前两个为迭代器表示范围,第三个表示序列中被替换的旧值,第四个表示新值;
-
unique函数用于相邻元素的去重,返回最后一个去重后相邻元素的位置的下一个,它只是覆盖重复元素并没有真正删除元素,因此前面要配合sort排序,后面要配合erase删除多余的元素,详见343页;
10.3 定制操作
- stable_sort可以维持相等元素的原有顺序,也可以向算法传入函数参数来自定义排序规则;详见344页
- lambda表达式 [capture list] (parameter list)->return type{function body},如果lambda的函数体包含任何单一return语句之外的内容,且未指定返回类型,则返回void;
- 一个lambda只有在其捕获列表中捕获一个它所在函数中的局部变量,才能在函数体中使用该变量;
- 捕获列表只用于局部肥static变量,lambda可以直接使用局部static变量和它所在函数之外声明的名字;
- 当想一个函数传递一个lambda时,同时定义了一个新类型和该类型的一个对象,传递的参数就是次编译器生成的类类型的未命名对象;
- 当用引用方式捕获一个变量时,必须保证在lambda执行时变量是存在的,详见351页;
- 当混合使用隐式捕获和显式捕获时,显式捕获的变量必须使用与隐式捕获不同的方式;即如果隐式捕获是引用方式,则显式捕获必须采用值方式;
- 可变lambda不传入引用时,要使用关键词mutable
void fun3() { size_t v1=42; auto f = [v1]() mutable{return ++v1;}; v1 = 0; auto j = f(); // j为43 } void fun4() { size_t v1=42; auto f = [&v1] {return ++v1;}; // 捕获引用 v1 = 0; auto j = f(); // j为1 }
-
当需要为一个lambda定义返回类型时,必须使用尾置返回类型,否则一旦函数体内有除了return 以外的语句都会默认返回void
transform(vi.begin(),vi.end(),vi.begin(), [](int i)->int { if(i<0) return -i; else return i;}); transform(vi.begin(),vi.end(),vi.begin(), [](int i){return i<0? -i:i;}); //等效写法
-
标准库bind函数,定义在头文件<functional>中,接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表;用例
//参数绑定案例 auto wc = find_if(words.begin(),words.end(), [sz](const string&s) {}); auto wc = find_if(words.begin(),words.end(), bind(check_size,_1,sz)); //这里把check_size函数绑定到一个临时函数的第一个参数
-
名字_1,_n都定义在命名空间placeholders中,而此空间又定义在std中,对于每一个占位符名字,我们都必须提供一个单独的using声明,现在可以直接通过using namespace namespace_name来使用所有placeholders的名字;
-
bind函数重排参数顺序,详见356页;
sort(words.begin(),words.end(),isShorter); //调用isShorter(A,B) sort(words.begin(),words.end(),bind(isShorter,_2,_1); //与上面的作用正好相反,调用isShorter(B,A)
-
练习10.22,
using namespace std; using namespace std::placeholders; bool lesssz(const string& s1, size_t sz) { return s1.size() <sz; } int main() { int sz = 6; vector<string> words{ "what","is","thissdff","wwer","wweaaaaa" }; auto num = count_if(words.begin(), words.end(), bind(lesssz,_1,sz)); cout << num; return 0; }
练习10.24
// lambda函数形式 int main() { string str = "what"; vector<int> vec = { 2,1,4,5,6 }; auto ans = find_if(vec.begin(), vec.end(), [&str](int a)->bool {return str.size() < a; }); cout << *ans << endl; return 0; } //bind()形式 bool checksize(const string& str, int num) { return str.size() < num; } int main() { string str = "what"; vector<int> vec = { 2,1,4,5,6 }; auto ans = find_if(vec.begin(), vec.end(), bind(checksize,str,_1)); cout << *ans << endl; return 0; }
10.4 再探迭代器
-
插入迭代器, 练习10.28
int main() { vector<int> lst = { 1,2,2,3,4 }; vector<int> lst2, lst3; deque<int> lst4; copy(lst.begin(), lst.end(), inserter(lst2, lst2.begin())); for (auto a : lst2) cout << a << " "; cout << endl; copy(lst.begin(), lst.end(), back_inserter(lst3)); for (auto a : lst3) cout << a << " "; cout << endl; copy(lst.begin(), lst.end(), front_inserter(lst4)); // vector,list没有push_front因此不能使用front_inserter for (auto a : lst4) cout << a << " "; return 0; }
-
istream_iterator<int> int_eof;
int_eof被定义为空的istream_iterator,可以当做尾后迭代器来使用,知道了这一点,就可以玩出很多骚操作了
istream_iterator<int> int_it(cin); //从cin 读取int istream_iterator<int> int_eof; //空的流迭代器,可以作为尾后迭代器 vector<int> vec; while(int_it!=int_eof) { vec.push_back(*int_it++); //把cin读入的int加入vec,同时int_it往后移 } vector<int> vec2(int_it,eof); //效果等同,相当于把从int_it到int_eof部分的int复制构造了vec2;
-
必须将ostream_iterator绑定到一个指定的流,不允许空的或者表示尾后的ostream_iterator;运算符*和++实际上对ostream_iterator对象不做任何事情,因此忽略并没有影响,但是为了符合迭代器标准操作逻辑还是最好加上 ;
-
一种新的输出容器的写法:
ostream_iterator<T> T_outer(cout, " "); //输出迭代器,每次读取一个元素后输出,并在后面加上" " copy(vec.begin(),vec.end(), T_outer); //等价写法 for(auto a:vec) { *T_outer++=a; //*和++无实际意义,但是为了符合迭代器标准还是加上 }
-
这章的内容得把练习题多写一写才能掌握其精髓,练习10.29
int main() { ifstream in("book_sales.txt"); vector<string> vec; string temp; istream_iterator<string> in_iter(in), eof; ostream_iterator<string> out_iter(cout, "\n"); while (in_iter != eof) { temp = *in_iter++; vec.push_back(temp); } copy(vec.begin(), vec.end(), out_iter); return 0; }
10.30
int main() { istream_iterator<int> in_iter(cin), eof; ostream_iterator<int> out_iter(cout, " "); vector<int> vec; while (in_iter != eof) { vec.push_back(*in_iter++); } sort(vec.begin(), vec.end(), [](int a, int b) {return a > b; }); copy(vec.begin(), vec.end(), out_iter); }
10.31 把copy改为unique_copy即可
-
不可能从一个forward_list或一个流迭代器创建反向迭代器;
-
反向迭代器rbegin()可以通过调用reverse_iterator的base成员函数来完成转换,其中riter与riter.base()指向不同的元素,具体关系看364页图10.2;
-
练习10.37
int main() { vector<int>vec = { 1,2,3,4,5,6,7,0,8,9 }; ostream_iterator<int> out_iter(cout, " "); list<int>ans; copy(vec.begin() + 3, vec.begin() + 7, front_inserter(ans)); copy(ans.begin(), ans.end(), out_iter); return 0; }
10.5 泛型算法结构
-
大多数泛型算法有如下4种结构之一:
alg(beg,end, otherargs); alg(beg,end, dest, otherargs); alg(beg,end, beg2, otherargs); alg(beg,end, beg2, end2, otherargs); //alg是算法的名字,beg,end表示算法的输入范围
-
dest参数是一个表示算法可以写入的目的位置的迭代器,算法假定按其需要写入数据,不管写入多少数据都是安全的;如果dest是一个直接指向容器的迭代器,那么算法将输出数据写到容器中已存在的元素内;如果dest指向的是一个空容器的迭代器,那么copy操作会报错;更常见的情况是把dest绑定到一个插入迭代器或者是一个ostream_iterator;
-
接受单独beg2的算法假定从beg2开始的序列与beg和end所表示的范围至少一样大;
10.6 特定容器算法
- list和forward_list不提供随机访问迭代器,因此不好使用sort函数,优先使用成员函数的算法(list.sort())而不是通用算法;