谓词与lambda、bind

谓词(predicate)

标准库中定义了一类可以“定制操作”的算法函数,这类函数接收一个参数,这个参数与以往我们所认识的参数不同,它是一个可调用的对象,其返回结果是一个能用作条件的值。
这个参数就是谓词。 我们通过谓词来定制特定的操作。
谓词分为一元谓词二元谓词,表示可调用表达式接收一个还是两个参数。
谓词的使用例子:

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

sort(words.begin(),words.end(),isShorter());
//isShorter函数就是一个谓词,接受什么样的谓词(一元还是二元)由算法本身决定,且必须严格遵守
//如果希望使用更多的参数,可以使用lambda表达式和标准库函数bind

可调用对象

可调用对象就是可以对其使用调用运算符的对象,有下面四四种:

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

如果希望使用更多的参数,可以使用lambda表达式和标准库函数bind


lambda表达式

lambda表达式是一个可调用的代码单元,可以定义在函数内部,标准形式如下

[capture list](parameter list) -> return type { function body }
  • cpature list
    捕获列表
    捕获并可以使用其所在函数中定义的局部变量
    可以为空
//example
int main()
{
    int a = 10;    //main()中定义的局部变量
    auto lbd = [a](int b) ->int {return a+b;};
    //捕获了所在函数main()中定义的局部变量 a
    cout<<lbd(5)<<endl;      //输出15
    return 0;
}
  • parameter list
    参数列表
    和函数的参数列表一致
    可以为空
int a = 10;
auto f1 = [a](){ return a; };
auto f2 = [a] {return a; };    //省略参数列表,两种形式都可以
  • return type
    返回类型
    必须用尾置类型
    可以为空
    如果缺省不写,编译器根据return语句返回的类型自行推断
    ※ 如果lambda函数体中有除了return以外的其他语句,且返回类型未指定,则返回类型为void
// 这个例子在visual studio2017中可以编译并正确运行,可能为编译器的优化
[](int a) 
{
    ++a;
    return a;         //未指定返回类型,编译器推断为void,实际返回a为int,编译错误
}
  • function body
    函数体
    就是函数体呀

使用捕获

这个问题下有三个重要概念:值、引用捕获隐式捕获可变捕获

值、引用捕获

和参数传递相似,捕获也有值捕获和引用捕获两种方式:

  • 值捕获
    拷贝值,对拷贝值的修改不会影响原变量
// 值捕获
int v1 = 10;
auto f1 = [v1]()  
{
    ++v1;         //不会影响局部变量v1的值
}
  • 引用捕获
    捕获对象的引用
    注意指明引用捕获的写法
//引用捕获
auto f2  = [&v1]()     //指明捕获的是v1的引用,不是捕获v1的地址
{                      //实际捕获列表只能填局部变量名,或用&指明是引用
                       //并不像填写参数那样可以用取地址和取值运算符(&、*)
    ++v1;              //真实改变了v1的值
}

隐式捕获

我们可以不用写出捕获哪些变量,而在函数体中直接使用它们,编译器会推断出要捕获哪些变量

但仍有几条规定

  • 与空捕获区分
wc = find_if(words.begin(),words.end(),[=](const string &s){ return s.size() >= sz;});

//捕获列表中填写了一个 “=” 告诉编译器我们使用隐式捕获而不是空捕获
// “=” 表示采用值捕获 

捕获列表中填写 “=”表示采用值捕获“&”表示采用引用捕获
如果希望使用混合捕获模式,只需要在捕获列表的第一位填入基本的捕获模式,在第二位填入例外的捕获变量表

//混合捕获
int v1,v2,v3;
auto f = [=,&v3](){
    ++v1;
    ++v2;
    ++v3;
};
//基本捕获模式是值捕获,根据函数体推断出要捕获v1,v2,v3,然后第二位指定对v3采用引用捕获

 可变捕获

值捕获一个局部变量后,实际上是拷贝了这个变量的值,函数体中无法对这个值进行修改。
使用mutable后可以修改这个拷贝值(依然不对原变量产生影响)

int a = 10;
auto f1 = [a](){return ++a;};               //error,不可以修改a(拷贝)的值
auto f2 = [a]() mutable{return ++a;};       //正确
a = 999;
cout<<a<<endl
    <<f2()<<endl;
//输出     999
//         11

从上面的例子我们可以看出:

  • f2中a(拷贝)的值就是捕获时拷贝的变量a的值,这符合值捕获的规律,在lambda定义时捕获
  • f2中对a(拷贝)的修改,修改的是拷贝的副本,不会影响原变量

虽然mutable可以修改值捕获的值,但与引用捕获有本质不同,要注意区分



参数绑定 bind

bind是一个标准库函数,定义在头文件functional
调用bind的一般形式为:

auto newCallable = bind(callable,arg_list);
  • newCallable
    bind生成一个可调用对象
  • callable
    bind接受一个可调用对象,不妨暂且理解为一个函数
  • arg_list
    arg_list就是传递给callable的参数表,个数必须与callable的参数个数一致
    arg_list分为两部分,一是占位符,形如_n;二是绑定参数,与newCallable调用时的参数无关,一经声明就与callable绑定

下面一个实例帮助理解

//callable原型,使用两个参数
check_size(const string &s,const int sz)
{
    return s.size()<sz;
}

//使用bind的目的便是实现只用一个参数达到相同的目的

//newCallable完整声明
auto newCallable = bind(check_size,_1,sz);  //args_list就是 _1,sz 

//通过调用newCallable实际调用callable
string A = "hello";
int sz = 5;
bool b1 = newCallable(A);   //这句等同于 调用check_size(A,sz),
                            //newCallable的第一个参数A被填入占位符_1的位置

//bind作为谓词的使用实例
auto wc = find_if(words.begin(),words.end(),bind(check_size,_1,sz));

其中占位符_n定义在名字空间 std::placeholders

bind的参数映射

定义一个bind

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

把可调用对象function看作一个函数,可以推断出function一定有5个参数
形如

return type function(para1,para2,para3,para4,para5);

bind的args_list中有两个占位符,可以推断g接收两个参数

这样调用g

g(A,B);

然后g的参数按照占位符填入function中

function(a,b,B,c,A);

绑定引用参数

用一个例子说明占位符绑定参数传值的不同

void Change(int &a,int &b)           //两个参数均为传引用
{
    ++a;
    --b;
}

int main()
{
    int a = 0;
    int b = 0;
    auto g = bind(Change, a, _1);   //绑定引用参数版本为 
                                    //auto g = bind(Change,ref(a),_1);
    g(b);
    cout << a << endl
        << b << endl;
    system("pause");
    return 0;
}

绑定非引用参数输出结果

非绑定引用参数输出结果

绑定引用参数输出结果
绑定引用参数输出结果

原因在绑定参数进行了两次参数传递,而占位符只进行了一次参数传递

change函数定义a,b参数都通过引用传参
而a是绑定参数,进行了两次传参,第一次(调用g,将绑定参数a传给Change函数)若是值传递,则第二次(从Change参数表传入函数内部,已经定义为引用传参)也只是引用第一次的值拷贝,没有真正引用到最开始的变量

下面一个例子说明这个问题

void Change1(int &a)        //传引用
{
    ++a;
}
void Change2(int a)     //传值
{
    Change(a);          //将值拷贝的引用传入Change1
}
int main()
{
    int a = 0;
    Change2(a);         //调用change2,传入a的值
    cout << a << endl;  //输出0,a的值并为改变
    system("pause");
    return 0;
}

如果希望传入const引用,可以使用cref函数

cref与ref都定义在头文件 functional中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值