泛型算法
大多数算法定义在头文件 algorithm 中。标准库还在头文件numeric中定义了一组数值泛型算法。算法大多不直接操作容器,而是遍历两个由迭代器指定的一个元素范围来操作。
标准库find方法
vector<int>vec = { 1,3,5,7,9,12 };
int val = 3;
// 在vec中查找,返回指向第一个等于给定值的元素的迭代器。不修改应使用cbegin()、cend();
auto res = find(vec.cbegin(), vec.cend(), val);
// 找不到返回时,第二个参数,vec.end()
auto res2 = find(vec.cbegin(), vec.cend(), 4);
auto end = vec.end(); // 等同于res2
res++; // 现在res为5
由于find操作的是迭代器,故可以在任何容器中使用此函数:
list<string>vec = { "a", "b", "c", "a value", "d"};
string val = "a value";
auto res = find(vec.cbegin(), vec.cend(), val);
auto res2 = find(vec.cbegin(), vec.cend(), "val");
res2--; // d
在数组中指针就像内置数组上的迭代器,因此也可以使用find。
int ia[] = { 27, 210, 12, 47, 109, 83 };
int val = 83;
auto res = find(std::begin(ia), std::end(ia), val);
auto res2 = find(std::begin(ia), std::end(ia), 111);
res2--; // 83
通过标准库begin和end函数,获取指向ia的首元素和尾元素之后位置的指针。
当要在序列的子范围中查找时,只要将指向子范围首元素和尾元素之后位置的迭代器(指针)传递给find。
int ia[] = { 27, 210, 12, 47, 1, 109, 83 };
int val = 1;
// 从ia[1]、ia[2]和ia[3]中查找给定元素,查不到时返回ia[4],
auto res = find(ia+1, ia+4, val);
在 C++ 中,可以通过标准库提供的工具或者运算符对迭代器进行移动,以调整迭代器的位置。
- 随机访问容器:使用
+
和-
操作符。也可使用双向迭代器的方式。 - 双向迭代器:使用
std::advance
、std::next
、std::prev
。 - 单向迭代器:仅能使用
std::advance
、std::next
。不支std::prev
和-
运算符(因为单向迭代器无法向后移动)。
list<string>vec = { "a", "b", "c", "a value", "1", "d" };
string val = "a value";
auto itStart = vec.cbegin();
std::advance(itStart, 2); // 原地操作
itStart = std::next(itStart, 1); // next返回新值
itStart = std::prev(itStart, 1);
auto res = find(itStart, vec.cend(), "a");
vector<string>vec2 = { "a", "b", "c", "a value", "1", "d" };
auto itStart2 = vec2.begin();
itStart2 += 2;
std::advance(itStart2, 2);
itStart2 = std::next(itStart2, 1);
itStart2 = std::prev(itStart2, 1);
std::forward_list<int> flist = { 1, 2, 3, 4, 5 };
auto it = flist.begin();
std::advance(it, 3);
//std::advance(it, -1); // error
//it = std::prev(it, 1); // error
迭代器令算法不依赖于容器,但算法依赖于元素类型的操作。算法永远不会执行容器的操作,算法永远不会改变底层容器的大小,可能会改变容器中保存的元素的值,也可能在容器内移动元素,但永远不会直接添加或删除元素。
vector<int>vec = { 1, 1, 2, 3, 2, 4, 2, 5 };
int ival = 2;
auto count = std::count(vec.cbegin(), vec.cend(), ival);
list<string>vlst = { "a", "c", "a", "d", "d", "i"};
string sval = "d";
auto scount = std::count(vlst.cbegin(), vlst.cend(), sval);
泛型算法
除少数例外,标准库算法都对一个范围内的元素进行操作。第一个元素与尾元素之后位置的迭代器。
只读算法
只读算法只读取其输入范围内的元素,而从不改变元素。通常使用cbegin()和cend(),但如果要使用算法返回的迭代器来改变元素的值,则需要使用begin()和end()的结果作为参数。
find、count、accumulate和equal都是只读算法。
#include <numeric>
vector<int>vec = { 1, 1, 2, 3, 2, 4, 2, 5 };
int initVal = 10;
auto sum = std::accumulate(vec.cbegin(), vec.cend(), initVal);
第三个参数的类型决定了函数中使用哪个加法运算符以及返回值的类型。序列中元素的类型必须与第三个参数匹配,或者能转换成第三个参数的类型。如果vector的元素类型为double,而参数3为int,会将double都转为int。
string定义了+运算符,使用string元素时:
list<string>vlst = { "a", "c", "a", "d", "d", "i"};
string sval = "zhu";
auto sum2 = std::accumulate(vlst.cbegin(), vlst.cend(), sval);
// zhuacaddi
const char * 上没有定义+运算符,使用会得到编译时错误:
list<string>vlst = { "a", "c", "a", "d", "d", "i"};
// auto sum2 = std::accumulate(vlst.cbegin(), vlst.cend(), ""); // error
equal用于确定两序列是否保存相同的值。前两个参数表示第一个序列中的元素范围,第三个表示第二个序列的首元素。
vector<int>vec = { 1, 1, 2, 3, 2, 4, 2, 5 };
vector<int>vec2 = { 1, 1, 2, 3, 2, 4, 2, 5, 2};
auto a1 = std::equal(vec.cbegin(), vec.cend(), vec2.cbegin());// true
vector<int>vec3 = { 1, 1, 2, 3, 2};
// auto a2 = std::equal(vec.cbegin(), vec.cend(), vec3.cbegin()); // error
vector<int>vec4 = { 1, 1, 2, 3, 2, 4, 2, 6 };
auto a3 = std::equal(vec.cbegin(), vec.cend(), vec4.cbegin());
只接受一个单一迭代器来表示第二个序列的算法,都假定第二个序列至少与第一个序列一样长。
写容器元素的算法
算法永远不会改变底层容器的大小,故使用写容器元素的算法时,序列的大小不可小于算法写入的元素数目。
vector<double>vec = { 1.5, 1, 2, 3, 2, 4, 2, 5 };
std::fill(vec.begin(), vec.end(), 0); //将每个元素重置为0
std::fill(vec.begin(), vec.begin()+vec.size()/2, 10);//将一个子序列设为10
算法不检查写操作,目的位置需能容纳要写入的元素。
vector<int>vec;
std::fill_n(vec.begin(), vec.size(), 10); // ok
// std::fill_n(vec.begin(), 10, 10); // 当容器为空时,会error。
使用插入迭代器,可解决上述问题。通常情况,通过迭代器向容器元素赋值时,值被赋予迭代器指向的元素,而使用插入迭代器赋值时,一个与赋值号右侧值相等的元素被添加到容器中(插入迭代器改变了容器大小)。
#include <iterator>
vector<int>vec;
auto it = std::back_inserter(vec); //通过它赋值会将元素添加到vec中
*it = 42; // vec中有一个元素,42
std::fill_n(std::back_inserter(vec), 10, 0); // vec中有11个元素
拷贝算法
算法接受三个迭代器,第二个序列至少与输入序列一样多。
int a1[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int a2[sizeof(a1) / sizeof(*a1)]; // a2与a1大小一样, sizeof(a1) 总大小, sizeof(*a1)首元素大小
auto ret = std::copy(std::begin(a1), std::end(a1), a2); // 把a1的内容拷贝给a2
copy返回其目标位置迭代器(递增后)的值,指向拷贝到a2的尾元素之后的位置。
replace算法:
int a1[] = { 0, 1, 2, 5, 5, 5, 6, 7, 8, 9 };
std::replace(std::begin(a1), std::end(a1), 5, 55); // 修改原序列
vector<int> a2;
std::replace_copy(std::begin(a1), std::end(a1), std::back_inserter(a2), 55, 5); // 使用插入迭代器,将结果存到新位置,不改变输入
消除重复单词
string a1[] = { "the", "quick", "red", "fox", "jumps", "over", "the", "slow", "red", "turtle"};
vector<string> words(std::begin(a1), std::end(a1));
// 按字符序排序 words, 以便查找重复字典
std::sort(words.begin(), words.end());
// unique重排输入范围,使每个字符出现一个
// 排列在范围的前部,返回指向不重复区域之后一个位置的迭代器
auto end_unique = std::unique(words.begin(), words.end());
words.erase(end_unique, words.end());
unique算法会重排序列,并返回一个指向不重复值范围末尾的迭代器。然后使用容器的删除操作,从end_unique至words末尾的所有元素全部删除。erase当两个参数相同时为空范围,所以也不会出问题。
定制操作
重载sort算法的默认行为,使其先按长度排序,在按字典序排序。使用 std::stable_sorts 算法,算法第三个参数接受一个谓词(predicate),标准库所使用的谓词分两类:一元谓词(接受单一参数)和二元谓词(有两个参数),元素类型必须能转化为谓语的参数类型。
std::stable_sort(words.begin(), words.end(), function)
lambda表达式
未命名的内联函数
string a1[] = { "the", "quick", "red", "fox", "jumps", "over", "the", "slow", "red", "turtle"};
vector<string> words(std::begin(a1), std::end(a1));
std::sort(words.begin(), words.end());
auto end_unique = std::unique(words.begin(), words.end());
words.erase(end_unique, words.end());
std::stable_sort(words.begin(), words.end(), [](const string& s1, const string& s2) {return s1.size() < s2.size(); });
定制for_each
std::for_each(words.begin(), words.end(), [](const string& s) {std::cout << s << " "; });
向lambda传参
lambda表达式可出现在函数中,使用其局部变量,但只能使用明确指明的变量。
[sz](const string &a){return a.size() >= sz; };
lambda捕获与返回
值传递与引用传递,当以引用方式捕获变量一个变量时,必须保证在lambda执行时变量是存在的。
size_t v1 = 42; // 局部变量
// 将v1拷贝到名为f的可调用对象
auto f = [v1] {return v1; };
// 对象f2包含v1的引用
auto f2 = [&v1] {return v1; };
v1 = 0;
auto j = f(); // 42
auto j2 = f2(); // 0
隐式捕获,在捕获列表中使用&为捕获引用,=则表示值捕获方式。隐式捕获
size_t v1 = 10; // 局部变量
size_t v2 = 20;
auto f1 = [&] {return v1 > v2; };
auto f2 = [&, v2] {return v1 > v2; };
bool flag = f1(); // false
v2 = 0;
flag = f1(); // true
flag = f2(); // false
改变局部变量的值
值传递可通过关键字 mutable 修改变量的值,非const变量的引用可直接通过修改引用改变。
size_t v1 = 42;
auto f = [=] () mutable {return ++v1; };
// 对象f2包含v1的引用
auto f2 = [&] { return ++v1; };
v1 = 0;
auto j = f(); // 43
auto j2 = f2(); // 1
指定lambda返回类型
默认情况下,若lambda表达式包含return之外的任何语句,编译器会假定返回void。 当需要为lambda定义返回类型时,必须使用尾置返回类型。
auto f = [](int i) -> int { if (i < 0) return -i; else return i; };
auto j = f(-4); // 4
文章评论