泛型算法
大多数算法都定义在头文件algoruthm中,标准库还在头文件numeric中定义了一组数值泛型算法。
-
只读算法
- accumulate:操作一个序列。定义于numeric中。使用那个运算符以及返回值由第三个参数决定。
string num=accumulate(v.cbegin(),v,cend(),string(""));//注意:第三个参数显示的创建了一个初始值为空的字符串 string num=accumulate(v.cbegin(),v,cend(),"");//错误:const char*上没有定义+运算符
- equal:操作两个序列。确保两个序列是否相同。相等返回true,反之返回false
eaual(v.cbegin(),v.cend(),v2,cbegin());//v2的元素数目应该至少与v一样多
那些接受一个单一迭代器来表示第二个序列的算法,都假定第二个序列至少与第一个序列一样长。
-
写容器元素算法-算法不检查写操作
这类算法必须保证序列原大小至少不小于我们要求算法写入的元素数目。算法不执行容器操作,因此它们自身不肯能改变容器的大小。
- fill:由于fill先给定输入序列中写入数据,因此只要我们传递了一个有效的输入序列,写入操作就是安全的。
fill(v.begin(),v.end(),0);//将每个元素重置为0 fill(v.begin(),v.begin()+v.size()/2,10);//将容器的一个子序列设置为10
- fill_n:接受一个迭代器来指定一个单独的位置。
- 调用形式:
fill(dest,n,val)
,dest开始的序列至少包含n个元素
- 调用形式:
vector<int> vec;//空vector fill_n(vec.begin(),vec.end(),0);//将所有元素重置为0 vector<int> vec;//空vector fill_n(vec.begin(),10,0);//严重错误:修改vec中的10个(不存在)的元素
- back_inserter:保证算法有足够空间来输出数据的方法是插入迭代器。接受一个指向容器的引用,返回一个与该容器绑定的插入迭代器。通过此迭代器赋值时,赋值运算符会调用push_back将一个具有给定值的元素添加到容器中。
vector<int> vec;//空向量 auto it=back_inserter(vec);//通过它赋值会将元素添加到vec中 *it = 42;//vec中现在有一个元素,值为42 vector<int> vec;//空 fill_n(back_inserter(vec),10,0);//正确:添加10个元素到vec末尾,每个元素值为0
- 拷贝算法copy:返回的目的位置迭代器(递增后)的值。
int a1[]={1,2,3,4,5}; int a2[sizeof(a1)/sizeof(*a1)];//a1和a2大小一样 //ret指向拷贝到a2的尾元素之后的位置 auto ret=copy(begin(a1),end(a1),a2);//把a1的内容拷贝给a2
-
元素重排算法
unique算法重排输入序列,将相邻的重复项“消除”,并返回一个指向最后一个不重复之后的位置的迭代器。
标准库算法对迭代器而不是容器进行操作。因此,算法不能(直接)添加或删除元素。
vector<string> words={"the","quick","red","fox","jumps","over","the","slow","red","turtle"};
//目标输出:fox jumps over quick red slow the turtle
void elimDups(vector<string> &words)
{//消除重复单词
//按字典序排序words,以便查找重复单词
sort(words.begin(),words.end());
//unique重排输入范围,使得每个单词只出现一次
//排序在范围的前部,返回指向不重复区域之后一个位置的迭代器
auto end_unique=unique(words.begin(),words.end());
//使用向量操作erase删除重复单词
words.erase(end_unique,words.end());
}
定制操作
-
谓词,参数数目严格
- 一元谓词:接受单一参数
- 二元谓词:接受两个参数
bool isShorter(const string&s1,const string &s2) {//二元谓词,替代sort的<来比较元素 return s1.size()<s2.size(); } sort(words.begin(),words.end(),isShorter);//按长度由短至长排序单词words
-
lambda表达式
谓词只能接受一个或二个参数,lambda用来打破这种限制。
- 表达式形式---参数列表和返回类型可省略,但必须包含捕获列表和函数体
[capture list](parameter list)->return type{function body}
[捕获列表](参数列表)->返回类型{函数体}
- 使用
auto f = []{return 42;}; cout<<f()<<endl;//打印42 //isShorter [] (const string &s1,const string &s2) {return s1.size()<s2.size();} //stable_sort stable_sort(words.begin(),words.end() [](const string &s1,const string &s2){return s1.size()<s2,size();});
- for_each
for_each(words.begin(),words.end(),[](const string &s){cout<<s<<" ";});
- 捕获列表的使用---biggies
一个lambda只有在其捕获列表中捕获一个它所在函数中的局部变量,才能在函数体中不用该变量。
捕获列表只有用于局部非static变量,lambda可以直接只用局部static变量和它所在函数之外声明的名字
stable_sort() 函数完全可以看作是 sort() 函数在功能方面的升级版。换句话说,stable_sort() 和 sort() 具有相同的使用场景,就连语法格式也是相同的(后续会讲),只不过前者在功能上除了可以实现排序,还可以保证不改变相等元素的相对位置。
和 find() 函数相同,find_if() 函数也用于在指定区域内执行查找操作。不同的是,前者需要明确指定要查找的元素的值,而后者则允许自定义查找规则。
make_plural是一个自定义方法。返回word的复数形式
void biggies(vector<string> &words,vector<string>::size_type sz) { elimDups(words);//将words按字典顺序排序,删除重复单词 //按长度排序,长度相同的单词维持字典序 stable_sort(words.begin(),words.end() [](const string &s1,const string &s2){return s1.size()<s2,size();}); //获取一个迭代器,指向第一个满足size>=sz的元素 auto wc=find_if(words.begin(),words.end(),[sz](const string &a) {return a.szie()>=sz;}); //计算满足size>=sz的元素数目 auto count=words.end()-wc; cout<<count<<" "<<make_plural(count,"word","s") <<" of length "<<sz<<" or longer"<<endl; //打印长度大于等于给定值的单词,每个单词后面接一个空格 for_each(wc.words.end(),[](const string &s){cout<<s<<" ";}); cout<<endl; }
-
捕获列表
列表 解释 [] 空捕获列表。lambda不能使用所在函数中的变量。一个lambda只有捕获变量后才能使用他们 [names] names是一个逗号分隔的名字列表,这些名字都是lambda所在哈桑农户的局部变量。默认情况下,捕获列表中的变量都被拷贝。名字前如果使用了&,则采用引用捕获方式 [&] 隐式捕获列表,采用引用捕获方式。lambda体所使用的来自所在函数的实体实体的值。 [=] 隐式捕获列表,采用值捕获方式。lambda体所拷贝所使用的来自所在夯实的实体的值。 [&,identifier_list] identifier_list是一个逗号分隔的列表,包含0个或多个来自所在函数的变量。这些变量采用值捕获方式,而任何隐式捕获的变量都采用引用方式捕获。identifier_list中的名字不能包括this,且这些名字之前不能使用& [=,identifier_list] identifier_list中变量都采用引用方式捕获,而任何隐式捕获的变量都采用值方式捕获。identifier_list中的名字不能包括this,且这些名字之前必须使用& -
lambda参数类型
lambda自己推断返回类型,但是程序看起来是等价于if语句的,则会编译出错
transform(vi.begin(),bi.end(),bi.begin(), [](int i){if(i<0)return -i;else return i;});//编译错误 transform(vi.begin(),bi.end(),bi.begin(), [](int i)->int {if(i<0)return -i;else return i;});//正确
参数绑定
对于捕获局部变量的Lambda,用bind来替代
- bind形式
auto newCallable = bind(callable,arg_list);
- newCallabe本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callabe的参数。
- 当我们调用newCallabe时,newCallabe会调用callabe,并传递给arg_list中的参数。
- arg_list中的参数可能包含形如_n的名字。其中n时一个整数。这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,....
- placeholders:
using std::placeholders::_1; using namespace std::placeholders;
bool check_size(const string &s,string::size_type sz){return s.size()>=sz;}
//check6是一个可调用对象,接受一个string类型的参数
auto check6 = bind(check_size,_1,6);
//bind调用一个占位符,表示check6只接受单一参数。占位符出现在arg_list的第一个位置,表示check_size的第一个参数。此参数是一个const string&,调用check6必须传递一个string类型的参数。
string s="hello";
bool b1=check6(s);//check6会调用chenk_size(s,6);
auto wc=find_if(words.begin(),words.end(),
[sz](const string &a){...});
auto wc=find_if(words.begin(),words.end(),bind(check_size,_1,sz));
auto g=bind(f,a,b,_2,c,_1);
g(_1,_2);
---->映射为f(a,b,_2,c,_1);
e.g
g(x,y)--->f(a,b,y,c,x);
- 绑定参数引用
默认情况下,bind的那些不是占位符的参数被拷贝到bind返回的可调用对象中。担忧的对象不支持拷贝。例如ostream
//os是一个局部变量,引用一个输出流 //c是一个局部变量,类型为char for_each(words.begin(),words.end(), [&os,c](const string &s){os<<s<<c;}); ostream &print(ostream &os,const string &s,char c){return os<<s<<c;} //错误:不能拷贝Os for_each(words.begin(),words.end(),bind(print,os,_1,' ')); //我们希望传递但不希望拷贝一个参数使用标准库ref函数 for_earch(words.begin(),words.end(),bind(print,ref(os),_1,' ')); ref返回一个对象,包含给定的引用,此对象是可以拷贝的。标准库还定义了一个cref,生成一个保存const引用的类。定义于头文件functional中
iterator
头文件:iterator
- 插入迭代器:这些迭代器被绑定到一个容器上,可用来向容器插入元素。
- 流迭代器:这些迭代器被绑定到输入或输出流上,可以用来遍历关联的IO流。
- 方向迭代器:这些迭代器向后而不是向前移动。除了forward_list之外的标准库容器都有方向迭代器。
- 移动迭代器:这些专用的迭代器不是拷贝其中的元素,而是移动它们。
插入迭代器
- it=t:在it指定的当前位置插入值t。假定c是it绑定的容器,依赖于插入迭代器的不同种类,此赋值会分别调用c.push_back(t),c_push_front(t)或c.insert(t,p),其中p为传递给inserter的迭代器位置
- *it,++it,it++:这些操作虽然存在,但不会对it做任何事情,每个操作都返回It
- back_inserter:创建一个使用push_back的迭代器(前提容器支持push_back)
- front_inserter:创建一个使用push_front的迭代器(前提容器支持push_front)
- inserter:创建一个使用insert的迭代器。此函数接受第二个参数,这个参数必须是一个指定给定容器的迭代器。元素将被插入到给定迭代器所表示的元素之前。
原理和使用:
//原理 it = c.insert(it,val);//it指向新加入的元素 ++it;//递增it使它指向原来的元素 //如果It是由insereter生成的迭代器,直接使用*it=val,等价于上面的操作 //使用 list<int> lst={1,2,3,4}; list<int> lst2,lst3;//空list //lst2--->{4,3,2,1} copy(lst.cbegin(),lst.cend(),front_inserter(lst2)); //lst3--->{1,2,3,4} copy(lst.cbegin(),lst.cend(),inserter(lst3,lst3.begin()));
流迭代器
流迭代器不支持递减运算,所以不可能在一个流中方向移动,也就是无法支持方向迭代器
istream_iterator
要读取的类型必须定义了输入运算符>>。它依赖于>>来读取流。
- 操作
istream_iterator<int> int_it(cin);//从cin读取int
istream_iterator<int> eof;//尾后迭代器:eof被定义为空istream_iterator,从而可以当作尾后迭代器
ifstream in("afile");
istream_iterator<string> str_it(in);//从“afile”读取字符串
//构造vec
istream_iterator<int> in_iter(cin),eof;
vector<int> vec;
while(in_iter != eof)
//后置递增运算读取流,返回迭代器旧值
//解引用迭代器,获取从流读取的前一个值
vec.push_back(*in_iter++);
-----------------------------------
//等价于前面的代码
istream_iterator<int> in_iter(cin),eof;
vector<int> vec(in_iter,eof);//从迭代器范围构造vec
//调用迭代器范围构造函数,直接从绑定流中读取数据,直到遇到文件尾或遇见一个不是int的数据为止
操作 | 解释 |
---|---|
istream_iterator |
in从输入流读取类型为T的值 |
istream_iterator |
读取类型为T的值的istream_iterator迭代器,表示尾后位置 |
in1==in2 in1!=in2 | in1和in2必须读取相同类型。如果他们都是尾后迭代器, 或绑定到相同的输入,则两者相等 |
*in | 返回从流中读取的值 |
in->mem | 与(*in).mem的含义相同 |
++in,in++ | 使用元素类型所定义的>>运算符从输入流中读取下一个值。与以往一样,前置版本返回一个指向递增后迭代器的引用,后置版本返回旧值 |
- 使用算法操作流迭代器
istream_iterator<int> in(cin),eof;
cout<<accumulate(in,eof,0)<<endl;
- istream_iterator允许懒惰求值
绑定一个流时,标准库并不保证迭代器立即从流中读取数据。具体实现可以推迟从流中读取数据,直到我们使用迭代器时才真正读取。
我们创建了一个istream_iterator,没有使用就销毁了,或者我们正从两个不同的对象同步读取一个流,那么这个特性将很重要。
ostream_iterator
依赖于<<操作流。因此操作对象必须定义了输出运算符<<
ostream_iterator必须绑定一个指定的流,无法以空ostream_iterator做为尾后迭代器
操作 | 解释 |
---|---|
ostream_iterator |
out将类型为T的值写到输出流os中 |
ostream_iterator |
out将类型为T的值写到输出流os中,每个值后面都输出一个d。d指向一个空字符结尾的字符数组,或者为一个字符串字面常量 |
out = val | 用<<运算符将val写入到out所绑定的ostream中。val的类型必须与out可写的类型兼容给 |
*out,++out,out++ | 这些运算符存在,但不对out做任何事情。每个运算符到返回out |
istream_iterator<Sales_item> item_iter(cin),eof;
ostream_iterator<Sales_item> out_iter(cout,"
");
Sales_item sum=*item_iter++;
while(iter_iter != eof )
{
if(item_iter->isbn() == sum.isbn())
sum += *item_iter++;
else
{
out_iter = sum;
sum = *item_iter++;
}
}
out_iter = sum;
方向迭代器
除forward_list外,其他容器都支持方向迭代器
调用:rbegin,rend,crbegin和crend
方向迭代器需要递减运算符的支持。
方向迭代器的目的时表示范围,而这些范围是不对称的,则导致的结果:当我们从一个普通迭代器初始化一个方向迭代器,或是给一个方向迭代器赋值时,结果迭代器与原迭代器指向的并不是相同的元素。
泛型算法结构
任何算法的最基本的特性时它要求其迭代器提供哪些操作。
find:只要求通过迭代器访问元素,递增迭代器以及比较两个迭代器是否相等这些能力
sort:比之find还要求读,写和随机访问元素能力。
类别
- 输入迭代器:可以读取序列中的元素。
- 必须支持:== ,!=,++,++(int),*,->
- 输出迭代器:可以看作输入迭代器功能上的补集——只写而不读元素。
- 必须支持:++,++(int),*
- 向前迭代器:可以读写元素
- 必须支持:输入和输出迭代器操作
- 双向迭代器:可以正反向读写序列中的元素
- 必须支持:前向迭代器操作和--,--(int)
- 随机访问迭代器:提供在常量时间访问序列中任意元素的能力
- 必须支持:<,<=,>,>=,+,+=,-,-=,-(两个迭代器间的减法),下标运算符(iter[n],和*(iter[n])等价)
算法形参模式
alg是算法名字,beg和end表示算法操作的输入范围。
dest:是一个表示算法可以写入的目的位置的迭代器。算法假定:按其需要写入数据,不管写入多少个元素都是安全的。向输出迭代器写入数据的算法都是假定目标空间足够容纳写入的数据。
alg(beg,end,other args);
alg(beg,end,dest,other args);
alg(beg,end,beg2,other args);
alg(beg,end,,beg2,end2,other args);
特定容器算法
对于有特定算法的容器,应该优先使用成员函数版本的算法而不是通用算法。
- list和forward_list成员函数版本的算法
都返回void
算法 | 解释 |
---|---|
lst.merge(lst2) | 将来自lst2的元素合并入lst。lst和lst2都必须是有序的。 |
lst.merge(lst2,comp) | 元素将从lst2中删除。在合并之后,lst2为空。第一个版本使用<运算符;第二个版本使用给定的比较操作 |
lst.remove(val) lst.remove_if(pred) | 调用erase删除掉与给定值相等(==)或令一元谓词为真的每个元素 |
lst.reverse() | 反转lst中元素的顺序 |
lst.sort() lst.sort(comp) | 使用<或给定比较操作排序元素 |
lst.unique() lst.unique(pred) | 调用erase删除同一个值的连续拷贝。第一个版本使用==;第二个版本使用给定的二元谓词。 |
- splice成员
lst.splice(args)或flst.splice_after(args)
参数 | 解释 |
---|---|
(p,lst2) | p是一个指向lst中元素的迭代器,或一个指向flst首前位置的迭代器。函数将lst2的所有元素移动到lst中p之前的位置或是flst中p之后的位置。将元素从lst2中删除。lst2的类型必须与lst或flst相同,且不能是同一个链表。 |
(p,lst2,p2) | p2是一个指向lst2中位置的有效的迭代器。将p2指向的元素移动到lst中,或将p2之后的元素移动到flst中。lst2可以是与lst或flst相同的链表 |
(p,lst2,b,e) | b和e必须表示lst2中的合法范围。将给定范围中的元素从lst2移动到lst或flst。lst2与lst(或flst)可以是相同的链表,但p不能指向给定范围中元素 |