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;
然后继续探究,发现一个更有意思的事情:
就是如果我的lamda不写在类中,直接先在main函数中,则必须用"=";否则报错1.
如果改写&则不报错,并且结果是15:
然后如果声明变量改写为static,lambda用值传递则不报错,并且结果是15:
然后如果变量不写static,lambda表达式用mutable,则不报错,但结果是10:
所以我想问一下,为什么会这样。通过写在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。