《C++ Primer》读书笔记第十章-1-初识泛型算法 And Lambda

笔记会持续更新,有错误的地方欢迎指正,谢谢!

概述

容器是一个模板类,是在数据类型之上的那一层。C++提供一组算法,独立于任何特定的容器,这些算法是类型无关的,是泛型(generic)的。

比如:find算法通过迭代器去依次访问元素,找到就停止,直到尾后元素,这些都不依赖于容器所保存的元素类型或者是容器类型,它就是迭代器去访问,也就是 迭代器让算法不依赖于容器

算法依赖于元素类型的操作。因为算法在执行时肯定要操作元素,比如我们的find算法,它至少要使用==符号来判等,所以就要求元素类型支持 ==,这里的类型是int,当然支持了。(又比如:string支持加法。)

总结:算法只会运行于迭代器之上,迭代器让算法不依赖于容器,但依赖于元素类型的操作(加减乘除等等操作)。

初识泛型算法

我们学习的泛型算法都对一个范围内的元素进行操作,这个范围由两个参数表示,前一个指向要处理的第一个元素,后一个指向尾元素的后一个位置,用这两个迭代器参数表示左闭右开区间。

算法分类:是否读取元素、改变元素、重排元素顺序。

只读算法

只读取其输入范围内的元素,从不改变元素,最好用常量迭代器。例如之前的find函数。
例子:

int sum = accumulate(vec.cbegin(), vec.cend(), 0);
//accumulate函数是对容器内元素求和,这个算法要求容器内的元素类型支持加法。

再来一个例子,string也定义了加法,所以可以对string求和:

string sum = accumulate(vec.cbegin(), vec.cend(), string("")); 
//泛型,跟上面一样

写容器元素的算法

举个例子:
fill(vec.begin(), vec.end(), 0); //将所有元素重置为0

replace算法:

replace(list.begin(), list.end(), 0, 42); //把list中所有的0改为42

//函数重载:
replace(list.cbegin(), list.cend(), back_inserter(ivec), 0, 42);
//list不变。而ivec包含list的一份拷贝,并且ivec里的0都变成了42。

重排容器元素的算法

重排算法:去除容器重复元素的方法是,先调用重排算法sort和unique,再用容器操作erase。

假定要简化一个vector,使得里面保存的每个单词只出现一次:

//假设原来的vector为a, b, d, c, a, b
void Func(vector<string> &words)
{
    sort(words.begin(), words.end()); 
    //a,a,b,b,c,d

    auto end_unique = unique(words.begin(), words.end()); 
    //a,b,c,d,a,b

    //end_unique的位置就是第一个重复的元素位置(即第二个a处)
    words.eraser(end_unique, wors.end());
    //a,b,c,d

定制操作

很多算法都会比较元素,默认情况下,这类算法使用元素类型的<或==运算符来完成比较,但允许我们传递任何类型的可调用对象,用自定义操作代替默认运算符。
有两种情况可能需要我们多做一些工作:
1. 我们希望的排序顺序与<定义的顺序不同;
2. 我们保存的元素没有定义<运算符。

在这两种情况下,我们需要重载sort的默认行为。

向算法传递参数

谓词:一个可调用的表达式,返回结果是一个能用作条件的值,谓词可以接受一个或两个参数,分别称为一元谓词和二元谓词。
例子:

stable_sort(words.begin(), words.end(), isShorter); //按长度排序
//用stable_sort()稳定排序是为了让长度相同的单词还是保持字典序不变。

标准库中有一个算法find_if用来查找第一个具有特定大小的元素,它接受三个参数,前两个是一对迭代器表示输入范围,第三个参数是一个一元谓词,find_if算法对输入序列中每个元素调用这个谓词,它返回第一个使谓词返回非0值的元素。 看着挺好用的,那么问题在哪呢?问题就在第三个参数是一元谓词,这个谓词函数肯定要传给它两个参数,一个string和一个长度,这就有问题了,因为人家是一元谓词,无法接受两个参数,所以,here comes lambda.

lambda(兰木达)表达式

一个lambda表达式表示一个可调用的代码单元,我们可以理解为未命名的内联函数。

[capture list] (parameter list) -> return type {function body}
依次为:[捕获列表](形参列表)->返回类型{函数体}
捕获列表是lambda表达式所在函数中定义的局部变量的列表。
我们可以忽略形参列表和返回类型,但必须包含捕获列表和函数体:

auto f = [] {return 42;} 
//空捕获列表 表明此lambda不使用它所在的函数中的任何局部变量。
//定义了一个可调用对象f,不接受参数,也就没有参数的括号。返回42。
cout << f() << endl; //打印42

向lambda传递参数:

lambda不能有默认参数,这是规定。
作为一个带参数的lambda例子,我们来写一个与isShorter函数功能相同的lambda:

[] (const string &a, const string &b){ return a.szie() < b.szie();}

我们可以使用此lambda来调用stable_sort函数:

stable_sort(
            words.begin(), word.end(), 
            [] (const string &a, const string &b){ return a.szie() < b.szie();}
            );

使用捕获列表:

不要忘了我们为什么要引出lambda这个概念,我们现在就来解决这个问题:编写一个可以传递给find_if的可调用表达式,我们希望这个表达式能将输入序列中的每个string的长度与lambda所在函数中的sz参数进行比较。 我们是怎么来传递多余信息呢?答案就是捕获列表,一个lambda通过将局部变量包含在其捕获列表中来使用:

[sz] (const string &a){ return a.szie() >= sz; };
//sz是lambda表达式所在函数的变量,是lambda捕获来的。

使用这个lambda就可以搞定了:

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

lambda捕获和返回

捕获方式:

捕获是一种传参方式,分为值捕获和引用捕获。
举例:

//值捕获,前提是变量可以被拷贝。

void f1()
{
    size_t v1 = 24;
    auto f = [v1] { return v1; };
    v1 = 0;
    auto j = f(); //j = 24;
}

//引用捕获,必须保证在捕获时该变量是存在的,是否可变由是否是常量引用而定。

void f2()
{
    size_t v1 = 24;
    auto f = [&v1] { return v1; };
    v1 = 0;
    auto j = f(); //j = 0;
}

指定lambda返回类型:

到目前为止,我们所编写的lambda都只有一个return语句,所以我们还没指定过返回类型。规定:默认情况下,如果一个lambda函数体包含return之外的任何语句,则编译器假定此lambda返回void。
例子:

transform(vi.begin(), vi.end(), 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函数体包含return之外的if语句!编译器推断该lambda返回类型为void,但它返回了int。 我们可以通过指定lambda返回类型来修正它:

transform(vi.begin(), vi.end(), vi.begin(), 
        [](int i) -> int //就是这样指定返回类型的
        { 
            if(i<0){return -i;}
            else{return i;}
        });
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值