lambda表达式

lambda表达式

  • 概念和基本用法
    • 概念
    • 基本用法
  • 简单应用
    • for_each函数
    • list中的应用
  • 对象调用函数

概念和基本用法

概念

lambda表达式定义了一个匿名函数,并且可以捕获一定范围内得变量
语法[capture](params)opt->ret{body}
该表达式中:capture是捕获列表;params是参数表;opt是函数选项;ret是函数返回类型,body是函数体。
举例大家看一下怎么用:

int main() {
    auto fa = [](int a)->int {return a + a; };
    int num = fa(10);
    auto fb=[](int x,int y)->int{return x+y;}
    int tmp=fb(12,12);
    cout << num << " "<<tmp<<endl;
    return 0;
}

很显然其输出结果是20和24,其使用其实和函数是一样的,比如用指针或者引用作为参数:

int main() {
    int x = 10, y = 20;
    auto fa = [](int& a)->int {return a += 10; };
    auto fb = [](int* p)->int {return *p += 10; };
    int num = fa(x);
    cout << num << endl;
    int num1 = fb(&x);
    cout << num1 << endl;
    return 0;
}

当然可以使用内置内置类型也可以使用自己定义得类来。

基本用法

我们使用整型得包装类型为例

class Int
{
public:
    Int(int val = 0) :value(val) {
        cout << "Create Int" << endl; 
    }
    Int(const Int& it) :value(it.value) {
        cout << "Copy Crate Int" << endl;
    }
    Int& operator=(const Int&it) {
        if (this != &it) {
            value = it.value;
        }      cout << "operator=" << endl;
        return *this;
    }
    ~Int() {
        cout << "Destory Int" << endl;
    }

private:
    int value;
};

lambda表达式可以通过捕获列表捕获一定范围内的变量

  • [] :不捕获任何变量。
  • [&] :捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。
  • [=]:捕获外部作用域所有变量,并且作为副本在函数体中使用(按值捕获)。
  • [=,&foo]:按值捕获外部作用域中所有变量,按引用捕获foo变量。
  • [bar]:按值捕获bar变量,同时不捕获其他变量。
  • [tis]:捕获当前类中this指针,让lambda表达式拥有和当前类成员函数同样的访问权限。

我们举例来使用一下上面所有的捕获:
[]

int g_max=0;
void func() {
	int tmp=1;
    g_max += 10;
}
void func(int x) {
    g_max += 10;
    int a = 10, b = 20;
    auto fa = []()->int { g_max+=10;return g_max; };
    int num=fa();
}
int main() {
	func();
	return 0;
}

该代码中在函数func中使用lambda表达式对全局变量进行了操作,而捕获方式是不捕获任何变量,捕获是针对局部区域内的,所以不存在对全局变量进行捕获,可以在lambda表达式中对全局变量进行操作,但是如果将g_max变成a变量,就会报错,因为使用的是[],不捕获任何变量,但是可以使用形参和表达式函数体中的变量。
[=]

void func(int x, int y)
{
    Int a(10), b(20);
    auto fa = [=]()->void {
        int z = x + y;
        //x += 10;//err
        int c = a.GetValue();
        a.Print();
        //a.SetValue(100);//error

    };
    fa();
}
int main() {
    int a = 10, b = 20;
    func(a,b);
}

我们观察上面代码,用[=]的lambda表达式是按值捕获局部所有的变量,但是不能进行修改,只能读取,而且在lambda表达式中调用了哪个外部捕获的变量,是拷贝了一份副本来进行操作,不是从本身进行调用函数。我们可以发现上面的表达式函数体张x不能进行改变,也不能对对象a进行修改成员变量。也可以从运行结果看出:
lambda表达式_第1张图片
在调用lambda表达式结束的时候,拷贝的对象就会被析构。
[&]

void func(int x, int y)
{
    Int a(10), b(20);
    auto fa = [&]()->void {
        a.Print();
        a.SetValue(100);
        a.Print();
    };
    fa();
}
int main() {
    int a = 10, b = 20;
    func(a,b);
}

可以发现我们使用了该方法之后,可以对捕获的值进行修改,而且没有进行拷贝构造,也可以从运行结果看出:
lambda表达式_第2张图片
[bar,&va]

void func(int x, int y)
{
    Int a(10), b(20);
    auto fa = [a,&b]()->void {
        a.Print();
        int tmp = a.GetValue() + b.GetValue();
        cout << tmp << endl;
        b.SetValue(1);
    };
    fa();
}
int main() {
    int a = 10, b = 20;
    func(a,b);
}

我们可以发现只捕获了a,b变量,不会捕获其他变量,而且a只读,b可以进行修改。运行结果也可以看出:
lambda表达式_第3张图片
当然这些方法都是相通的,都可以进行套用,比如:[=,&x]:以值形式捕获局部所有变量,以引用形式捕获x变量;[&,x]:以引用形式捕获所有局部变量,以值得形式捕获x变量。
注意:以值得形式捕获的所有变量,只有在函数体中使用才会被拷贝一份副本来使用!!!

[this]
该函数在Int类内定义:

    void func(int a, int b) {//const Object* const this
        auto fa0 = []() {return 10; };
        auto fa1 = [a, b]() {return a + b; };
        auto fa2 = [=]() {
            this->Print();
            this->SetValue(200);
            this->Print();
            this->add();
        };
        fa2();
    }

可以发现我们可以通过传this指针对this之内的所有函数调用操作。
说了这么多,那么lambda表达式在编写程序中有哪些应用呢?又存在什么好处呢?

简单应用

for_each函数

我们在讲简单应用之前需要了解一下for_each函数,该函数用于,遍历数据,遍历一下调用一次函数。具体实现代码很简单:

template<class InputIt,class unaryFunction>
unaryFunction my_for_each(InputIt first, InputIt last, unaryFunction fy) {
    for (; first != last; ++first) {
        fy(&first);
    }
    return fy;
} 

简单的使用一下该函数:

void print(int x) {
    if (x % 2 == 0) {
        cout << x << endl;
    }
}
int main() {
    std::vector<int>ar = { 1,2,3,4,5,6,7,8,9,10 };
    my_for_each(ar.begin(), ar.end(), print);

}

我们可以发现通过该函数讲容器中的偶数进行了打印。这是怎么实现的呢?
我们首先要知道了解函数指针,是这样的,一个函数的函数名代表函数的 入口地址,函数名加括号代表调用该函数,我们传参也就是传的是print函数的函数首地址(函数指针),将函数指针传入之后通过形参加括号来调用函数。
这里可以简单的说一下函数指针,我们知道变量有不同的类型,其实函数也有类型,函数的类型是根据返回值和参数的不同区分的,
函数指针使用:

void it(int* a,int* b);
void jt(int* x,int* y);
//这两个函数其返回值和类型名都一样,那么久可以用函数指针:
void(*ls)(int,int)=nullptr;//函数指针ls,类型为void(int,int)类型,然后初始化为nullptr
ls=it;//函数指针指向it函数,
(*ls)(1,2);//调用函数
ls(1,2);//调用函数

为什么说for_each函数呢,他就可以使用我们的lambda表达式,不需要调用函数,因为lambda表达式可以理解为匿名的内敛函数,效率高。

int main() {
    std::vector<int>ar = { 1,2,3,4,5,6,7,8,9,10 };
    my_for_each(ar.begin(), ar.end(), [](int& x)->void {
        if (x % 2 == 0) cout << x << endl;
        });
}

也可以计算其中的偶数个数

int main() {
    std::vector<int>ar = { 1,2,3,4,5,6,7,8,9,10 };
    int count = 0;
    my_for_each(ar.begin(), ar.end(), [&count](int& x)->void {
        if (x % 2 == 0) count++;
        });
    cout << count << endl;
}

也可以这样计算:

class CountEven {
    int& count;
public:
    CountEven(int& c) :count(c) {}
    void operator()(int val) const {
        if (val % 2 == 0) ++count;
    }
};
int main() {
    std::vector<int>ar = { 1,2,3,4,5,6,7,8,9,10 };
    int count = 0;
    my_for_each(ar.begin(), ar.end(),CountEven(count));
    cout << count << endl;
}

list中的应用

我们创建一个list容器,并且初始化,删除其中的偶数,就可以用remove_if函数,该函数的参数中使用lambda表达式返回是否为偶数,是偶数就进行删除,不为是偶数就不删除。

int main() {
    list<int> ar = { 1,2,3,4,5,6,7,8,9,10 };
    for (auto& x : ar) { cout << x << " "; }
    cout << endl;

    //ar.remove(3);
    ar.remove_if([](auto& x)->bool {
        return (x % 2 == 0);
        });
    for (auto& x : ar) { cout << x << " "; }
    cout << endl;
    return 0;
}

而为什么vector一般不这样做呢?因为其底层是顺序表,删除元素不是尾删的话效率是很低的,使用vector删除只能这样删除,而且不建议这样的做法:

int main() {
    vector<int> ar = { 1,2,3,4,5,6,7,8,9,10 };
    for (auto& x : ar) { cout << x << " "; }
    cout << endl;

    //ar.remove(3);
    ar.erase(std::remove_if(ar.begin(),ar.end(),[](auto& x)->bool {
        return (x % 2 == 0);
        }));
    for (auto& x : ar) { cout << x << " "; }
    cout << endl;
    return 0;
}

对象调用函数

void func() { cout << "func" << endl; }
struct Foo {
    void operator()()const {
        cout << "Foo:operator()" << endl;
    }
};

struct Bar {

    using pFun = void(*)(void);
    static void func(void) {
        cout << "Bar::func==>static" << endl;
    }
    operator pFun() {//operator void(*)(void)
        return func;
    }
};

struct Test{
    int a;
    void memfunc(void) {
        cout << "Test::memfunc" << endl;
    }
};
int main() {
    void(*pfun)(void) = func;//定义函数指针,指向func函数
    pfun();//通过函数指针调用函数
    
    Foo foo;
    foo();//仿函数调用

    Bar bar;
    bar();//首先对象名加括号,系统会判断是不是仿函数(括号重载),
    //没找到括号重载就会调用强转,将func强转为函数指针,通过函数指针调用函数。
    //bar.operator Bar::pFun();
    void(Test:: * mpfun)(void) = &Test::memfunc;//定义了Test类内的函数指针,
    Test t;
    (t.*mpfun)();//使用函数指针调用函数,为了防止系统将其认为是成员函数,所以将其函数指针前加上解引用
}

为了增强代码的可用性,我们又引进了这样的写法:

int main() {
    std::function<void(void)>fra = func;
    fra();
    Bar bar;
    fra = bar;
    fra();
    Foo foo;
    fra = foo;
    fra();
    Test t;
    fra = std::bind(&Test::memfunc,t);
    fra();
}

类似于函数指针调用,而Test对象呢,是将其和对象绑定在一块来通过函数指针调用函数。

你可能感兴趣的:(C++,c++,算法,c语言)