C++中lambda表达式的值传递和引用传递到底有什么区别?类中实现与main函数中实现有什么区别?

问题遇到的现象和发生背景

C++中lambda表达式的值传递和引用传递到底有什么区别?

初学C++的时候,学到函数形参可以通过值传递和引用传递这两种方式。如果函数实现中让形参参与了运算,那么如果通过值传递,形参没有发生改变,如果用引用传递,则形参发生改变,此为背景。

然后我在用lambda表达式的时候发现一个神奇的现象,就是如果在类中用ambda表达式,无论值传递和引用传递,都可以改变形参的值。我贴一下代码,

问题相关代码,请勿粘贴截图

class A { //声明类A
public:
int a = 10; //声明变量a
void B1(int a) { //声明一个值传递的普通函数
a += 5;
}

void B2() {     //声明一个函数,用lambda表达式值传递来实现
    [=]() {a += 5; }();
}

};

void main() { //测试
A a1; //分别创建两个对象
A a2;
a1.B1(a1.a); //a1调用B1()后,打印a1.a
cout << "a1中的a=" << a1.a << endl;

a2.B2();        //a2调用B2()后,打印a2.a
cout << "a2中的a=" << a2.a << endl;
运行结果及报错内容

img


可以看到,a1调用B1()后,a=10,没有改变,符合值传递不改变形参值的结论;但是a2调用B2()后,a=15,说明发生了改变,我lambda表达式中无论用‘=’还是‘&‘都不影响结果。

然后继续探究,发现一个更有意思的事情:
就是如果我的lamda不写在类中,直接先在main函数中,则必须用"=";否则报错1.

img

如果改写&则不报错,并且结果是15:

img


img

然后如果声明变量改写为static,lambda用值传递则不报错,并且结果是15:

img


img

然后如果变量不写static,lambda表达式用mutable,则不报错,但结果是10:

img


img

我的解答思路和尝试过的方法
我想要达到的结果

所以我想问一下,为什么会这样。通过写在main函数中的lambda表达式可以知道,lambda其实是区别值传递和引用传递的,值传递确实不改形参,但是如果写在类里,效果等同于引用传递!这是为啥?

因为类成员函数中,用lambda进行隐式捕获,会捕获成员函数的局部变量,即this指针。也就是说,相当于会捕获this指针。不论你用值捕获,还是引用捕获,都可以通过this来访问其数据成员aa。
也就是说,[=](){aa += 5;}();会相当于下面代码

// 为避免名称遮掩,下面代码把成员变量a修改为aa
// class A的成员函数B2,隐含第一个参数是A* this,但这里this是一个右值
    void B2() {     //声明一个函数,用lambda表达式值传递来实现
        auto it = this;
        [it]() { it->aa += 5;}();
    }

// 写成其他形式(值捕获this)
    void B2() {     //声明一个函数,用lambda表达式值传递来实现
        [this]() { this->aa += 5;}();
    }
// 隐式引用捕获this
    void B2() {     //声明一个函数,用lambda表达式值传递来实现
        [&]() { this->aa += 5;}();
    }
// 隐式值捕获
    void B2() {     //声明一个函数,用lambda表达式值传递来实现
        [=]() { this->aa += 5;}();
    }

不过,this比较特殊,是一个右值,无法通过显式引用捕获,即[&this](){}不合法。this本身值也无法修改,但成员变量可以修改,因为整个过程你并没有捕获成员变量本身,而是捕获this。

lambda捕获列表,始终是针对局部变量的。static变量、全局变量,都不受此影响,不需要捕获。
lambda相当于是匿名函数,(表达式内部)默认不能修改值捕获的变量,如果要修改,必须在参数列表首加上mutable关键字修饰。另外,要在表达式内部改变捕获的值,该值本身必须是可修改的左值。因为通常来说,修改一个值传递给匿名函数的变量,没有意义,并不会影响原值。
因此,在main函数中用lambda,捕获的也是main函数局部变量。要在表达式内部修改值捕获的c,必须加上mutable,但不会影响原来main中的c的值;要影响原来捕获的c,就不能值捕获,而要引用捕获。
还有没有其他方法,通过值捕获的方式,修改c?
有的,可以参考上面值捕获this的方式,通过指针来修改c。

int c = 10;
int* p = &c;
[=]() { ++(*p);} ();
cout << "c=" << c << endl; // 打印11,而非10

void B1(int a) { //声明一个值传递的普通函数
a += 5;
}

类的B1函数想复杂了吧,因为形参名是a,所以函数中的a引用的就是形参a。给形参加5,改变的是形参的值。而形参定义的又是值,所以不会改变类成员a。