C++11(9):泛型算法

大多数算法都是定义在头文件algorithm中。标准库还在头文件numeric中定义了一组数值泛型算法
算法不依赖于容器,但依赖于元素类型
泛型算法本身不会执行容器的操作,它们只会运行于迭代器之上,执行迭代器的操作。因此,算法不会改变底层容器的大小。
find(bigin(ia),end(ia),val);
int sum=accumulate(vec.cbegin( ),vec.cend( ),0);
这条语句将sum设置为vec中元素的和,和的初始值为0,第三个参数说明了函数中使用哪个加法运算符以及返回值得类型。
vec中的元素必须可以是int,或者是double,long long或者其他任何可以加到int上的类型,

string sum = accumulate(v.cbegin(),b.cend( ),string(""));   ///正确
string sum = sccumulate(v.cbegin(),b.cend(), "")//错误,const char*上么有定义+运算符

另一个只读算法equal(),用于确定两个序列是否保存相同的值。
equal(roster1.cbegin(),roster1.cend(),roster2.cbegin());
两个容器的元素不必一样,只要可以用==比较。而且有一个假设,第二个序列至少与第一个序列一样长。

写入元素:fill(vec.begin(), vec.end(), 0);
vector<int> vec;   //空vector;
fill_n(vec.begin() , 10,0);   //   灾难,修改vec中不存在的元素
fill_n (vec.begin(), vec.size(), 0 );//将所有元素置零

插入迭代器 back_inserter,定义在头文件iterator中的一个函数。接受一个指向容器额引用。返回一个与该容器绑定的插入迭代器。
vector<int> vec;
auto it=back_inserter(vec);
*it = 42;
vector<int>vec://空向量
fill_n(back_inserter(vec) , 10 ,0);正确,back_inserter创建一个插入迭代器,可用来添加元素
 
int  a1[ ] = {0,1,2,3,4,5,6,7,8,9};
int  a2[sizeof(a1)/sizeof(*a1)];//a2  和 a1大小一样
//    ret指向拷贝到a2的尾元素之后的位置
auto ret = copy(begin(a1), end(a1), a2);
前两个迭代器是被拷贝元素范围,第三个是目的序列的其实位置

replace(ilst.begin(),ilst.end(),0,42);//将所有的0都替换为42;
replace_copy(ilst.cbegin(),ilist.cend(),back_inserter(ievc), 0, 42);
ilist中的元素不变,ivec包含ilst的一份拷贝,不过原来在ilst中的值为0的元素用42替换
sort 用元素类型的<来实现排序

重排容器元素的算法:
void elimDups(vector<string> &words)
{
    sort(words.begin(),words.end());
    auto end_unique = unique(words.begin(),words.end());//将重复元素移到末尾,并返回最后一个不重复元素之后的位置
    words.erase(end_unique,words.end());//删除元素
}
标准库算法对迭代器而不是容器进行操作。因此,算法不能(直接)添加或删除元素。删除是调用成员函数
 
若果使用sort时,比较元素么有定义<   我们可以使用谓词:一个可调用的表达式,其返回结果是一个能用作条件的值
一元谓词,意味着只接受单一参数,二元谓词,元素类型必须可以转换成谓词的参数,定义的是元素之间的一种关系
bool isShorter (const string &s1,const string &s2)
{
    return s1.size()<s2.size();
}
sort(words.begin(),words.end(),isShorter);

通常我们不在意排序时相等元素的相对位置,但是我们可以通过stable_sort这种稳定的排序算法来排序,保证排序后相等元素相对位置不变
elimDups(words);
stable_sort(words.begin(),words.end(),isShorter);
for(const auto &s : words)
    cout<< s << " ";
cout<<endl;

find_if: 接受一对迭代器,表示一个范围,第三个参数是一个谓词。find_if对输入序列中的每个元素调用给定的谓词。它返回第一个使谓词返回非0值得元素,如果不存在则返回尾迭代器,find_if接受的一元谓词

对于一个对象或表达式,如果可以对其使用调用运算符,则称它为可调用的。如果e是一个可调用的表达式,则我们可以编写代码e(args),args是用逗号分隔的参数列表

可调用的对象:函数,函数指针,重载了函数调用符的类,lambda表达式
一个lambda表达式表示一个可调用的代码单元。我们可以将其理解为一个未命名的内联函数。
[capature list](parameter) -> return type { function body }
capture list (捕获列表) 是一个lambda所在函数中定义的局部变量的列表(通常为空),lambda表达式必须使用尾置返回
auto f = [ ] { return 42 };//我们定义了可调用对象f,不接受参数,返回42;
cout<< f() <<endl;//调用方式和普通函数一样
如果忽略返回类型,函数体中只有一个return,lambda根据函数中的代码推断出返回类型, 
如果lambda的函数体包含任何单一return 语句之外的内容,且未指定返回类型,则返回void
lambda不能有默认参数,因此一个lambda调用的实参数目永远和形参数目相等
空的捕获列表表示lambda不使用它所在函数中的任何局部变量
 一个lambda表达式通过局部变量包含在捕获列表中来指出将会使用这些变量。

[sz](const string &a)   { returen a.size( ) >= sz ;};     //注意,花括号后的分号!
auto wc = find_if(  words.begin( ) , words. end( ), [sz](const string &a) { return a.size( ) >= sz;}  )
for_each(wc , words.end( ) , [ ](const string &a){cont << s << " ";});
cout<<endl;

完整的biggles:
string make_plural(size_t ctr, const string &word, const string &ending)
{
     return (ctr > 1) ? word + ending : word;
}
void biggies ( vector<string> &words, vector<string>::size_type sz)
{
    elimDups(words);   //将words按字典顺序排序,删除重复单词
    stable_sort(words.begin( ), words.end(), [ ](const string& a ,const string &b)
            {return a.size( ) < b.size( );}); //  按长度安排,长度相同的单词维持字典序
    auto wc = find_if(words. begin( ), words. end( ), [sz](const string &a),
            [sz](const string &a)
                { return a.size( ) >= 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对应的新的(未命名)类类型。当使用lambda初始化的变量时,定义一个从lambda生成的对象。默认情况,从lambda生成的类包含一个对应这个lambda所捕获的变量的数据成员,类似普通成员,lambda的数据成员也在lambda对象被创建时被初始化

捕获变量的值在lambda创建时拷贝,因而随后对变量的修改不会影响到对应lambda内对应的值
auto f2 = [&v1] {return v1;};//返回的是v1指向的对象的值
io对象不可拷贝,所以对于io对象来说,我们就可以用引用来传递了。特别的,对于捕获引用来说,必须保证引用是存在的,且有预期的值
一般来说,我们应该尽量减少捕获的数量,来避免潜在的捕获导致的问题,而且,如果可能的话,应该避免捕获指针或引用

隐式捕获,除了显示的列出希望使用的来自所在函数的变量之外,我们也可以让编辑器根据lambda中的代码来推测,
&捕获引用;=值捕获
//sz采用隐式捕获,值捕获方式
wc = find_if(words.begin( ),words.end( ),[=](const string& s){return s.size( )>=sz;);
如果我们希望对一部分变量采用值捕获,对其他变量采用引用捕获,可以混合使用隐式捕获和显示捕获 
for_each(words.begin( ), words.end( ),[& , c](const string &s){os<< s << c;})
当我们混合使用了隐式捕获和显示捕获时,捕获列表中的第一个元素必须是一个&或=。此符号指定了默认的捕获方式
但使用混合捕获方式时,显示与隐式必须采用不同的捕获方式。如,如果隐式捕获采用引用方式,则显式捕获命名变量必须采用值得方式

默认情况下,对于一个被拷贝的变量,lambda不会改变其值,若果要改变被捕获的值,就必须在参数列表首加上mutable,因此可以省略参数列表
auto f = [v1] ( )mutable { return ++v1;};
一个引用捕获的变量是否可以修改依赖于此引用指向的是一个const还是非const类型
auto f2 = [&v1] { return ++v1; };

默认情况下,如果一个lambda体包含return之外的任何语句,则编译器假定返回void。就是没有返回值、
transform(vi.begin( ), vi.begin( ) ,vi.begin( ), [ ](int i) { return i<0? -i : i ;});//将序列中的每一个负数替换成绝对值。
前两个迭代器是输入范围,第三个迭代器是目的位置,我们无须指定返回类型,可以根据条件运算符的类型推断出来
transform(vi.begin( ), vi.end( ),vi.begin( ),[ ](int i) { if ( i < 0 ) return -i; else return i;});//错误
编辑器推断这个版本的lambda返回类型为void,但返回一个 int,此时我们就好指定返回类型
transform(vi.begin( ) , vi.end( ) , vi.begin( ) , [ ] (int i) -> int { if ( i < 0 ) return -i; else return i;});

对于那种只要一两个地方使用的简单操作,lambda表达式是最有用的,若果在多个地方,或这比较复杂,通常用的是函数
如果lambda表达式的捕获列表是空的,通常可以用函数来代替
定义在functional头文件中的bind函数:可以看做是一个通用的函数适配器,它接受一个可调用的对象,生成一个新的可调用对象“适应”源对象的参数列表,auto newCallable = bind(callable,arg_list)
当我们调用newCallable时,newCallable会调用callable,并传递它arg_list中的参数
arg_list 中的参数可能使_n.n是整数。这些参数是占位符,数值n表示的是生成可调用对象的位置:_1为newCallable的第一个参数,依次类推
auto wc = find_if (words.bengin(),words,end(),bind(check_size,_1,sz);
_n定义在placeholders的命名空间中,而这个命名空间本身定义在std命名空间中。
_1的声明为   using std::placeholders::_1;
与bind函数一样,palceholders命名空间也定义在functional头文件中
用bind重排参数顺序
sort(words.begin(),words.end(),isShorter);///按单词长度由短到长排序
sort(words.begin(),words.end(),bind(isShorter,_2,_1));///由长到短
默认情况下,bind的那些不是占位符的参数被拷贝到bind返回的可调用对象中,但是我们可以使用引用
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;
}
for_each(words.begin(),words.end(),bind(print,os,_1,' '));//错,不能拷贝os
for_each(words.begin(),words.end(),bind(print,ref(os),_1,' '));
ref返回的一个对象,包含给定的引用,此对象可以拷贝。标准库中还有一个cref函数,生成的是const引用的类。这两个函数在functional头文件中(老版c++用的是bind1st和bind2nd,已经被弃用)

除了为每个容器定义的迭代器之外,标准库的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的迭代器
front_inserter  创建一个使用push_front的迭代器
insert    创建一个使用insert的迭代器。此函数接受第二个参数,这个参数必须是一个指向给定容器的迭代器,元素被插入到给定迭代器所表示的元素之前
只有在容器支持对应操作的情况下,我们才能使用对应的迭代器
调用 insert(c,iter)时,得到一个迭代器,接下来使用它会将元素插入iter原来所指向的元素之前的位置。
*it = val;
其效果与下面代码一样:
it = c.insert(it,val);//it 指向新插入的元素
++it;  //递增it使他指向原来元素
当我们使用front_inserter时,元素总是插入到容器第一个元素之前。而对于inserter指向第一个元素,只要我们插入新元素,此元素就不是首元素了,当调用front_inserter(c),我们得到一个插入迭代器,接下来会调用push_front。当每个元素被插入c中时,它变为首元素,可见,front_inserter生成的迭代器会将插入的元素序列顺序颠倒过来,而inserter和back_inserter则不会

istream_iterator   读取输入流, ostream_iterator  向一个输出流写数据
当创建一个流迭代器时,必须指定迭代器将要读写对象的类型,一个istream_iterator使用>>来读取流,因此该类型必须定义了输入允许符。创建时,我们可以将它绑定到一个流上,也可以默认初始化,来创建一个可以当作尾后值使用的迭代器
istream_iterator<int> int_it(cin);
istream_iterator<int> int_eof;  //定义成空
while(int_it!=int_eof)   //当有数据可供读取时执行,一旦其关联的流遇到文件尾或遇到IO错误,迭代器的值就与尾后迭代器相同
    vec.push_back(*in_iter++);   //存入vector

使用一对表示范围的迭代器来构造vector:
istream_iterator<int> in_iter(cin),eof:
vector<int> vec(in_iter,eof);

in_iter   返回从流中读取的值
in->men    与(*in).men含义相同
使用算法:
istream_iterator<int> in(cin),eof:
 cout<<accumulate(in,eof,0)<<endl;
istream_iterator绑定到一个流时,标准库不保证迭代器立即从流读取数据,保证的是,在我们第一次解引用迭代器之前,从流中读取数据的操作已经完成。一般情况无所谓,如果我们创建了一个istream_iterator,么有使用就销毁了,或者我们正在从两个不同的对象同步读取同一个流,那么何时读取就很重要了

我们可以对任何带有输出运算符的类型定义ostream_iterator。创建时我们可以提供第二个参数,一个c风格的字符串。在输出每个元素后都会打印此字符串。必须将ostream_iterator绑定到一个流上,不允许空的
out=val;   用<<运算符将val写入到out所绑定的ostream中。val的类型必须与out可写的类型兼容
*out     ++out     out++这些运算符值存在的,但是不对out做任何事情,每个运算符都返回out
ostream_iterator<int> out_iter(cout, " ");
for (auto e:vec)
        *out_iter++=e;
cout<<endl;

值得注意的是,当我们向out_iter赋值时,可以忽略解引用可递增运算。循环写成
for(auto e: vec)
        out_iter = e;
cout<<endl;// 省略*    ++   因为他们不会做任何事情
也可以通过copy类打印:
copy(vec.begin() , vec.end() , out_iter) ;
cout<<endl;

除了forward_list之外,其他容器都支持反向迭代器。我们可以通过调用rbegin、rend、crbegin、crend成员函数来获得反向迭代器。返回的是指向容器尾元素和首元素之前一个 位置的迭代器。
sort(vec.begin(),vec.end());//按正常顺序
sort(vec.rbegin().vec.rend());//逆序
因为流迭代器不支持递减运算,因为不可能在一个流中反向移动。因此不能砸iforward_list和流迭代器创建反向迭代器
反向迭代器的目的是表示元素的范围,而这些范围是不对称的,导致一个结果:当我们从一个普通迭代器初始化一个反向迭代器,或是给反向迭代器赋值,结果迭代器与原迭代器指向的不是相同的元素

五类迭代器:输入迭代器、输出迭代器、前向迭代器、双向迭代器、随机访问迭代器
除了输出迭代器之外,一个高层类别的迭代器支持低层类别迭代器的所有操作
(对于传递错误类别迭代器,很多编译器不会给出任何警告或提示)
find算法在一个序列上进行一遍扫描,对元素进行只读操作,至少需要输入迭代器
replace函数需要一对迭代器,至少是前向迭代器。
replace_copy前两个迭代器参数要去至少是前向迭代器。其第三个表示目的位置,必须至少是输出迭代器。

输入迭代器:可以读取序列中的元素。必须支持:
==       !=     ++     *      ->
输入迭代器只用于顺序访问,*it++保证是有效的,但递增科恩能够导致所有其他指向流的迭代器失效,因此,输入迭代器只能用于单遍扫描的算法。find和accumulate
输出迭代器,必须支持:
++    *   
只能向输出迭代器赋值一次,而且只能用于单遍扫描的算法。用作目的位置的迭代器通常就是输出迭代器,如,copy的第三个参数
前向迭代器:支持输入输出的说有操作,使用前向迭代器可以进行多遍扫描,replace要求的就是前向迭代器,forward_list上的就是
双向迭代器,支持前向迭代器的所有操作,还支持递减运算符(--)reverse要去的就是双向迭代器
随机访问迭代器,sort要求随机访问,array、deque、string、vector的迭代器都是随机访问迭代器

使用谓词的算法
unique(beg,end);
unique(beg,end,comp);
_if版本:
find(beg,end,val)//查找输入范围中val第一次出现的位置
find(beg,end,pred);//查找第一个令pred为真的元素

默认情况,重排算法将重排后的元素写回给定的输入序列中,这些算法还提供了另一个版本,将元素写到一个指定的输出目的位置。
reverse(beg,end);     ///反转输入范围中元素的顺序
reverse_copy(beg,end,dest);
一些算法同时提供_copy和_if版本
remove_if(v1.begin(),v1.end(),[ ](int i){return i % 2;});
remove_copy_if(v1.begin(),end(),back_inserter(v2),[ ](int i){return i % 2;});

链表类型list和forward_list定义了几个成员函数形式的算法;
对于链表,应该优先使用成员函数的版本,性能会好很多
lst.merge(lst2)      将来自lst2的元素合并入lst。lst和lst2必须都是有序的。
lst.merge(lst2,comp)      删除lst2,合并之后lst2为空。这个版本使用给定的比较操作
lst.remove(val)    调用erase删除与给定值相等(==)或令一元谓词为真的每个元素
lst.remove_if(pred)
lst.reverse()       反转
lst.sort()     使用<或给定的比较操作排序
lst.sort(comp)
lst.unique()       调用erase删除同一个值得连续拷贝。第一个调用==。第二个调用给定的二元谓词
lst.unique(pred)    

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中合法的范围,移动的两链表可以相同。但是p不能指向给定范围中的元素

remove的链表版本会删除指定的元素。unique的链表版本回删除第二个和后继的重复元素
类似的。merge和splice会销毁其参数。

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值