标准库为我们提供了容器,同时为开发者提供了许多算法,这些算法中大多数都独立于任何特定的容器,也就是说这些算法是通用的(generic,或者称为泛型的):它们可用于不同类型的容器和不同类型的元素,本章主要学习泛型算法与对迭代器更加深入的讨论
大多数算法在头文件algorithm
,numeric
中定义了一组数值泛型算法
下面简单使用 find 算法
//example1.cpp
<int> vec = {1, 2, 3, 4, 5};
vectorauto target = find(vec.begin(), vec.end(), 3);
if (target != vec.cend())
{
<< "finded " << *target << endl; // finded 3
cout }
//泛型算法是一种通用算法
= "abcde";
string str auto ch = find(begin(str), end(str), 'c');
if (ch != str.cend())
{
<< "fined " << *ch << endl; // fined c
cout }
可见迭代器令算法不依赖于容器、不同的容器可以使用相同的算法
算法依赖于元素类型的操作,如 find
就依赖于元素类型间的==操作,其他算法可依赖>、<等等
关键概念:算法永远不会执行容器的操作,因为这样就会对特定容器产生依赖,不是真正的泛型算法,它们只会运行在迭代器上
标准库提供了超过 100 个算法,虽然很多但它们大部分有类似的接口
只读算法就是像 find 一样,使用算法时只读取数据而不进行修改数据的操作
其在声明在 numeric 头文件内
//example2.cpp
<int> vec = {1, 2, 3, 4, 5};
vectorint sum = accumulate(vec.begin(), vec.end(), 0);//求和初始值为0
<< sum << endl; // 15 cout
accumulate 算法和元素类型,因为 string
之间定义了+
运算
//example2.cpp
<string> vec1 = {"hello", "ni"};
vector= accumulate(vec1.begin(), vec1.end(), string(""));
string str << str << endl; // helloni cout
确定两个序列中是否保存着相同的值,返回布尔类型值
//example3.cpp
<int> seq1 = {1, 2, 3, 4, 5};
vector<int> seq2 = {1, 2, 3, 4, 5};
list<int> seq3 = {2, 3, 4, 5, 6};
vector// 1
<< equal(seq1.begin(), seq1.end(), seq2.begin(), seq2.end()) << endl;
cout // 0
<< equal(seq2.begin(), seq2.end(), seq3.begin(), seq3.end()) << endl; cout
将新值赋予序列中的元素,使用这类算法要注意各种迭代器范围以及长度大小不能超过容器原大小
fill(begin,end,value) [begin,end) 赋值为 value
//example4.cpp
<int> vec(10, 999);
vector(vec); // 999 999 999 999 999 999 999 999 999 999
printVec(vec.begin(), vec.end(), 8);
fill(vec); // 8 8 8 8 8 8 8 8 8 8
printVec(vec.begin() + vec.size() / 2, vec.end(), 666);
fill(vec);
printVec// 8 8 8 8 8 666 666 666 666 666
fill(begin,n,value) 从 begin 开始 n 个位置赋值为 value
//example5.cpp
<int> vec1 = {1, 2, 3, 4, 5};
vector(vec1.begin() + 1, 4, 666);
fill_n(vec1); // 1 666 666 666 666 printVec
back_inserter 是定义在头文件 iterator 中的一个函数,插入迭代器是一种向容器中添加元素的迭代器,通常情况下通过迭代器向元素赋值时,值被赋予迭代器指向的元素
//example6.cpp
<int> vec = {1, 2, 3};
vectorstd::back_insert_iterator<std::vector<int>> iter = back_inserter(vec);
*iter = 4;
(vec); // 1 2 3 4
printVec*iter = 5;
(vec); // 1 2 3 4 5
printVec//配和fill_n
(iter, 3, 666);
fill_n(vec); // 1 2 3 4 5 666 666 666 printVec
主要有 copy、replace、replace_copy 等
//example7.cpp
void print(int (&vec)[7])
{
auto iter = begin(vec);
while (iter != end(vec))
{
<< *iter << " "; // 0 1 2 3 4 5
cout ++;
iter}
<< endl;
cout }
int main(int argc, char **argv)
{
int a1[] = {0, 1, 2, 3, 4, 5};
int a2[sizeof(a1) / sizeof(*a1) + 1];
int *ret = copy(begin(a1), end(a1), a2);
// ret指向拷贝到a2的尾元素之后的位置
*ret = 999;
(a2); // 0 1 2 3 4 5 999
printreturn 0;
}
将指定范围内的为目标元素的值进行替换
//example7.cpp
(a2); // 0 1 2 3 4 5 999
print//将[begin,end)内为值3的元素替换为333
(begin(a2), end(a2), 3, 333);
replace(a2); // 0 1 2 333 4 5 999 print
replace_copy 为也是 replace,只不过不再原序列内修改,而是修改后添加到新的序列中
//example8.cpp
<int> vec1 = {};
vector<int> vec2 = {3, 4, 5, 4, 5, 3, 2};
vector// replace到vec1序列中
(vec2.cbegin(), vec2.cend(), back_inserter(vec1), 4, 444);
replace_copyfor (auto &item : vec1)
{
<< item << " "; // 3 444 5 444 5 3 2
cout }
<< endl; cout
此时 vec2 并没有被改变,vec1 包含 vec2 的一份拷贝,然后进行了 replace
最常用的就是 sort 进行对数字序列进行排序
用于消除重复项
//example9.cpp
<int> vec = {1, 2, 3, 4, 1, 2, 3, 4};
vector//排序
(vec.begin(), vec.end());
sort(vec); // 1 1 2 2 3 3 4 4
printVec//使用unique重新排列返回夫重复序列的下一个位置
auto end_unique = unique(vec.begin(), vec.end());
auto iter = vec.begin();
while (iter != end_unique)
{
<< *iter << " "; // 1 2 3 4
cout ++;
iter}
<< endl;
cout //使用erase删除重复项
.erase(end_unique, vec.end());
vec(vec); // 1 2 3 4 printVec
如果有学过 java 或者 javascript,都知道 java 有一种定义接口对其实现内部类或者使用 lambda 表达式,javascript 则是传递函数或者使用箭头函数,比如它们中的 sort 都提供了函数传递的机制,在 C++中也是有这种功能的
//example10.cpp
//将s1想象为前面的元素s2为后面的,像排序后满足怎样的性质,就return什么
//此处为后面的长度大于前面的长度
bool shortCompare(const string &s1, const string &s2)
{
return s1.length() < s2.length();
}
int main(int argc, char **argv)
{
<string> vec = {"dscss", "aaaaaa", "ll"};
vector(vec); // dscss aaaaaa ll
printVec(vec.begin(), vec.end(), shortCompare);
sort(vec); // ll dscss aaaaaa
printVecreturn 0;
}
不知道排序算法的稳定性?好好去学下数据结构吧!
//example10.cpp
//stable_sort
= {"dscss", "aaaaaa", "ll"};
vec (vec); // dscss aaaaaa ll
printVec(vec.begin(), vec.end(), shortCompare);
stable_sort(vec); // ll dscss aaaaaa printVec
形式
[capture list](parameter list) specifiers exception->return type{function body}
//example11.cpp
auto f1 = []() -> int{ return 42; };
auto f2 = []{ return 42; };
<< f1() << endl; // 42
cout << f2() << endl; // 42 cout
//example12.cpp
<string> vec = {"sdcs", "nvfkdl", "acndf"};
vector(vec); // sdcs nvfkdl acndf
printVec(vec.begin(), vec.end(), [](const string &s1, const string &s2) -> bool
sort{ return s1.length() < s2.length(); });
(vec); // sdcs acndf nvfkdl printVec
在 javascript 中因为有 this 的指向,在箭头函数内部可以使用定义它时外部的作用域的变量,C++也能实现功能,就是使用捕获列表。
捕获列表的变量必须是一个自动存储类型,Lambda 表达式中的捕获列表只能捕获局部变量,而不能捕获全局变量或静态变量。
//example13.cpp
int count = 0, sum = 1, k = 100;
auto f = [count, sum]()
{
<< count << " " << sum << endl;
cout // count = 3;//error count readonly
// cout << k << endl; // error: 'k' is not captured
};
(); // 0 1
f++;
count(); // 0 1 可见捕获列表只是只拷贝 f
在 lambda 中使用全局变量与静态变量
#include <iostream>
using namespace std;
int x = 0;
static int j = 0;
int main(int argc, char **argv)
{
int y = 0;
static int z = 0;
auto fun = [x, y, z, j] {};
// x z j都无法捕获
return 0;
}
//============修改后===========
#include <iostream>
using namespace std;
int x = 0;
static int j = 0;
int main(int argc, char **argv)
{
int y = 0;
static int z = 0;
auto fun = [y]() -> int
{
return x * y * z * j;
};
return 0;
}
查找第一个满足条件的元素
//example14.cpp
<int> vec = {1, 2, 3, 7, 5, 6};
vectorauto target = find_if(vec.begin(), vec.end(), [](const int &item) -> bool
{ return item >= 6; });
if (target != vec.end()) //找到了满足要求的元素的位置的迭代器
{
<< *target << endl; // 7
cout }
遍历算法
//example15.cpp
<int> vec = {1, 2, 3, 4, 5, 6};
vector(vec.begin(), vec.end(), [](int &item) -> void
for_each{ cout << item << " ";item++; });
<< endl; // 1 2 3 4 5 6
cout
(vec.begin(), vec.end(), [](int &item) -> void
for_each{ cout << item << " "; });
<< endl; // 2 3 4 5 6 7 cout
捕获分为值捕获与引用捕获
#include <iostream>
using namespace std;
int main(int argc, char **argv)
{
int count = 0;
// 值捕获 也就是值拷贝到捕获列表的变量
auto f1 = [count]()
{
<< count << endl;
cout // count = 999; readonly
};
// 引用捕获
auto f2 = [&count]() -> void
{
<< count << endl;
cout ++;
count};
= 99;
count (); // 0
f1// 可见值捕获的count在lambda定义后,哪怕外部修改了变量值,也不会影响到lambda捕获到的
// 定义即捕获,捕获即确定
= 100;
count (); // 100
f2<< count << endl; // 101
cout // 编译器自动推断 都使用引用型
auto f3 = [&]() -> void
{
= 666;
count };
();
f3<< count << endl; // 666
cout return 0;
}
但是最新捕获列表还支持
#include <iostream>
using namespace std;
class X
{
public:
() : x(0) {}
Xvoid print()
{
<< "class X.x=" << x << endl;
cout }
void test_this()
{
auto fun = [this]
{
this->print();
= 5;
x ();
print};
();
fun}
void test_val()
{
auto fun = [=]
{
this->print();
= 6;
x ();
print};
();
fun}
void test_ref()
{
auto fun = [&]
{
this->print();
= 7;
x ();
print};
();
fun}
private:
int x;
};
int main(int argc, char **argv)
{
;
X x.test_this();
x.test_val();
x.test_ref();
xreturn 0;
}
/*
class X.x=0
class X.x=5
class X.x=5
class X.x=6
class X.x=6
class X.x=7
*/
刚才我们发现在 lambda 表达式函数体内部不能修改值捕获的变量的值,使用 mutable 使得值捕获的值可以改变
//example17.cpp
int i, j, k;
= j = k = 0;
i auto f1 = [i, j, k]() mutable -> void
{
= j = k = 999;
i << i << " " << j << " " << k << endl; // 999 999 999
cout };
();
f1<< i << " " << j << " " << k << endl; // 0 0 0 cout
C++的 lambda 表达式和仿函数非常像
#include <iostream>
using namespace std;
class X
{
public:
(int x, int y) : x(x), y(y)
X{
}
int operator()()
{
return x * y;
}
private:
int x, y;
};
int main(int argc, char **argv)
{
int x = 1, y = 2;
(x, y);
X func<< func() << endl; // 2
cout auto m_lambda = [=]() -> int
{
return x * y;
};
<< m_lambda() << endl; // 2
cout return 0;
}
m_lambda 是一个 lambda 表达式,func 为一个函数对象,二者都可以在初始化的时候获取 main 函数中变量 x,y 的值,并在调用之后返回相同结果。二者明显不同之处有。
重点:lambda 表达式在编译时会自动生成一个闭包类,在运行时由这个闭包类产生一个对象,称为闭包。C++中的闭包可以理解为一个匿名且可以包含定义时作用域上下文的函数对象。lambda 表达式是 C++11 的语法糖,lambda 表达式的功能完全可以手动实现。
在 C++中,无状态(lambda)表达式是一种特殊类型的 lambda 表达式,它不捕获任何外部变量。它仅依赖于其参数列表中的参数,并且在执行时不依赖于任何额外的上下文。因此,它被称为”无状态”,因为它不存储或访问任何状态信息。
无状态 lambda 表达式可以隐式转换为函数指针
,如
#include <iostream>
using namespace std;
void func(void (*ptr)())
{
();
ptr}
int main(int argc, char **argv)
{
([]
func{ cout << "hello world" << endl; });
// hello world
return 0;
}
C++14 及以上版本支持,支持在捕获列表中自定义新的变量
g++ main.cpp -o main -std=c++17
#include <iostream>
#include <string>
using namespace std;
class X
{
public:
int x{1};
int y{2};
public:
void func1()
{
//*this并不是广义捕获
auto lambda = [*this]() mutable
{
<< x << endl;
cout << y << endl;
cout ++;
x++; // 副本
y};
();
lambda<< x << " " << y << endl;
cout }
void func2()
{
//&包含捕获this
auto lambda = [&]() mutable
{
<< this->x << endl;
cout << this->y << endl;
cout this->x = 666;
this->y = 666; // 引用
};
();
lambda<< x << " " << y << endl;
cout }
};
int main(int argc, char **argv)
{
int x = 42;
// 广义捕获
// 通过复制或移动语义捕获外部变量
auto lambda1 = [x = x + 1]()
{
<< x << endl;
cout };
(); // 43
lambda1<< x << endl; // 42
cout = "hello world";
string str auto lambda2 = [str = std::move(str)]
{
<< str << endl;
cout };
(); // hello world
lambda2<< str.size() << endl; // 0
cout // 在成员函数的lambda中可以使用*this捕获当前对象的引用
;
X xobj.func1(); // 1 2 1 2
xobj.func2(); // 1 2 666 666
xobjreturn 0;
}
C++14 支持让 lambda 表达式具备模板函数的能力,称为泛型 lambda 表达式
auto 型
#include <iostream>
#include <string>
using namespace std;
int main(int argc, char **argv)
{
auto m_lambda = [](auto obj)
{
return obj;
};
int n = m_lambda(1);
= "hello world";
string str = m_lambda(str);
string str_cpy << n << " " << str_cpy << endl;
cout return 0;
}
// 1 hello world
模板语法型 lambda 表达式 C++20
#include <iostream>
#include <vector>
using namespace std;
int main(int argc, char **argv)
{
auto f = []<typename T>(T t1, T t2)
{
= t1 + t2;
T t3 return t3;
};
<< f(1, 2) << endl; // 3
cout << f(5, 6) << endl; // 11
cout return 0;
}
C++20 引入了可构造可赋值的无状态 lambda(Constructible and Assignable Stateless Lambdas),这意味着我们可以声明和定义一个无状态的 lambda,并将其赋值给变量或将其作为函数参数传递,以便在代码中进行重复使用。
在 C++20 之前,lambda 表达式默认是不可构造和不可赋值的,因为它们的类型是匿名的,并且没有默认构造函数或赋值运算符。但是在 C++20 中,如果 lambda 表达式没有捕获任何变量(即无状态),则它可以被构造和赋值。
需要注意的是,如果 lambda 捕获了任何变量,它将不再是无状态的,因此不满足可构造和可赋值的条件。
#include <iostream>
using namespace std;
template <typename T>
auto func(T lambda) -> void
{
();
lambda}
int main(int argc, char **argv)
{
auto lambda = []()
{
<< "hello world" << endl;
cout };
(); // hello world
lambdadecltype(lambda) lambda_ref = lambda;
auto lambda_ref1 = lambda;
(); // hello world
lambda_ref(); // hello world
lambda_ref1auto lambda1 = [lambda]()
{
();
lambda};
(); // hello world
lambda1decltype(lambda1) lambda1_ref = lambda1;
(); // hello world
lambda1_refint a = 666;
auto lambda2 = [&a]()
{
<< a << endl;
cout };
(lambda2); // 666
funcreturn 0;
}
//有状态怎么也可以构造和赋值,不知道阿
/*Chatgpt
有状态的lambda表达式之所以可以构造和赋值,
是因为使用了auto关键字和自动类型推导。
auto关键字可以根据右侧表达式的类型进行类型推导,
并创建相应的lambda表达式副本。
这使得我们可以在代码中使用auto声明和赋值有状态的lambda表达式,
而不需要显式指定其类型。
*/
从C++17开始,lambda表达式在条件允许的情况下都会隐式声明constexpr
#include <iostream>
using namespace std;
constexpr int foo()
{
return []() -> int
{ return 58; }();
}
// auto : class lambda [](int i)->int
auto get_size = [](int i)
{ return i * 2; };
char buffer1[foo()] = {0};
char buffer2[get_size(5)] = {0};
int main(int argc, char **argv)
{
return 0;
}
实际上这里[](int i){return i*2;}
相当于
class GetSize
{
public:
constexpr int operator()(int i) const
{
return i * 2;
}
};
当lambda表达式不满足constexpr条件时,lambda表达式也不会编译错误,会作为运行时lambda表达式存在即退化了
#include <iostream>
using namespace std;
int i = 5;
auto get_size = [](int i)
{ return i * 2; };
// char buffer1[get_size(i)] = {0};//编译错误i运行时 所以get_size也是运行时
int a1 = get_size(i); // 退化
auto get_count = []()
{
static int x = 5;
return x;
};
int a2 = get_count(); // 运行时 因为内部static x运行时才会初始化
int main(int argc, char **argv)
{
return 0;
}
可以强制要求lambda表达式是一个常量表达式,用constexpr声明即可
#include <iostream>
using namespace std;
auto get_size = [](int i) constexpr -> int
{ return i * 2; };
char buffer2[get_size(5)] = {0};
// 编译错误 x是一个static变量
// auto get_count = []() constexpr -> int
// {
// static int x = 5; // error:constexpr 函数中的变量不具有自动存储持续时间
// return x;
// }
int main(int argc, char **argv)
{
return 0;
}
将所在位置修改为 lambda 表达式返回的内容
前两个参数为遍历的范围,第三个参数为将 transform 后的值从哪里开始存储
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
void printVec(const vector<int> &vec)
{
for (auto &item : vec)
{
<< item << " ";
cout }
<< endl;
cout }
int main(int argc, char **argv)
{
<int> vec{1, 2, 3, 4, 5};
vector(vec.begin(), vec.end(), vec.begin(),
transform[](int item) -> int
{
if (item >= 4)
{
return 666;
}
return item;
});
(vec); // 1 2 3 666 666
printVecreturn 0;
}
统计满足条件的元素个数
//example18.cpp
(vec); // 1 2 3 666 666
printVec// count_if
int sum = count_if(vec.begin(), vec.end(), [](int &item) -> bool
{ return item >= 666; });
<< sum << endl; // 2 cout
对于 lambda 表达式,如果一个 lammbda 的捕获列表为空,且经常使用,如 sort 算法提供的比较函数,这事往往提供函数比提供 lambda 表达式开发起来更为方便
bind 函数接受一个可调用对象,返回一个新的可调用对象
newCallable=bind(callable,arg_list)
callable
为可调用对象,arg_list 是逗号分隔的参数列表,当调用 newCallable
时,newCallable 会调用 callable
//example19.cpp
int big(const int &a, const int &b)
{
return a > b ? a : b;
}
int main(int argc, char **argv)
{
int a = 1, b = 2;
auto big_func = bind(big, a, b);
<< big_func() << endl; // 2
cout return 0;
}
用于 bind 传递参数列表时,保留与跳过特定的参数
//example20.cpp
bool check(string &str, string::size_type size)
{
return str.size() >= size;
}
int main(int argc, char **argv)
{
<string> vec = {"vd", "fdvd", "vfdvgfbf", "fvddv"};
vectorauto iter = find_if(vec.begin(), vec.end(), bind(check, placeholders::_1, 6));
// bind返回一个以string&str为参数且返回bool类型的可执行对象,且调用check时传递给size的实参为6
<< *iter << endl; // vfdvgfbf
cout return 0;
}
总之 bind 的参数列表参数是与 callable 参数一一对应的,且用 placeholders 使用 newCallable 的形参参数作为其调用 callable 时的实参
//example21.cpp
void func(int a, int b, int c, int d)
{
<< "a " << a << endl;
cout << "b " << b << endl;
cout << "c " << c << endl;
cout << "d " << d << endl;
cout }
int main(int argc, char **argv)
{
int a = 1, b = 2;
auto func1 = bind(func, a, placeholders::_1, b, placeholders::_2);
// a1 b3 c2 d4
(3, 4); // func(1,_1,2,_2) 即 func(1,3,2,4)
func1//调换placeholders
auto func2 = bind(func, a, placeholders::_2, b, placeholders::_1);
(3, 4); // a1 b4 c2 d3
func2return 0;
}
其实就是在 bind 中使用引用捕获,默认 bind
参数列表的值绑定是拷贝而不是引用,要实现引用参数的绑定则要使用ref函数
,如果并不改变其值,可以使用cref函数
绑定
const 类型的引用
如果 bind 的 callable 的参数有引用变量参数,bind
的参数列表是不能直接进行绑定的
//example22.cpp
void func(int &a, int &b, int &c, int &d)
{
<< "a " << a << endl;
cout << "b " << b << endl;
cout << "c " << c << endl;
cout << "d " << d << endl;
cout }
int main(int argc, char **argv)
{
int a = 1, b = 2, c = 3, d = 4;
auto func1 = bind(func, a, placeholders::_1, b, placeholders::_2);
(c, d);
func1= 666, b = 666;
a (c, d); //并不会输出666
func1//为什么因为其使用了拷贝,而不是绑定引用
auto func2 = bind(func, ref(a), placeholders::_1, ref(b), placeholders::_2);
= 777, b = 777;
a (c, d); //可以使用实时的a与b的值
func2return 0;
}
除了我们知道的容器迭代器之外,还有几个特殊的迭代器
插入迭代器(insert iterator)
:这些迭代器被绑定到一个容器上,可用来向容器插入元素流迭代器(stream iterator)
:被绑定在输入或输出流上,可用来遍历所关联的
IO 流反向迭代器(reverse iterator)
:这些迭代器向后而不是向前移动,除了
forward_list 之外的标准容器都有反向迭代器移动迭代器(move iterator)
:这些专用的迭代器不是拷贝其中的元素,而是移动它们插入迭代器有三种
//example23.cpp
<int> vec = {1, 2, 3};
vector// back_inserter
auto back = back_inserter(vec);
*back = 4;
(vec); // 1 2 3 4
printVec<int> m_list = {1, 2, 3};
list// front_inserter
auto front = front_inserter(m_list); // vector没有push_front操作
*front = 0;
(m_list); // 0 1 2 3
printVec// insert
auto inser = inserter(m_list, m_list.end());
*inser = 4;
(m_list); // 0 1 2 3 4 printVec
泛型算法与迭代器的配和
//example24.cpp
//泛型算法与迭代器配和
<int> vec1;
vector(vec.begin(), vec.end(), back_inserter(vec1));
copy(vec1); // 1 2 3 4
printVec
<int> m_list1;
list(vec.begin(), vec.end(), front_inserter(m_list1));
copy(m_list1); // 4 3 2 1 printVec
iostream 虽然不是容器,但标准库定义了相关迭代器,istream_iterator 读取输入流、ostream_iterator 像一个输出流写数据
//example24.cpp
<int> int_iter(cin);
istream_iterator<int> eof;
istream_iterator<int> vec;
vectorwhile (int_iter != eof)
{
.push_back(*int_iter);
vec++;
int_iter}
(vec);
printVec// input 1 2 3 4 5 g
// output 1 2 3 4 5
return 0;
使用迭代器范围初始化 vector 以及利用迭代器范围使用泛型算法
//example26.cpp
<int> eof;
istream_iterator<int> int_iter1(cin);
istream_iteratorint sum = accumulate(int_iter1, eof, 0);
<< sum << endl;
cout // input 1 2 3 4 5 g
// output 15
//example27.cpp
<string> out(cout);
ostream_iterator= "hello1";
out = "hello2";
out = "hello3";
out = "hello4"; // hello1hello2hello3hello4
out << endl;
cout <int> out1(cout, "\n");
ostream_iterator= 1; // 1\n
out1 = 2; // 2\n
out1
<int> vec = {1, 2, 3, 4};
vector(vec.begin(), vec.end(), out1); // 1\n2\n3\n4 copy
反向迭代器就是++移动到上一个元素,–移动到下一个元素,除了 forward_list 之外,其他的容器都支持反向迭代器,可以通过调用 rbegin、rend、crbegin、crend 成员函数获得反向迭代器
//example28.cpp
<int> vec = {1, 2, 3, 4, 5};
vector//倒序遍历
auto iter = vec.rbegin();
while (iter != vec.crend())
{
<< *iter << " ";
cout ++;
iter}
<< endl;
cout //正序遍历
= vec.rend();
iter while (true)
{
--;
iter<< *iter << " "; // 1 2 3 4 5
cout if (iter == vec.crbegin())
{
break;
}
}
<< endl; cout
重点:关键点在于[str.crbegin(),rcomma)
的范围与[rcomma.base(),str.cend())
的范围相同,所以.base()是返回反向迭代器的下一个位置的普通迭代器
看一个有趣的例子
//example29.cpp
= "hi,hello,world";
string str auto iter = find(str.cbegin(), str.cend(), ',');
if (iter != str.end())
{
<< string(str.cbegin(), iter) << endl; // hi
cout }
//如果找最后一个单词呢
std::string::const_reverse_iterator target = find(str.crbegin(), str.crend(), ',');
if (target != str.crend())
{
<< *target << endl; //,
cout << *target.base() << endl; // w
cout << string(str.crbegin(), target) << endl; // dlrow
cout //调用reverse_iterator.base()获得普通迭代器
<< string(target.base(), str.cend()) << endl; // world
cout }
与容器类似,一些操作所有迭代器都支持,另外一些只有特定类别的迭代器才支持,如 ostream_iterator 只支持递增、解引用、赋值,vector、string、deque 的迭代器另外还支持递减、关系、算数运算
用于读取序列中的元素,输入迭代器必须支持
1、用于比较两个迭代器是否相等和不相等(==、!=)
2、用于推进迭代器的前置和后置递增运算(++)
3、用于读取元素的解引用运算符(*),解引用只会出现在赋值运算符的右侧
4、箭头运算符,->,等价于(*iter).member
输入迭代器只能用于单遍扫描算法,算法 find 和 accumulate 要求输入迭代器,而 istream_iterator 是一种输入迭代器
输入迭代器的补集,只写不读
1、用于推进迭代器的前置和后置递增运算(++)
2、解引用运算符(*),只出现在赋值运算符的左侧(也就是只能读取元素,不能写)
输出迭代器只能用于单遍扫描算法,例如 copy 函数的第三个参数就是输出迭代器,ostream_iterator 类型也是输出迭代器
可以读写元素,只能沿序列一个方向移动,支持所有输入和输出迭代器操作,可以多次读写同一个元素,算法 replace 要求前向迭代器,forward_list 上的迭代器是前向迭代器
可以正向/反向读写序列元素,支持所有前向迭代器之外,还支持递减运算符(–),算法 reverse 要求双向迭代器,除 forward_list 之外,能提供双向迭代器
1、用于比较两个迭代器相对位置的关系运算符(<、<=、>和>=)
2、迭代器和一个整数值的加减运算(+、+=、-、-=),计算结果是迭代器在序列中前进或后退给定整数个元素的位置
3、用于两个迭代器上的减法运算符(-),得到两个迭代器的距离
4、下标运算符(iter[n]),与*(iter[n])等价
算法 sort 要求随机访问迭代器,array、deque、string、vector 的迭代器都是随机访问迭代器,用于访问内置数组元素的指针也是
(beg,end,other,args);
alg(beg,end,dest,other,args);
alg(beg,end,beg2,other,args);
alg(beg,end,beg2,end2,other,args); alg
alg
是算法的名字,end
表示算法所操作的输入范围,dest
、beg2
、end2
,都是迭代器参数,如果需要则承担指定目的位置和第二个范围的角色
dest 参数是一个表示算法可以写入的目的位置的迭代器,算法假设,按其需要写入数据,不管写入多少个元素都是安全的
如果算法接收
beg、end2,则两个迭代器表示第二个范围,[beg,end)、[beg2,end2)表示的第二个范围
只接受单独的 beg2,将 beg2
作为第二个输入范围中的首元素,此范围的结束位置未指定,这些算法假定从
beg2 开始的范围与 beg、end 表示的范围至少一样大,如 equal 函数
如 unque 的使用
//example30.cpp
<int> vec = {1, 2, 3, 3, 4, 4};
vectorauto iter = unique(vec.begin(), vec.end());
auto beg = vec.begin();
while (beg != iter)
{
<< *beg << " "; // 1 2 3 4
cout ++;
beg}
<< endl;
cout //提供谓词
= {1, 2, 3, 3, 4, 4};
vec = unique(vec.begin(), vec.end(), [](int &a, int &b) -> bool
iter { return (a == b); });
= vec.begin();
beg while (beg != iter)
{
<< *beg << " "; // 1 2 3 4
cout ++;
beg}
<< endl; cout
find 函数第三个函数为 val,而 find_if 第三个参数为 pred 函数
//example31.cpp
struct Person
{
int age;
int height;
};
int main(int argc, char **argv)
{
<Person> vec;
vectorfor (int i = 0; i < 10; i++)
{
;
Person person.age = i;
person.height = i;
person.push_back(person);
vec}
// find_if
auto iter = find_if(vec.begin(), vec.end(), [](Person &item) -> bool
{ return (item.age == 3); });
if (iter != vec.end())
{
// height: 3 age: 3
<< "height: " << iter->height << " age: " << iter->age << endl;
cout }
return 0;
}
用于反转指定范围的元素
//example32.cpp
<int> vec = {1, 2, 3, 4, 5};
vector(vec.begin(), vec.end());
reverse(vec); // 5 4 3 2 1
printVec<int> vec_copy;
vector(vec.begin(), vec.end(), back_inserter(vec_copy));
reverse_copy(vec_copy); // 1 2 3 4 5 printVec
用于删除满足指定条件的元素
//example33.cpp
// remove_if
<int> vec = {1, 2, 3, 4, 5};
vectorauto iter = remove_if(vec.begin(), vec.end(), [](int &item) -> bool
{ return (item <= 2); }); //移除小于等于2的元素
auto beg = vec.begin();
while (beg != iter)
{
<< *beg << " "; // 3 4 5
cout ++;
beg}
<< endl;
cout // remove_copy_if
= {1, 2, 3, 4, 5};
vec <int> vec_copy;
vector(vec.begin(), vec.end(), back_inserter(vec_copy), [](int &item) -> bool{ return item <= 2; });
remove_copy_if(vec); // 1 2 3 4 5
printVec(vec_copy); // 3 4 5 printVec
学过数据结构知道的是,有些算法对于顺序容器非常好操作,对于非顺序结构则需做出特殊的处理操作,如 list 和 forward_list 就不能进行随机访问,它们拥有独特的 sort、merge、remove、reverse、unique。
例
//example34.cpp
// unique
<int> m_list = {1, 2, 3, 3, 3, 6, 6, 6, 6, 7};
list(m_list); // 1 2 3 3 3 6 6 6 6 7
printVecm_list.unique(); //删除同一个值得连续拷贝
(m_list); // 1 2 3 6 7
printVec// merge
<int> m_list1 = {1, 2, 3, 4, 5};
list<int> m_list2 = {1, 2, 3, 4};
listm_list1.merge(m_list2);
(m_list1); // 1 1 2 2 3 3 4 4 5
printVec(m_list2); // printVec
链表类型还定义了 splice 算法,用于将链表的一部分移动到另一个链表上去,此算法为链表数据结构特有的,并不是完全的泛型算法
//example35.cpp
// list用 splice
<int> m_list1 = {1, 2, 3, 4, 5};
list<int> m_list2 = {6, 7, 8, 9};
listm_list1.splice(++m_list1.begin(), m_list2); //在指定迭代器前添加
(m_list1); // 1 6 7 8 9 2 3 4 5
printVec(m_list2); //
printVec// forward_list用 splice_after
<int> m_forwardlist1 = {1, 2, 3, 4, 5};
forward_list<int> m_forwardlist2 = {6, 7, 8, 9};
forward_listm_forwardlist1.splice_after(++m_forwardlist1.begin(), m_forwardlist2); //在指定迭代器后添加
(m_forwardlist1); // 1 2 6 7 8 9 3 4 5
printVec(m_forwardlist2); // printVec