• 定制操作


    向算法传递函数

    谓词

    谓词是一个可调用的表达式,其返回结果是一个能用作条件的值。标准库算法使用的谓词分为两类:

    • 一元谓词,意味着它们只接受单一参数。
    • 二元谓词,意味着它们有两个参数。

    接受谓词参数的算法对输入序列中的元素调用谓词,因此,元素类型必须能够转换为谓词的参数类型。

    接受一个二元谓词参数的sort:

    bool isShorter(const string &s1, const string &s2)
    {
    	return s1.size() < s2.size();
    }
    
    sort(words.begin(),words.end(),isShorter);
    

    排序算法

    将 words 按照大小重排后,希望具有相同长度的元素按照字典顺序排列,可以使用stable_sort 算法。

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

    lambda 表达式

    可以向算法传递任何可调用对象,对于一个对象或一个表达式,如果可以对其使用调用运算符,则称它为可调用对象或表达式。

    • 一个 lambda 表达式表示一个可调用的代码单元,可以将其理解为一个未命名的内联函数。
    • 一个 lambda 具有一个返回类型,一个参数列表和一个函数体。
    • 与函数不同,lambda 可以定义在函数内部。

    lambda 表达式的具体形式如下:

    [capture list](parameter list)->return type{function body}
    
    • capture list (补获列表)是一个 lambda 所在函数中定义的局部变量的列表。
    • return type 表示返回类型。
    • parameter list 表示参数列表。
    • function body 表示函数体。

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

    可以忽略参数列表和返回类型,但是必须永远包含补获列表和函数体。

    //定义一个lambda表达式,返回常量42
    auto f = [] { return 42; };
    //调用时使用调用运算符
    cout << f() << endl;
    

    如果忽略返回类型,lambda 会根据函数体中的代码推断出返回类型,但是如果lambda的函数体中包含任何单一return 语句之外的内容,且未指定返回类型,则返回 void

    向lambda传递参数

    调用一个 lambda 时会使用给定的实参来初始化形参,通常,实参类型与形参类型必须匹配。

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

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

    使用补获列表

    lambda 可以出现在一个函数中,通过将局部变量包含在补获列表中来指明将会使用这些变量。

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

    find_if 调用返回第一个长度不小于给定参数 sz 的元素。如果这个参数不存在,则返回 words.end() 的一个拷贝。

    打印 words 中长度大于等于 sz 的元素:

    for_each(wc,words.end(),
    	[](const string &s){cout<<s<<" ";});
    

    注意:

    补获列表只用于局部非静态变量,lambda 可以直接使用局部静态变量和它所在函数之外声明的名字。

    lambda 补获和返回

    值捕获

    变量的捕获方式也可以是指或引用。

    与传值参数类似,采用值捕获的前提是变量可以拷贝,与参数不同,被捕获的值是在 lambda 创建时拷贝,而不是调用时拷贝。

    void func1()
    {
    	size_t v1 = 42;
    	auto f = [v1] {return v1; };
    	v1 = 0;
    	auto j = f(); //j为42,f保存了创建它时的v1的拷贝
    }
    

    引用捕获

    void func1()
    {
    	size_t v1 = 42;
    	auto f2 = [&v1] {return v1; };
    	v1 = 0;
    	auto j = f2(); //j为0,f2保存了v1的引用而不是v1的拷贝
    }
    

    引用捕获与返回引用有着相同的问题和限制,如果采用引用捕获一个变量,就必须确保被引用的对象在 lambda 执行的时候是存在的。lambda 捕获的都是局部变量,这些变量在函数结束后就不付存在了,如果 lambda 可能在函数结束后执行,捕获的引用指向的局部变量已经消失。

    可以从一个函数返回 lambda,函数可以直接返回一个可调用对象,或者返回一个类对象,该类含有可调用对象的数据成员,如果函数返回一个 lambda ,则与函数不能返回一个局部变量的引用类似,此 lambda 也不能包含引用捕获。

    注意:

    • 捕获一个普通变量,通常情况下采用简单的值捕获,在此情况下,只需关注变量在捕获时是否有我们所需的值。
    • 如果捕获一个指针或迭代器或引用,就必须确保在 lambda 执行时,绑定到迭代器、指针、引用的对象仍然存在,另外需要注意lambda创建到执行时这些值可能被修改了。
    • 一般来说,尽量减少捕获的数据量,也尽量避免捕获指针或引用。

    隐式捕获

    可以让编译器根据 lambda 函数体中的代码来推断使用哪些变量:

    • = 指示编译器按值捕获。
    • & 指示编译器按引用捕获。
    //按值捕获,隐式捕获
    wc = find_if(words.begin(), words.end(), [=]
    	(const string &s) {return s.size() >= sz; })
    
    

    如果希望一部分变量按值捕获,其它变量按引用捕获,可以混合使用隐式捕获和显示捕获:

    //os隐式捕获,引用捕获方式,c显示捕获,值捕获方式
    for_each(words.begin(), words.end(), [&, c](const string &s) {os << s << c; });
    //os显式捕获,引用捕获方式,c隐捕获,值捕获方式
    for_each(words.begin(), words.end(), [=, &osc](const string &s) {os << s << c; });
    
    

    注意:

    • 当使用混合捕获模式时,捕获列表中的第一个元素必须是 & 或者 = 来指定默认的捕获方式。
    • 当使用混合捕获模式时,显示捕获的变量必须与隐式捕获的变量采用不同的方式,如果隐式捕获采用引用方式,则显示捕获必须采用值方式,如果,如果隐式捕获采用值方式,则显示捕获必须采用引用方式。

    可变 lambda

    对于一个值拷贝的变量,默认情况下 lambda 不会改变它的值,如果希望能够改变一个被捕获的变量的值,就必须在参数列表首加上关键字 mutable

    void func1()
    {
    	size_t v1 = 42;
    	auto f = [v1] () mutable {return v1++; };
    	v1 = 0;
    	auto j = f();
    }
    

    一个引用捕获的变量能否修改它的值则依赖于此引用指向的是一个 const 类型还是一个非 const 类型。

    void func1()
    {
    	size_t v1 = 42;
    	auto f = [&v1] () {return v1++; };
    	v1 = 0;
    	auto j = f();
    }
    

    指定 lambda返回类型

    默认情况下,如果一个 lambda 函数体包含 return 之外的任何语句,则编译器假定此 lambda 返回 void。与其他返回 void 的函数类似,被推断返回 voidlambda 不能返回值。

    返回一个序列中所有数据的绝对值:

    transform(vi.begin(), vi.end(), vi.begin(), [](int i) {return i < 0 ? -i : i; });
    

    上面的表达式是正确,但是如果改写成:

    transform(vi.begin(), vi.end(), vi.begin(), [](int i) {if (< 0)return -i; else return i; });
    

    将不能推断返回类型,编译器将返回 void 类型。

    当需要为 lambda 指定返回类型的时候,必须使用尾置返回类型:

    transform(vi.begin(), vi.end(), vi.begin(), [](int i) ->int {if (< 0)return -i; else return i; });
    

    参数绑定

    • 对于只在一两个地方使用的简单操作,lambda 表达式最有用,但是如果需要在很多地方使用,通常应该定义成函数,而不是多次编写 lambda 表达式。
    • 如果一个操作需要很多语句才能完成,通常使用函数更好。
    • 如果 lambda 的捕获列表为空,通常可以用函数来代替它;但是对于捕获局部变量的 lambda ,用函数来替换就不那么容易。

    例如:

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

    可以很容易写出一个函数:

    bool check_size(const string &s,string::size_type sz)
    {return s.size() >= sz;}
    

    但是不能将 check_size直接作为 find_if 的一个参数,因为 find_if 接受一个一元谓词,因此传递给 find_if 的可调用对象必须接受单一参数。为了使用 check_size 来代替此 lambda 必须解决如何向 sz 形参传递一个参数的问题。

    标准库 bind 函数

    bind 函数定义在头文件 functional 中,可以将 bind 函数看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表。

    bind 函数的一般形式为:

    auto newCallable = bind(callable,arg_list);
    

    当调用 newCallable 时,newCallable 会调用 callable,并传递给它 arg_list 参数。

    arg_list 中的参数可能包含形如 _n 的名字,其中 n 是一个整数,这些参数是占位符,表示 newCallable 的参数,它们占据了传递给 newCallable 的参数位置,数值 n 表示生成的可调用对象中参数的位置:_1newCallable 的第一个参数,_2newCallable 的第二个参数,以此类推。

    绑定 check_size 的 sz 参数

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

    bind 调用只有一个占位符,表示 check6 只接受单一参数。

    占位符出现在 arg_list 的第一个位置,表示 check6 的此参数对应 check_size 的第一个参数。此参数是一个 const string& 。因此,调用 check6 必须传递给它一个 string 类型的参数,check6 会将此参数传递给 check_size

    string s = "hello";
    bool b1 = check6(s); //check6 会调用check_size(s,6)
    

    如此,可以替换原来的 lambda 表达式:

    auto wc = find_if(words.begin(),words.end(),
    	bind(check_size,_1,sz));
    

    bind 调用生成一个可调用对象,将 check_size 的第二个参数绑定到 sz 的值。

    使用 placeholders 名字

    名字 _n 都定义在一个名为 placeholdaers 的命名空间中,而这个命名空间本身定义在 std 命名空间。为了使用这个名字,两个命名空间都需要写:

    using std::placeholders::_1;
    

    除了对每一个使用的名字单独 using 声明外,也可以统一声明:

    using namespace std::placeholders;
    

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

    如果第一个调用类似:isShorter(A,B) ,则第二个调用为:isShorter(B,A)

    绑定引用参数

    默认情况下,bind 的那些不是占位符的参数被拷贝到 bind 返回的可调用对象中,但是与 lambda 类似,有时对有些绑定的参数希望以引用 得方式传递,或是绑定的参数无法拷贝。

    for_each(words.begin(),words.edn(),[&os,c](const string) &s {os<<s<<c;});
    

    可以编写函数替换lambda表达式:

    ostream & print(ostream &os,cpnst string &s,char c)
    {
    	return os<<s<<c;
    }
    

    但是不能直接使用 bind 来代替 os 的捕获:

    //错误,ostream 不可以拷贝
    for_each(words.begin(),words.edn(),bind(print,os,_1,' '));
    
    

    如果想传递给bind一个对象而又不拷贝它,则必须使用标准库 ref 函数:

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

    ref 函数返回一个对象,包含给定的引用,此对象是可以拷贝的,标准库还有一个 cref 函数,生成一个保存 const 引用的类。

    refcref 也都定义在头文件 functional 中。

  • 相关阅读:
    JAVA多线程编程设计模式:总结
    JAVA多线程编程设计模式:Half-sync/Half-async(半同步/半异步)模式
    JAVA多线程编程设计模式:Pipeline(流水线)模式
    尚学堂Spring视频教程(七):AOP XML
    尚学堂Spring视频教程(六):AOP Annotation
    JAVA基础知识:网络
    JAVA基础知识:IO
    JAVA基础知识:容器
    Java基础知识:代理
    尚学堂Spring视频教程(五):Spring AOP
  • 原文地址:https://www.cnblogs.com/xiaojianliu/p/12497061.html
Copyright © 2020-2023  润新知