《C++Primer》学习笔记(10、泛型算法)

泛型算法:

称为算法:因为它们实现了一些经典算法的,公共接口

称为泛型:它们可用于,不同类型的元素多种容器类型

概述:

大部分算法,都定义在头文件,algorithm中;在头文件,numeric中,还定义了数值泛型算法。

标准库算法find:

查找范围,[ vec.cbegin(),vec.cend() ],vec.cend()表示尾元素的后一个位置。

对于向量vector vec的查找:

find(vec.cbegin(),vec.cend(),val);//查找失败返回第二个参数迭代器,成功的话,返回val对应的迭代器。

对于数组int ia[] = {27,210}:

find(ia.begin(),ia.end(),val);

 

迭代器令算法不依赖于容器,但算法依赖于元素类型的操作。

泛型算法本身不会执行容器的操作。

编程假定:算法永远不会改变底层容器的大小。但算法可能改变容器中保存的元素的值,也可能在容器内移动元素。

若迭代器是插入器,当算法给这个迭代器赋值时,则它们会在底层容器上执行插入操作。但并不是算法插入的。

 

初识泛型算法:

只读算法:

accumulate(vec.cbegin(),vec.cend(),0),定义在numeric中,第三个参数是和的初值。

string定义了+运算符,可以用它来实现,vec中的string元素的拼接。

但是因为const char *没有定义+运算符,则第三个参数不能为空串""。

 

操作两个序列的算法:

equal(roster1.cbegin(),roster1.cend(),roster2.cbegin());

roster1和roster2可以是不同类型的容器。

编程假定:第二个序列至少与第一个序列一样长。

 

写容器元素的算法:

fill(vec.begin(),vec.end(),0); //将所有元素重置为0

fill_n(dest,n,val);  //从dest开始的序列,至少包含n个元素。

常犯错误:对一个空向量,插入值。

 

介绍back_inserter:

插入迭代器:保证算法,有足够元素空间来容纳输出数据的方法。

对插入迭代器赋值时,会将元素拷贝进容器。

back_inserter,定义在iterator头文件中。

auto it=back_inserter(vec);//输入容器引用,返回一个迭代器

*it=42;//调用底层容器的push_back

常用搭配:

fill_n(back_inserter(vec),10,0);//添加10个元素到vec

 

拷贝算法:

int a1[] = {0,1,2,3,4};

int a2[sizeof(a1)/sizeof(*a1)];

auto ret = copy(begin(a1),end(a1),a2);  // 把a1的内容拷贝给a2

ps:ret指向拷贝到a2的尾元素之后的位置。

 

replace(ilst.begin(),ilst.end(),0,42);  // 对给定范围内的序列,将0值替换为42

replace_copy(ilst.begin(),ilst.end(),back_inserter(ivec),0,42);   //ivec包含ilst的一份拷贝,但此拷贝中,0都替换为42了。

 

重排容器元素的算法:

消除重复元素

words是含有重复元素的的vector。

先通过,sort(words.begin(),words.end())排序。

再通过,auto end_unique=unique(words.begin(),words.end());// 返回不重复区之后一个位置的迭代器。

最后通过,words.erase(end_unique,words.end());删除重复元素。

 

定制操作:

谓词:是一个可调用的表达式,其返回结果是一个能用作条件的值。

二元谓词:只接受两个参数。

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

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

}

sort(words.begin(),words.end(),isShorter);

稳定排序:

stable_sort(words.begin(),words.end(),isShorter);  //保持原有相等元素的顺序不变。

 

lambda表达式:

find_if:接受一个迭代器表示的范围,和一个谓词;返回第一个使谓词返回非0值的元素,若不存在,则返回尾迭代器。

C++四种可调用对象:

函数、函数指针、重载了函数调用运算符的类、lambda表达式。

一个lambda表达式就有的形式:

[capture list](parameter list) -> return type {function body}

捕获列表:函数中局部变量列表。要永远包含捕获列表和函数体。

与普通函数不同,lambda必须使用尾置返回,来制定返回类型。

auto f = []{ return 42;};f();

若未指定返回类型,则要么根据函数体代码,推断出返回类型;要么返回void。

 

向lambda传递参数:

与普通函数不同,lambda不能有默认参数,调用的实参数目永远与形参数目相等。

空捕获列表,表明lambda不使用它所在函数中的任何局部变量。

使用捕获列表:

一个lambda只有在其捕获列表中,捕获一个它所在函数中的局部变量,才能在函数体中使用该变量。

调用find_if:

auto wc=find_if(words.begin(),words.end(),[sz](const string &a){return a.size() >= sz};

ps:返回指向,第一个满足条件的元素所在的迭代器。

计算从wc到words的末尾一共有多少个元素:

auto count = words.end() - wc;

可根据count是否为1,输出单复数:make_plural(count, ”word“,"s);

 

for_each算法:

for_each(wc, words.end(), [] (const string &s){cout << s << " ";} ); // 每个单词后面接一个空格

ps: lambda可以直接使用局部static变量在它所在函数之外声明的名字。使用局部非static变量,需先捕获。

 

lambda捕获和返回:

当定义一个lambda时,编译器生成一个,与lambda对应的新的,没名字的类类型。

在函数中,用lambda传递参数时,被编译器生成为,没名字的对象。

捕获的变量,相当于lambda生成的类,的数据成员,在lambda对象创建时被初始化。

 

值捕获:

采用值捕获的前提是,变量可以拷贝。

引用捕获:

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

必须确保,lambda执行时,这个引用捕获的变量是存在的。

引用捕获是必要的:

因为不能拷贝ostream对象,因此捕获os的唯一方法就是,捕获其引用。

[&os ,c ](const string &s){ os <

ps:确保lambda每次执行的时候,捕获的变量都有预期的意义,是程序员的承认。

捕获一个普通变量,如int、string或其他非指针类型,采用简单的值捕获方式即可。

 

隐式捕获:

在捕获列表中写一个&(引用捕获)或=(值捕获)

混合使用:

捕获列表的第一个元素必须是一个&或=,显示捕获必须使用与隐式捕获不同的方式。

 

可变lambda:

对于一个值捕获的变量,lambda不会改变其值,若要改变,需这样写:

auto f = [v1] () mutable { return ++v1};

指定lambda返回类型:

若一个lambda既没有指定返回类型,体内又包含了return之外的语句,则编译器假定此lambda返回void。

若被假定返回void,但实质上又返回类值,则产生编译错误。

因此,最好声明一下返回值:

[] (int i) -> int {if(i<0) return -i; else return i; };

 

参数绑定:

标准库bind函数,它定义在头文件functional中,可看作是,通用的适配器。

bind函数返回一个,可调用对象。

例如,函数,bool check_size(const string &s, string::size_type sz);

如不想使用lambda的捕获列表,若传递函数check_size(),则find_if又只能传一个参数,匹配不上check_size的参数列表。则,auto check6 = bind(check_size, _1, 6);

所以又,auto wc = find_if(words.begin(), words.end(), check6);

使用placeholders名字:

名字_n定义在名为placeholders的命名空间,placeholders又定义在std命名空间。

placeholders命名空间定义在functional头文件中。

bind的参数:

auto g = bind(f, a, b, _2, c, _1);

g的第一个参数绑定到_1,g的第二个参数绑定到_2。

栗子://按单词长度由长至短排序:

sort(words.begin(), words.end(),bind(isShorter, _2, _1));

绑定引用参数:

因为不能拷贝一个ostream,因此需使用标准库,ref函数:返回一个对象,包含给定引用,可拷贝:

for_each(words.begin(), words.end(), bind(print, ref(os), _1, ' '));

//还有一个cref函数,生成一个保存const引用的类。

再探迭代器:

(1)、插入迭代器:能实现向给定容器添加元素。直接向迭代器it = t,这样赋值即可。

*it、++it,it++这些操作存在,但不做任何事情,只返回it

插入迭代器:back_inserter,front_inserter,inserter。

使用inserter时,需要给定第2个参数,指向给定容器的迭代器,插入的元素在该迭代器所指的元素之前。

list lst = {1,2,3,4};

list lst2;

copy(lst.cbegin(), lst.cend(), inserter(lst2,lst2.begin()));  // 给定的迭代器是lst2.begin() 记为x,插入元素之后变为,1,2,3,4,x

因此,插入元素序列的顺序没被颠倒。

(2)、iostream迭代器:

istream_iterator、ostream_iterator,这些迭代器将它们对应的流,当作一个特定类型的元素序列来处理。

istream_iterator:

创建流迭代器时,必须制定读写的对象类型;读取的类型也必须定义了,输入运算符。

istream_iterator in_iter(cin);

istream_iterator eof; // 可当作尾后迭代器

vector vec(in_iter,eof);  // 从流中读取的数据被用来构造vec

accumulate(in, eof ,0); // 从标准输入读取的值的和

使用懒惰求值:

istream_iterator绑定一个流时,直到我们使用迭代器时才真正读取。

ostream_iterator:

该迭代器可接受第2个参数,该参数必须是C语言风格的字符串。

ostream_iterator out_iter(cout," ");

*和++对ostream_iterator对象不做任何事情。

通过调用copy来打印vec中的元素:copy(vec.begin(),vec.end(),out_iter);

ps:若某种类型,定义了>>和<<,即可定义,istream_iterator和ostream_iterator。

 

 

你可能感兴趣的:(C++,Python学习笔记)