C++新特性:lambda表达式

C++ lambda表达式

C++ Lambda表达式是一种匿名函数,可以方便地创建和传递函数对象。Lambda表达式最初是在C++11标准中引入的,可以使用Lambda表达式来代替函数指针和Functor对象。Lambda表达式的语法形式如下:

[capture list] (parameter list) -> return type { function body }

其中,capture list是一个可选的捕获变量列表,用于在Lambda表达式中访问外部变量,parameter list是参数列表,与普通函数的参数列表类似,return type是返回值的类型,可以省略,如果省略则会自动推断,function body是Lambda表达式的主体语句,与普通函数语法类似。

Lambda表达式中的捕获列表可以是[]、[&]、[=]、[this]、[a,b]等形式,用于指定Lambda表达式内对外部变量的访问方式。其中,[]表示没有捕获外部变量,即不使用外部变量;[&]表示以引用方式捕获所有外部变量;[=]表示以值的方式捕获所有外部变量;[this]表示以指针形式捕获当前对象的this指针;[a,b]表示捕获外部变量a和b。

下面是一个使用Lambda表达式求向量元素和的示例:

#include 
#include 
#include 
using namespace std;

int main() {
    vector vec = {1, 2, 3, 4, 5};
    int sum = 0;
    // Lambda表达式
    for_each(vec.begin(), vec.end(), [&sum](int x){sum += x;});
    cout << "sum = " << sum << endl; //输出 sum = 15
    return 0;
}

在这个例子中,for_each算法内是一个Lambda表达式,对于vec中的每个元素,Lambda表达式都会执行一遍,将sum的值累加,并最终输出sum的值。

Lambda表达式参数详解
Lambda捕获列表
​ Lambda表达式与普通函数最大的区别是,除了可以使用参数以外,Lambda函数还可以通过捕获列表访问一些上下文中的数据。具体地,捕捉列表描述了上下文中哪些数据可以被Lambda使用,以及使用方式(以值传递的方式或引用传递的方式)。语法上,在“[]”包括起来的是捕获列表,捕获列表由多个捕获项组成,并以逗号分隔。捕获列表有以下几种形式:

[]表示不捕获任何变量
auto function = ([]{
std::cout << “Hello World!” << std::endl;
}
);

function();
1
2
3
4
5
6
[var]表示值传递方式捕获变量var

int num = 100;
auto function = ([num]{
std::cout << num << std::endl;
}
);

function();
1
2
3
4
5
6
7
[=]表示值传递方式捕获所有父作用域的变量(包括this)

int index = 1;
int num = 100;
auto function = ([=]{
std::cout << "index: "<< index << ", "
<< "num: "<< num << std::endl;
}
);

function();
1
2
3
4
5
6
7
8
9
[&var]表示引用传递捕捉变量var

int num = 100;
auto function = ([&num]{
num = 1000;
std::cout << "num: " << num << std::endl;
}
);

function();
1
2
3
4
5
6
7
8
[&]表示引用传递方式捕捉所有父作用域的变量(包括this)

int index = 1;
int num = 100;
auto function = ([&]{
num = 1000;
index = 2;
std::cout << "index: "<< index << ", "
<< "num: "<< num << std::endl;
}
);

function();
1
2
3
4
5
6
7
8
9
10
11
[this]表示值传递方式捕捉当前的this指针

#include
using namespace std;

class Lambda
{
public:
void sayHello() {
std::cout << “Hello” << std::endl;
};

void lambda() {
    auto function = [this]{ 
        this->sayHello(); 
    };

    function();
}

};

int main()
{
Lambda demo;
demo.lambda();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[=, &] 拷贝与引用混合

[=,&a,&b]表示以引用传递的方式捕捉变量a和b,以值传递方式捕捉其它所有变量。

int index = 1;
int num = 100;
auto function = ([=, &index, &num]{
num = 1000;
index = 2;
std::cout << "index: "<< index << ", "
<< "num: "<< num << std::endl;
}
);

function();
1
2
3
4
5
6
7
8
9
10
11

1
[&,a,this]表示以值传递的方式捕捉变量a和this,引用传递方式捕捉其它所有变量。

不过值得注意的是,捕捉列表不允许变量重复传递。下面一些例子就是典型的重复,会导致编译时期的错误。例如:

[=,a]这里已经以值传递方式捕捉了所有变量,但是重复捕捉a了,会报错的;

[&,&this]这里&已经以引用传递方式捕捉了所有变量,再捕捉this也是一种重复。

Lambda表达式的应用

Lamdba表达式应用于STL算法库

Lambda表达式可以方便地应用于STL算法库中,让我们看几个例子:

  1. for_each算法

for_each算法用于遍历容器中的每个元素,并应用指定的操作,可以使用Lambda表达式指定所需的操作。

std::vector v{ 1, 2, 3, 4, 5 };

// 使用Lambda表达式计算并打印每个元素的平方
std::for_each(v.begin(), v.end(), [](int i) {
    int square = i * i;
    std::cout << square << " ";
});
  1. sort算法

sort算法用于对容器中的元素进行排序,可以使用Lambda表达式指定排序规则。

std::vector v{ 5, 2, 4, 1, 3 };

// 使用Lambda表达式指定降序排列
std::sort(v.begin(), v.end(), [](int a, int b) {
    return a > b;
});
  1. count_if算法

count_if算法用于计算容器中满足特定条件的元素数量,可以使用Lambda表达式指定所需的条件。

std::vector v{ 1, 2, 3, 4, 5 };

// 使用Lambda表达式计算偶数元素的数量
int count = std::count_if(v.begin(), v.end(), [](int i) {
    return i % 2 == 0;
});

Lambda表达式使得STL算法库更加灵活和可定制化,可以根据实际需求进行不同的操作。

Lamdba表达式应用于短小不需要复用函数场景

Lambda表达式的一个特点就是它可以用于短小的函数场景,不需要定义一个复杂的函数或函数对象,更加方便和简洁。

比如,当我们需要对一个集合中的值进行一些简单的转换或过滤时,可以使用Lambda表达式直接在函数调用时定义:

std::vector v{ 1, 2, 3, 4, 5 };

// 将v中的所有元素都加上1,并只保留偶数元素
std::transform(v.begin(), v.end(), v.begin(), [](int i) {
    return i % 2 == 0 ? i + 1 : i;
});

// 输出修改后的v
for (int i : v) {
    std::cout << i << " ";
}

在上面的例子中,Lambda表达式被用来定义一个简单的转换操作,并在std::transform函数中直接使用,从而避免了定义一个短暂使用的函数对象。

Lambda表达式的灵活性也使得它可以与其他语言特性结合使用,比如C++11引入的auto关键字,可以进一步简化代码:

std::vector v{ 1, 2, 3, 4, 5 };

// 将v中的所有元素平方,并只保留大于10的元素
auto f = [](int i) { return i * i; };
auto g = [](int i) { return i > 10; };
std::transform(v.begin(), v.end(), v.begin(), f);
auto newEnd = std::remove_if(v.begin(), v.end(), std::not1(g));
v.erase(newEnd, v.end());

// 输出修改后的v
for (int i : v) {
    std::cout << i << " ";
}

在这个例子中,Lambda表达式被用来定义两个操作函数,其中一个用于平方转换,另一个用于判断是否大于10。使用auto关键字推导出的函数类型和STL算法库的类型推断机制,使得这段代码更加简洁,没有冗余的函数对象定义和类型信息。

Lamdba表达式应用于多线程场景

Lambda表达式在多线程编程中也有广泛应用,特别是和C++11引入的std::thread和std::async等线程库一起使用。

一般来说,我们需要将Lambda表达式作为线程函数传入std::thread的构造函数中,例如:

#include 
#include 

int main() {
    std::thread t([](){
        std::cout << "Hello from thread!" << std::endl;
    });

    t.join();

    return 0;
}

在这个例子中,Lambda表达式被作为线程函数传入std::thread的构造函数中。当std::thread对象的join函数被调用时,这个Lambda表达式被执行,在新的线程中打印出一句话。

和std::thread的使用类似,std::async也可以接受一个Lambda表达式作为异步操作的函数:

#include 
#include 

int main() {
    auto f = std::async(std::launch::async, []{
        return 42;
    });

    std::cout << "The answer is: " << f.get() << std::endl;

    return 0;
}

在这个例子中,std::async函数创建了一个异步操作,并返回一个std::future对象,用于获取异步操作的结果。这个异步操作是由Lambda表达式来定义的,返回一个整数值42。在主线程中,std::future对象的get函数被调用等待异步操作完成,并获取其返回值,再将其打印出来。

需要注意的是,在多线程编程中,要特别注意线程安全和竞态条件等问题,以避免出现潜在的并发错误和不确定行为。

Lamdba表达式应用于函数指针与function

Lambda表达式也可以用于替代函数指针和std::function对象。在C++11之前,如果想要将一个函数传递给另一个函数,通常需要使用函数指针或std::function对象,例如:

#include 
#include 

void print_num(int n) {
    std::cout << "The number is: " << n << std::endl;
}

void call_with_42(std::function func) {
    func(42);
}

int main() {
    call_with_42(&print_num);

    return 0;
}

在这个例子中,我们定义了一个名为print_num的函数,它的参数是一个整数,用于将这个整数打印到标准输出上。另外,我们还定义了一个叫做call_with_42的函数,它的参数是一个std::function对象,用于调用该对象所代表的函数,并将整数值42作为参数传递给它。在main函数中,我们使用&print_num将print_num函数的地址传递给call_with_42函数。

使用Lambda表达式,我们可以更加简洁地完成上述任务,例如:

#include 
#include 

void call_with_42(std::function func) {
    func(42);
}

int main() {
    call_with_42([](int n){
        std::cout << "The number is: " << n << std::endl;
    });

    return 0;
}

在这个例子中,我们直接将一个Lambda表达式作为参数传递给了call_with_42函数。这个Lambda表达式接受一个整数参数n,并将其打印到标准输出上。

可以看到,使用Lambda表达式可以让我们更加方便地定义匿名函数,无需显式地定义函数并传递函数指针或std::function对象。Lambda表达式也更加灵活,可以捕获外部变量,拥有更多的语言特性。

Lamdba表达式在QT中的应用

Lambda表达式在Qt中的应用非常广泛,Qt中的信号槽机制是其中一个主要的应用场景。

在早期的Qt版本中,我们需要手动将信号连接到槽函数,例如:

connect(button, SIGNAL(clicked()), this, SLOT(onButtonClicked()));

在这个例子中,我们将信号button的clicked()连接到槽函数onButtonClicked()。当button被点击时,onButtonClicked()函数将被调用。

在C++11中,我们可以使用Lambda表达式来替代手动定义槽函数,例如:

connect(button, &QPushButton::clicked, [=]() {
    QMessageBox::information(this, "Title", "Message");
});

在这个例子中,我们将信号button的clicked()连接到一个Lambda表达式,这个Lambda表达式会在button被点击时被调用,弹出一个信息框。

使用Lambda表达式,我们不再需要手动定义和命名槽函数,极大地简化了代码。此外,Lambda表达式可以轻松地访问外部变量,使得信号槽函数的编写更加方便和灵活。

除了信号槽机制,Lambda表达式还可用于Qt的算法和容器类中,例如:

QStringList list = {"One", "Two", "Three", "Four"};
auto it = std::find_if(list.begin(), list.end(), [](const QString& str) {
    return str.contains("O");
});

if (it != list.end()) {
    qDebug() << "Found string: " << *it;
}

在这个例子中,我们使用了std::find_if算法来查找包含字母’O’的字符串。Lambda表达式被用作谓词函数,用于判断每个字符串是否符合条件。

需要注意的是,在使用Lambda表达式时,一定要记得检查Qt的兼容性,遵循Qt的线程和内存管理规则,以避免潜在的问题。

Lambda表达式的优缺点
Lambda表达式的优点
1.可以直接在需要调用函数的位置定义短小精悍的函数,而不需要预先定义好函数

2.使用Lamdba表达式变得更加紧凑,结构层次更加明显、代码可读性更好

Lambda表达式的缺点
Lamdba表达式语法比较灵活,增加了阅读代码的难度
对于函数复用无能为力

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