C++学习笔记-泛型算法

来源:互联网 发布:python官网 中文版 编辑:程序博客网 时间:2024/06/07 22:19

泛型算法

大多数算法都定义在头文件algorithm中,标准库还在头文件numeric中定义了一组泛型算法。

它们可以用于不同类型的元素和多种容器类型(不仅包括标准库类型,如vector或list,还包括内置的数组类型)

只读算法

find(beg,end,val);  //前两个参数是迭代器范围,第三个参数是要查找的值

返回第一个等于给定值的元素的迭代器。如果范围中无匹配元素,则返回第二个参数。

所需的头文件是algorithm。


accumulate(beg,end,iniSum)  //前两个参数是迭代器范围,第三个参数是和的初始值

注意,元素类型与和初始值的类型不必相同,但是它们之间必须能够执行“+”运算。

所需的头文件是numeric。

对于只读而不改变元素的算法,通常用cbegin()和cend()指定迭代器范围


equal(v1.cbegin(),v1.cend(),v2.cbegin());

v1和v2中元素的类型不必相同,只要我们能用==来比较这两种元素类型即可。

注意,equal基于一种非常重要的假设:它假定第二个序列至少与第一个序列一样长。用单一迭借口代器表示第二个序列的算法都有这个假设。

写容器元素的算法

使用这类算法时,必须确保原序列大小至少不小于我们要求算法写入的元素数目。(算法不会执行容器操作,因此它自身不可能改变容器大小)


fill(v.begin(),v.end(),val)   //将迭代器范围内的元素都置为val


fill_n(v.begin(),n,val)   //将给定值赋予迭代器指向的元素开始的指定个元素。


back_iterator

back_iterator接受一个指向容器的引用,,返回一个与该容器绑定的插入迭代器。当我们通过此迭代器赋值时,赋值运算会调用push_back将一个具有给定值的元素插入到容器中

用法:

vector<int> vec; //空容器

auto it=back_iterator(vec);

*it=42;  //容器中现在有一个元素

将back_iterator作为算法的目的位置:

vector<int> vec;

fill_n(back_iterator(vec),10,0)  //添加10个元素到vec中(不能用vec.begin()作为迭代器写入元素。因为此时vec中没有元素,是空的。)


copy(v1.begin(),v1.end(),v2.begin()); //前两个参数是一对迭代器范围,第三个参数是目的序列的起始位置

其返回值是指向v2尾元素之后的位置的迭代器

注意,目的序列至少要包含与输入序列一样多的元素。这一点很重要。


replace(v.begin(),v.end(),oldValue,newValue)//前两个参数是一对迭代器范围,算法将该范围内所有等于oldValue的元素的值替换为newValue

replace(v.begin(),v.end(),v2.begin(),oldValue,newValue) //原序列不变,v2包含v1的拷贝,且v2中所有等于oldValue的元素的值替换为newValue


sort(v.begin(),v.end())  //利用元素的<运算符实现从小到大的排序

unique(v.begin(),v.end())  //假如某个元素第一次出现的,那么它的位置保持不变。之后重复出现的该元素都被放在序列末尾。算法返回指向不重复区域之后一个位置的迭代器


定制操作

谓词

谓词是一个函数,接受一个或两个参数。返回bool类型值。接受谓词的算法对输入序列中的元素调用谓词。因此,元素类型必须能转换成谓词的参数类型

例 

bool isShorter(const string &s1,const string &s2)

{

return s1.size()<s2.size();

}

stable_sort(v.begin(),v.end(),isShorter); //isShorter使得算法用长度大小排序。而stable_sort与sort不太一样。它能维持相等元素的原有顺序。

lambda表达式

有些算法只能接受一元谓词,有些可以接受二元谓词。有时我们希望的操作需要更多参数,超出了算法对谓词的限制。因此,引用lambda表达式。lambda表达式可能定义在函数内部

lambda表达式具有以下形式

[捕获列表](参数列表)->返回类型{函数体} 例 []{return 42}  //该lambda表达式的捕获列表为空,省略了参数列表和返回类型

捕获列表是由若干个定义在lambda所在函数中的局部变量组成的列表(将局部变量包含在其捕获列表中,lambda表达式能够在函数体使用这些变量)

调用lambda表达式

例:

auto f=[]{return 42}; 用lambda表达式定义可调用对象

cout<<f();//调用lambda表达式。输出42

例:

vector<string> words

...

stable_sort(v.begin(),b.end(),[](const string &a,const string &b){return a.size()<b.size();});  //按长度排序

使用lambda表达式代替谓词时,它的参数个数谓词的参数个数一样。但是它可以访问自己所在函数中的局部变量(要在捕获列表中指出)。还可以直接使用定义在当前函数之外的名字。

lambda表达式的值捕获

注意被捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝

void f(){

int n=1;

auto f=[n]{return n;};

n=2;

auto j=f();//j 是1

}

lambda表达式的引用捕获

void f(){

int n=1;

auto f=[&n]{return n;};

n=2;

auto j=f();//j 是2

}

注意,使用引用捕获时,一定要确保被引用的对象在lambda执行的时候是存在的。

若函数返回lambda,则lambda不能包含引用捕获。(因为捕获的引用指向的是局部变量,而局部变量在函数结束后就不存在了)

捕获指针,迭代器也是一样,必须确保lambda执行时指针或迭代器指向的对象存在。

隐式捕获

可以让编译器根据lambda的代码来推断我们要用哪些变量

用法:在捕获列表中写一个&或者=.&表示采用引用方式,=表示采用值捕获方式。

void f(){

int n=1;

auto f=[=]{return n;}; //隐式捕获,值捕获方式

n=2;

auto j=f();//j 是1

}


void f(){

int n=1;

auto f=[&]{return n;};  //隐式捕获,引用捕获方式

n=2;

auto j=f();//j 是2

}

还可以混合使用隐式捕获和显示捕获


void f(){

int n1=1;

int n2=3;

auto f=[=,&n2]{return n1+n2;}; //n1隐式捕获,值捕获方式;n2显式捕获,引用捕获方式

n1=2;

n2=5;

auto j=f();//j 是1+5=6

}

void f(){

int n1=1;

int n2=3;

auto f=[&,n2]{return n1+n2;};  //n1隐式捕获,引用捕获方式;n2显式捕获,值捕获方式

n1=2;

n2=5;

auto j=f();//j 是2+3=5

}

可变lambda

默认情况下,对于一个值被拷贝的变量,lambda不会改变其值。如果想要改变值拷贝的变量,需要在参数列表后面加上关键字mutable.

int _tmain(int argc, _TCHAR* argv[])
{

    int m = 0, n = 0;
    // 不加mutable会报错
    //[=] (int a){ m = ++n + a; }(4);
    //[m,n] (int a){ m = ++n + a; }(4);

    int sum=[=] (int a) { return m = ++n + a; }(4);
    //
    // [=] (int m, int n, int a){m=++n+a; }(m, n, 4);
    // 下面这个函数m,n的值依然会被修改,因为m,n是按引用传入的
    // [=] (int &m, int &n, int a){m=++n+a; }(m, n, 4);
    cout<<sum<<endl;  //输出5
    cout << m << endl ;//输出0

  cout<< n << endl;  //输出0
    return 0;
}

即一个值被拷贝的变量,它的值 依然没有被修改。这维持按值引用的特性。

指定lambda返回类型

若lambda函数体中只有单一的return语句,那么无须指定返回类型,编译器会根据return 后面的表达式推断返回类型

若lambda函数体中有多个return语句,那么需要指定返回类型。定义返回类型时,必须使用尾置返回类型

[](int i)->int {

if(i<0)

return -i;

else

return i;

}

参数绑定

bind接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表

用法: auto newCallable=bind(callable,arg_list);

arg_list中可能含有形如_n的名字。n指代传递给新生成的可调用对象的参数表中的第n 个参数。例,

bool check_size(const string&,string::size_type sz){

return s.size()>=sz;

}

auto check=bind(check_size,_1,6);

string s="hello,you";

check(s);  //实际上调用了check_size(s,6);

所以bind用来

1.将一个多参数的可调用对象,转换成较少参数的新的可调用对象。如上面的例子

2能实现重排参数顺序

例:

sort(words.begin(),words.end(),bind(ishorter,_2,_1));  //实现单词长度由长到短排列

注意,_1,_2,...等名字在命名空间placeholders中。假如要使用_1,要在程序中加入using std::placeholders::_1

绑定引用参数

有些对象不能拷贝,可以用cref函数。ref(对象)返回一个新对象,包含原对象的引用。新对象是可以拷贝的。

cref(对象)生成一个保存const引用的类。

插入迭代器

有三种:

back_inserter(c) //创建一个使用push_back的迭代器(c是支持push_back的容器)

front_inserter(c)  //创建一个使用push_front的迭代器(c是支持push_front的容器)

inserter(c,p) //p是迭代器,创建一个使用insert的迭代器,将元素插入到给定迭代器所表示的元素之前

工作过程:

若it是inserter生成的迭代器,则*it=val;等效于

it=c.insert(it,val);  //it指向新加入的元素

++it;  //递增it,使其指向原来的元素

若使用front_inserter.

list<int> lst={1,2,3,4};

list<int> lst2,lst3;

copy(lst.cbegin(),lst.cend(),front_inserter(lst2));  //拷贝完成后,lst2包含4 3 2 1

copy(lst.cbegin(),lst.cend(),inserter(lst3,lst3.begin())); //拷贝完成后,lst3包含1 2 3 4


iostream迭代器

istream_iterator

例:
    istream_iterator<int> in(cin); //in从输入流cin记取类型为int的值
    istream_iterator<int> end;  //尾后迭代器
    cout<<accumulate(in,end,0); //使用算法操作流迭代器。若输入1 2 3 4 5 ctl+z则输出15

ostream_iterator
    ostream_iterator<T> out(os); //out将类型为T的值写到输出流os
    ostream_iterator<T> out(os,d); //out将类型为T的值写到输出流os,每个值后面都输出一个d.d是一个空字符结尾的字符数组

 ostream_iterator<int> out(cout," ");

for(auto e:vec){

*out++=e;  //赋值语句实际上将元素写到cout

}

cout<<endl;

可以为任何定义了输入运算符的类型创建istream_iterator对象。可以为任何定义了输出运算符的类型创建ostream_iterator对象

反向迭代器


0 0
原创粉丝点击