自学内容网 自学内容网

C++11bind、function、lambda详细讲解

一.lambda表达式

        关于lambda表达式,我之前是详细讲过的,现在我们只来做重点讲解(如果存在疑问可以回看我之前的作品)。

固定格式:

        []()->返回值{};([capture-list] (parameters) mutable -> return-type { statement}
capture-list:可以自写[],表示不传递任何变量到该作用域,当然也可以如下:

[capture-list]:不能省流(最基础为[])
----------------------------------------------------------------
[var]:表示值传递方式捕捉变量var
[=]:表示值传递方式捕获所有父作用域中的变量(包括this)
[&var]:表示引用传递捕捉变量var
[&]:表示引用传递捕捉所有父作用域中的变量(包括this)
[this]:表示值传递方式捕捉当前的this指针
-----------------------------------------------------------------
例子如下:
//1.[]不传递任何变量
//2.[=]表示以传值的方式传递作用域外面的所有变量
//3.[&}表示以传引用的方式传递作用域外面的所有变量
//4.[=,&a]表示a变量以传引用传递,其余变量都以传值传递
//5.[&,b]表示a变量以传值传递,其余都以传引用传递
//6.[a,&b]表示a以传值传递,b以传引用传递
//7.[this]传递父作用域中的this指针

(parameters):表示参数,如果不传参可以省略(模仿函数参数写即可)

mutable 这个不常用,相信大家了解不多,其是一个关键字,表示可变的,与const恰好相反,mutable是为了突破const的限制而设置的。被mutable修饰的变量,将永远处于可变的状态,即使在一个const函数中。例子如下:

class Test
{
public:
    Test(int count=0):_count(count){};
    //析构默认
    vois print() const
    {
        _count++;
        std::cout<<"hello world! this is "<< _count<< " times" << std::endl;
    }
private:
    mutable int _count;//记录输出次数
};

//对于该类对象,print函数是被const修饰的,但是我们想要改变_count的值,所以这种情况下就可以使用_mutable 关键字来使被修饰的变量可以改变

-> return-type:->后面加上返回值类型,如果是void,可以直接全部省略

{ statement}:类似函数体,如果不需要写内容,可以直接写{}

总结:

最简单的lambda表达式:
[]{];//当然没有任何实际意义,大家可以用其理解那些部分不可省略

例子:
int main()
{
    int a=10,b=20;
    auto func1=[=](int c)->int{return a+b-c;};//注意两个“;”,不要忘了函数体中的分号
    std::cout<<func1(15)<<std::endl;
    return 0;
}

补充知识:lambda表达式之间不能赋值,即使看起来类型相似(底层学习):

lambda表达式在底层中会被当成函数对象处理(仿函数),以VS为例,在vs底层会将灭一个lambda表达式
生成一个固定的uuid,每一个都不同,uuid就可以看做类名,而lambda表达式的类型在VS中会被认为是
lambda+uuid,因为uuid不同,每一个lambda表达式的类型都是唯一,因此不能相互赋值,即使如下:
auto f1 = []{cout << "hello world" << endl; };
auto f2 = []{cout << "hello world" << endl; };
这两个看起来类型相同的lambda表达式
实际上类型都是唯一的


二.function包装器

function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器
我们还是先知道如何使用再来讲为什么需要它?

//首先:头文件为:
#include <functional>
模版:
function<返回值(参数)> 包装器名(函数名等);
function<返回值(参数)> 包装器名=函数名等;
//主要使用情况如下4种:

//1.函数名(函数指针)形式
int sum(int x,int y){return x+y;};
void Test1()
{
    std::function<int(int,int)> func1=sum;
    std::cout<<func1(1,2)<<std::endl;;//通过func1调用sum函数
    //注意点:区分函数指针如何写:返回值类型(*函数指针名)(参数);
}
//2.函数对象(仿函数)
class Functor
{
public:
    int operator()(int a,int b)
    {
        return a+b;    
    }
};
void Test2()
{
    std::function<int(int,int)> func2(&Functor);
    //等价于:std::function<int(int,int)> func2=Functor();
    std::cout<<func2(2,3)<<std::endl;
}
//3.lambda表达式
void Test3()
{
    std::function<void(const string)> func3=[](const string str){
    std::cout<<str<<std::endl;};
    func3("hello world");
}
//4.类成员函数
class Count
{
    public:
        double del(double x,double y)
        {
            return x/y;
        }
}
void Test4()
{
    std::function<double(double,double)> func4=&Count::del;
    std::cout<<func4(3.2,2.0)<<std::endl;
}

当然,对于例四也可以将类名传递,但是要注意类要取地址!!!

        下面我们再来讲下为什么要在C++11中加入function包装器呢?

        目的是为了解决模版效率低下问题,模版为什么说效率低下,看下面这个语句:

ans=Func1(x);对于该语句Func1可能是函数指针,也可能是仿函数对象,更可能是lambda对象、函数名等等,如此多的类型,模版推演的效率自然就低下了,因此在C++11中引入了包装器function,这样我们通过function包装可以清晰的调用该调用的函数、lambda等,自然效率就高了。

包装器在实际运用时作用非常广泛,大家可以自行了解。

补充知识:

在该部分开始时,我们放出了包含如下语句

template <class Ret, class... Args>
class function<Ret(Args...)>;

的照片,现在补充解释下"..."的作用:

1.在class中"...”位于模版名前面,表示可变参数列表

2.在运用Args时在其后加“...”表示对参数的可变化



三.bind1st/bind2nd/bind

        上面三个都是绑定器,第一个和第二个是STL中提供的,bind则是C++11引入boost中的内容提出的,下面我们下来学习前两个:

bind1st简单来说就是将第一个参数绑定,这样就可以解决需要两个参数,但是函数形参只有一个的问题,具体例子:

#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>
int main()
{
std::vector<int> arr = { 1,25,7,21,66,10 };
sort(arr.begin(), arr.end(), std::greater<int>());//从大到小排序
for (auto a : arr)
{
std::cout << a << " ";
}
std::cout << std::endl;
//现在想在插入27
auto iter = find_if(arr.begin(), arr.end(), std::bind1st(std::greater<int>(),27));
std::cout << *iter << std::endl;//找到从大到小的25
//插入27
arr.insert(iter, 27);
for (auto a : arr)
{
std::cout << a << " ";
}
std::cout << std::endl;
return 0;
}

在该例子中我们需要注意:

1.bind1st第一个参数通常是函数对象,第二个参数则是T val,通过绑定我们就可以讲greater仿函数所需要的两个参数变成一个,第一个参数是bind1st绑定的值,第二个为arr中值,从而变成找第一个小于27的位置迭代器

2.find_if函数的理解:前两个参数传迭代器,第三个参数可以是仿函数,也可以是bind,这也揭示了其本质上也是一个函数模版

该大体与bind1st相似,主要区别在于greater中第二个参数才是bind绑定的值,第一个是arr中的值,在绑定时写法和上面一样

​
bind1st(greater<int>(),27);
bind2nd(greater<int>(),27);
//这里位置一样的,别记错了!!!

​

大家应该会发现,我们绑定器无论是bind1st,还是bind2nd,都只能处理二元函数(两个参数),对于多元函数是没法处理的,这也是bind提出的原因,下面我们就来进入本文的最后一个话题:bind绑定器

大家一看这张图片就会明白我们之前的补充,可变参数,即bind的参数可变的,对于bind我们可以直接来学习内容,就没必要讲解其他部分(原因等):

        首先,bind有一个placeholders,有1-20左右,可以先通过bind绑定placeholders_1等,然后传递实际参数,例子如下:

//注意:头文件件还是<functional>
#include <functional>
//1.bind绑定函数:
int sum(int a,int b)
{
    return a+b;
}
void Test1()
{
    std::function<int(int,int)> func1=std::bind(sum,std::placeholders::_1,std::placeholders::_2);
    std::cout<<func1(1,2)<<std::endl;//输出:3
}
//2.bind绑定类成员函数:
class Functor
{
public:
int sub(int x, int y)
{
return x - y;
}
};
void Test2()
{
    std::function<int(int, int)> func2 = std::bind(&Functor::sub,             
    Functor(),std::placeholders::_1, std::placeholders::_2);
    std::cout << func2(1, 2) << std::endl;//结果是:-1
    std::function<int(int, int)> func3 = std::bind(&Functor::sub, Functor(), 
    std::placeholders::_2, std::placeholders::_1);
    std::cout << func3(1, 2) << std::endl;//交换_1和_2的顺序,结果是:1
}
//绑定类成员函数时,第一个参数为&类名::函数,第二个参数为类对象,后面为可变参数

最后,祝大家国庆快乐!!!


原文地址:https://blog.csdn.net/2301_79813267/article/details/142692732

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!