C++primer阅读笔记---------泛型算法

该博客用于记录自己在阅读过程中不懂的知识点,很少用到但比较重要的知识点以及模棱两可的知识点


为什么要叫泛型算法,因为它们实现类一些经典算法的公共接口,并且适用于多种不同类型的元素和多种容器,因为它们是作用在迭代器上的,本身不会执行容器操作


标准库算法---find
    find(vec.cbegin(), vec.cend(), val)     //查找vec中val的位置,未找到 则返回vec.cend()

除了少数例外,标准库算法都对一个范围的元素进行操作,所以往往前2个参数表示范围内首元素和尾元素的后一个元素

    accumulate(vec.cbegin(), vec.cend(), val) 累加,val决定了使用哪个加法运算符以及返回值类型,当其是字符串操作时,如果val是string类型的,也可以成功,但如果是字面值字符串就不行,因为默认保存字面值字符串的是const char*,而char并没有定义+运算符

    equal(vec1.cbegin(), vec1.cend(), vec2.begin())     对比1范围内的元素是否和2相同,保证第二个容器元素数量大于等于第二个容器

fill(vec.cbegin(), vec.cend(), val) 将范围内所有元素设为val
fill_n

通过迭代器赋值时,迭代器指向的位置的元素被赋值,而通过插入迭代器赋值,值被插入容器

back_inserter接受一个容器的引用,返回一个插入迭代器,通过此迭代器赋值时,就会调用push_back将元素添加进容器
auto it = back_inserter(vec);
*it = 42; //42被插入vec中了

通常使用back_inserter创建一个迭代器,作为算法的位置来使用
fill_n(back_inserter(vec), 10, 0);
当第一个元素是一个迭代器时,改变它所指向的元素开始的10个元素的值为0,而如上文所说,这里相当于调用了10次push_back添加了10个0元素

内置数组的拷贝:
int a1[] = {1, 2, 3, 4, 5};
int a2[sizeof(a1) / sizeof(*a1)];
auto ret = copy(begin(a1), end(a1), a2); ret指向a2的尾元素的后位置

replace(vec.begin(), vec.end(), 0, 42) //将容器中的所有0改为42
拷贝版本:
replace_copy(vec.begin(), vec.end(), back_inserter(vec2), 0, 42);
vec不变,将替换后的版本存入vec2中

vector<string> vec1 = {"aa", "aa", "cc", "bb", "cc"};
sort (vec1.begin(), vec1.end());
auto end_unique = unique(vec1.begin(), vec1.end());
排序后,重复的元素都会挨在一起,而unique则会使得所有不重复的元素都放在前面(这的条件是unique只会移动挨在一起的重复的元素), end_unique指向不重复元素序列的后面一个元素
vec1.erase(end_unique, vec1.end());
由于算法并不能执行容器的操作,所以此时用erase将后面的元素删除就达到了去重的目的

find_if(vec.begin(), vec.end(), predicate) predicate是一个二元谓词

可调用对象:可以使用调用运算符(())则为可调用对象(函数,函数指针,重载了调用运算符的类(挖坑), lambda表达式)
可以将lambda表达式理解为一个未命名的内联函数
[捕获列表](参数列表) -> 返回类型 {函数体}
捕获列表和函数体不能省略
auto f = [] {return 42;}; //定义类一个f可调用对象,调用时和普通 函数一样f();
lambda表达式不能有默认参数
lambda表达式可以出现在一个函数中,并且使用它的局部变量,此时就会用到捕获列表
auto wc = find_if(vec.begin(), vec.end(),
[sz] (const string &a) {return a.size() >= sz;});
由于find_if需要的是一元谓词,而如果我们需要找出容器中长度大于size的字符串,那么谓词就需要传入一个字符串以及一个size,这显然不是一元谓词,所以我们使用lambda表达式来解决,lambda表达式使用所在函数的局部变量需要捕获,但使用更外层的就不需要,值捕获的前提是可以拷贝,lambda表达式可以理解为一个未命名的类创建的未命名的对象,还有一种捕获方式是引用捕获,还有一种方式是隐式捕获,不需要在捕获列表中写出要捕获的值,让编译器根据函数体的代码推断需要捕获哪些局部变量,但此时你得告诉编译器是采用值捕获还是引用捕获,捕获列表中分别填 “=”和 “&”,当隐式捕获是“=”时,可以与引用捕获混用,“&”同理,隐式捕获必须写在最前面

foreach前两个参数接受一个范围,第三个参数是一个谓词

可变lambda,对于值捕获的lambda如果想要改变捕获的值,就需要在参数列表后加上mutable关键字

如果一个lambda表达式包含return语句以外的任何语句并且没有指定返回值类型,则编译器认为返回值为void。如果只有一条return语句,编译器会自动推断它的返回值类型

除了使用lambda表达式将多余的参数传递给谓词,还可以使用一个叫bind的函数
auto newCallable = bind(callable, arg_list);
当调用newCallable时,newCallable会调用callable并将arg_list传递给他,bind中还会用到一个叫占位符的东西用_n表示,于是find_if还可以写成
auto wc = find_if(vec.begin(), vec.end(), bind(check_size, _1, sz));
bind生成了一个可调用对象, 当调用这个可调用对象时,就会调用check_size,同时将vec中的元素和sz传递给他


bind的另外一种巧妙的方法是它可以轻易的改变参数的位置,从而改变函数的用法
sort(vec.begin(), vec.end(), cmp); //vec中的元素从小到大排
sort(vec.begin(), vec.end(),bind(cmp, _2, _1)); //大到小

上文中提到过,bind传递多的参数时(不用占位符的参数),其实是用到拷贝的方法,万一某些类型不支持拷贝(输入输出流)怎么办呢, 标准库中提供了两个ref/cref(const版本)函数用于返回一个可拷贝的对象(引用)
auto wc = find_if(vec.begin(), vec.end(), bind(check_size, _1, ref(sz)));
//这里假设sz不能拷贝
流迭代器 //绑定在流上,用来遍历所关联的IO流
istream_iterator;
ostream_iterator;
使用流迭代器,我们可以用泛型算法操作流,
istream_iterator<int> in(cin), eof;
cout << accumulate(in, eof, 0) << endl;

移动迭代器(挖坑)

两个链表容器list和forward_list定义了其专有的成员函数版本的算法,特别是sort,merge,remove,reverse,unique等,就拿sort来说,普通版本的算法需要交换两个元素位置,而在链表中这是代价很高的行为,取而代之的是为们可以通过改变其中的链接关系来实现排序,这样可以有效的提供效率

lst.merge(lst2); //将两个有序的链表合并,合并后lst2变为空
lst.merge(lst2, cmp);

lst.remove(val);
lst.remove_if(pred); //调用erase删除

lst.reverse(); //反转

lst.sort(); //排序
lst.sort(cmp);

lst.unique(); //删除连续拷贝
lst.unique(pred); //删除连续的并且使二元谓词为真的元素

lst.splice(p, lst2); //将lst2的所有元素移动到p之前
flst.splice(p, lst2); //p之后
//lst2变为空,lst和lst2类型必须相同,且不能是同一个链表

(p, lst2, p2) //list调用时移动p2指向的元素,forward_list调用时 移动p2之后的元素,目标位置和上面相同,可以是相 同链表, lst2变为空

(p, lst2, b, e)
链表的多数算法版本和普通版本是相似的,但链表版本会改变底层的容器

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值