C++中的迭代器和泛型算法
2018-01-22 11:18
169 查看
简单的迭代器和算法
1.迭代器令算法不依赖于容器,但算法依赖于元素类型的操作。泛型算法本身不会执行容器的操作,它们只会运行于迭代器之上,执行迭代器的操作。算法永远不会改变底层容器的大小。算法可能改变容器中保存的元素的值,也可能在容器内移动元素,但永远不会直接添加或删除元素。2.常见的只读算法
<1>accumulate
vector<int> v = {1,2,3}; //这条语句将sum设置为v中元素的和,和的初值设置为0 int sum = accumulate(v.begin(), v.end(), 0);
vector<string> v = {"ab","cd"}; //通过第三个参数创建一个string,然后将vector中所有string元素连接起来 string s = accumulate(v.begin(), v.end(),string("")); string s1 = accumulate(v.begin(), v.end(),"");//错误,无法从const char *转换为string,而const char *没有+运算<2>equal:用于确定两个序列是否保存相同的值。
注意:equal是利用迭代器来完成操作,所以可以通过调用equal来比较两个不同类型的容器中的元素。而且,元素类型也不必一样。
vector<int> v1 = {1,2,3}; vector<int> v2 = { 1,2,3,4};//equal是假定第二序列至少与第一个序列一样长。否则程序就会down掉。 int a=equal(v1.begin(), v1.end(), v2.begin());与equal类似,那些只接受一个单一迭代器来表示第二个序列的算法,都假定第二个序列至少与第一个序列一样长。
3.常见的写算法
vector<int> v1 = {1,2,3}; vector<int> v2 = {4,4,4,4,4,4,4}; fill(v1.begin(), v1.end(), 10);//将每个元素重置为10 fill_n(v2.begin(), 5, 10);//从v2.begin()开始的5个值,用10替换 for (auto i : v1) cout << i << " " ; cout << endl; for (auto i : v2) cout << i << " " ;
4.重排容器元素的算法
vector<string> v = {"sf","ag","vr","ag","ht","er","we","yu"}; sort(v.begin(),v.end()); for (auto s : v) cout << s << " "; cout << endl; auto u = unique(v.begin(), v.end());//重排输入范围,使相同的字符串只出现一次,返回的是不重复区域之后第一个位置的迭代器 v.erase(u, v.end());//删除多余元素 for (auto s : v) cout << s << " ";注意:unique算法并不会删除任何元素,它只是从后向前覆盖相邻的重复元素,使得不重复元素出现在序列开始部分。unique返回的迭代器是指向最后一个不重复元素之后的位置。可以使用erase进行删除。
定制操作
很多算法都会比较序列中的元素。默认情况下,这类算法使用元素类型的<或==运算符完成比较。标准库还为这些算法定义了额外的版本,允许我们提供自己定义的操作来代替默认运算符。1.谓词:是一个可调用的表达式,其返回结果是一个能用作条件的值。可分为一元谓词(只接受单一参数)和二元谓词(接受两个参数)。接受谓词参数的算法对输入序列中的元素调用谓词。所以,元素类型必须能转换为谓词的参数类型。
bool isShorter(const string &s1, const string &s2)//二元谓词 { return (s1.size() < s2.size()); }2.lambda表达式
<1>根据算法接受一元谓词还是二元谓词,我们传递给算法的谓词必须严格接受一个或两个参数。但如果一个算法只接受一元谓词,但根据实际需求,确实要用到两个参数,这时候就可以利用lambda。
我们可以向一个算法传递任何类别的可调用对象。对于一个对象或者表达式,如果可以对其使用调用运算符即(),则称它为可调用的。可调用对象有:函数、函数指针、lambda表达式、重载了函数调用运算符的类。
lambda介绍:一个lambda表达式表示一个可调用的单元代码。可以将其理解为未命名的内联函数。lambda可能定义在函数内部。
[capture list](parameter list)->
aabf
return type {function}
capture list是指捕获列表,是在lambda所在函数中国定义的局部变量列表。需要注意的是:我们只对lambda所在函数中定义的(非static)变量使用捕获列表。并且,lambda可以直接使用定义在当前函数之外的名字。与普通函数不同的是,lambda必须使用尾置返回来指定返回类型。在lambda中,可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体。
auto f = [] {return 24; };
cout<<f()<<endl;lambda的调用方式与普通函数的调用方式类似,都是使用调用运算符。
<2>向lambda传递参数
lambda不能有默认参数,所以,一个lambda调用的实参永远与形参数目相等。
<3>lambda捕获
当定义一个lambda时,编译器生成一个与lambda对应的新的类类型。也就是说,当向一个函数传递一个lambda时,同时定义了一个新类型和该类型的一个对象:传递的参数就是此编译器生成的类类型的未命名的对象。类似的,当使用auto定义一个用lambda初始化的变量时,定义了一个从lambda生成的类型的对象。
类似参数传递,变量的捕获方式也可以是值或者引用。与传值参数类似,采用值捕获的前提是变量可以拷贝。与参数不同的是,被捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝。
void fun() { size_t v = 42; auto f = [v] {return v; }; v = 24; auto j = f();//j的值还是42,因为是值捕获,被捕获的变量是在创建时拷贝的 }引用捕获与返回引用有着相同的问题和限制。所以要确保被引用的对象在lambda执行的时候是存在的。
<4>指定lambda返回类型
如果一个lambda体包含return之外的任何语句,则编译器假定此lambda返回void。当我们需要一个lambda定义返回类型时,必须使用尾置返回类型。
void fun() { vector<int> v{1,3,-2,6,8,-6}; transform(v.begin(), v.end(),v.begin(), [](int i)->int {if (i < 0) return -i; else return i; }); }3.参数绑定,标准库bind函数
作用就是将bind函数看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表。
auto newCallable = bind(callable, arg_list);newCallable是一个新的可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。即,当我们调用newCallable时,newCallable会调用callable,并传递给它arg_list中的参数。
auto g=bind(f,a,b,_2,c,_1);//f这个可调用对象有5个参数,_1和_2这两个参数由g这个新的调用对象提供,也就是 //新调用对象将它自己的参数作为第三个和第五个参数传递给f。f的第一个、第二个、 //和第四个参数分别被绑定到给定的值a、b和c上。
再探迭代器
以下几个迭代器都是在头文件iterator中。1.插入迭代器
用来向容器中插入元素,是一种迭代器适配器,它接受一个容器,生成一个迭代器,能实现向给定容器添加元素。
back_inserter | 创建一个使用push_back的迭代器 |
front_inserter | 创建一个使用push_front的迭代器 |
inserter | 创建一个使用insert的迭代器。此函数接受两个参数,这个参数必须是一个指向给定容器的迭代器。元素将被插入到给定迭代器所表示的元素之前 |
int main() { list<int> li1 = {1,2,3,4}; list<int> li2,li3,li4; copy(li1.cbegin(), li1.cend(), back_inserter(li2)); copy(li1.cbegin(), li1.cend(), front_inserter(li3)); copy(li1.cbegin(), li1.cend(), inserter(li4,li4.begin())); for (auto i : li2) cout << i << " "; cout << endl; for (auto i : li3) cout << i << " "; cout << endl; for (auto i : li4) cout << i << " "; cout << endl; system("pause"); return 0; }
2.iostream迭代器
iostream类型不是容器,但标准库定义了可以用于这些IO类型对象的迭代器isteram_iterator读取输入流,ostream_iterator向一个输出流写数据。这些迭代器将它们对应的流当作一个特定类型的元素序列来处理。通过使用流迭代器。我们可以用泛型算法从流对象读取数据以及向其写入数据。
int main() { vector<char> v; istream_iterator<char> in_iter(cin); istream_iterator<char> eof;//不为它指定istream对象,它即代表了end-of-file(文件尾) while (in_iter != eof) v.push_back(*in_iter++); for (auto i : v) cout << i << " "; system("pause"); return 0; }
int main() { vector<char> v = { 'I','a','w' }; ostream_iterator<char> out_iter(cout, " "); for (auto e : v) *out_iter++ = e; system("pause"); return 0; }
3.反向迭代器
是在容器中从尾元素反向移动的迭代器。此时,++it会移动到前一个元素;--it会移动到下一个元素。
int main() { string line = "FIRST,MIDDLE,LAST"; auto comma = find(line.crbegin(), line.crend(), ','); cout << string(line.crbegin(), comma) << endl;//反向迭代器,内容也反着出来了 cout << string(comma.base(), line.cend()) << endl;//从后向前找,输出是正常迭代器 system("pause"); return 0; }
通过调用reverse_iterator的base成员函数能够返回普通迭代器。
相关文章推荐
- <C++>迭代器:访问string对象和vector对象
- C++ 学习笔记(10)泛型算法、lambda表达式、bind函数、迭代器
- C++中的迭代器和迭代器的辅助函数
- C++ STL的迭代器(iterator)
- C++ STL 迭代器失效问题的剖析
- c++学习-为什么要用迭代器
- C++学习笔记(三) 迭代器
- 泛型算法 (输入输出迭代器和算法综合介绍)
- C++ 迭代器Iterator的分类
- 笔记:C++学习之旅---泛型算法
- 面向对象与C++程序设计-向量和迭代器学习笔记
- C++ 迭代器失效
- 【C++】Runtime error:iterators incompatible 迭代器类型不一致
- c++关于顺序容器指针迭代器失效问题
- C++:顺序容器与迭代器
- 用C++实现设计模式---两个迭代器的传说
- C++ STL 基础及应用(3) 迭代器
- C++容器和迭代器(一)——基础概念
- C++容器以及迭代器
- STL,迭代器的C++简单实现