Lambda表達(dá)式里面修改外部變量問(wèn)題
先看下Lambda表達(dá)式里面修改外部變量問(wèn)題
因?yàn)槠脚_(tái)用的是JDK8,而且發(fā)現(xiàn)自己對(duì)那些新特性好像一點(diǎn)都不了解,就例如Lambda表達(dá)式,所以就開始對(duì)自己的代碼進(jìn)行改進(jìn)了。。。
例如遍歷Map,像我們正常遍歷肯定是下面這樣子的。
String result = "select * from where id = '#userId#' and name = '#userName#'"; Map<String,String> sysParams = new HashMap<String,String>(); sysParams.put("#userId#", "userId"); sysParams.put("#userName#", "userName"); sysParams.put("#realName#", "realName"); sysParams.put("#orgIds#", "orgIds"); sysParams.put("#departname#", "departname"); sysParams.put("#roleId#", "roleId"); for(Map.Entry<String, String> entry : sysParams.entrySet()){ if(result .contains(entry.getKey())){ result = result .replaceAll(entry.getKey(), AppDataUtils.replaceSysData(entry.getValue())); } }
但是如果用Lambda表達(dá)式呢,將會(huì)非常的簡(jiǎn)單,
但是我們會(huì)發(fā)現(xiàn),result那里是會(huì)報(bào)錯(cuò)的:Local variable result defined in an enclosing scope must be final or effectively final
String result = "select * from where id = '#userId#' and name = '#userName#'"; Map<String,String> sysParams = new HashMap<String,String>(); sysParams.put("#userId#", "userId"); sysParams.put("#userName#", "userName"); sysParams.put("#realName#", "realName"); sysParams.put("#orgIds#", "orgIds"); sysParams.put("#departname#", "departname"); sysParams.put("#roleId#", "roleId"); sysParams.forEach((key,value)->{ if(result.contains(key)){ result = result.replaceAll(key, value); } });
這是因?yàn)椋篔ava會(huì)將result的值作為參數(shù)傳遞給Lambda表達(dá)式,為L(zhǎng)ambda表達(dá)式建立一個(gè)副本,它的代碼訪問(wèn)的是這個(gè)副本,而不是外部聲明result變量??赡芎芏嗤瑢W(xué)會(huì)問(wèn)為什么非要建立副本呢,直接訪問(wèn)外部的result變量得多方便呢。答案是:這是不可能滴,因?yàn)閞esult定義在棧中,當(dāng)Lambda表達(dá)式被執(zhí)行的時(shí)候,result可能已經(jīng)被釋放掉了。
當(dāng)然啦,你要是一定要在Lambda表達(dá)式里面修改外部變量的值也是可以的,可以將變量定義為實(shí)例變量或者將變量定義為數(shù)組。
下面我就改為數(shù)組咯,實(shí)例變量不方便。
String result = "select * from where id = '#userId#' and name = '#userName#'"; //將變量定義為數(shù)組就好了 String[] arr = new String[]{result}; Map<String,String> sysParams = new HashMap<String,String>(); sysParams.put("#userId#", "userId"); sysParams.put("#userName#", "userName"); sysParams.put("#realName#", "realName"); sysParams.put("#orgIds#", "orgIds"); sysParams.put("#departname#", "departname"); sysParams.put("#roleId#", "roleId"); sysParams.forEach((key,value)->{ if(arr[0].contains(key)){ //都是對(duì)數(shù)組進(jìn)行操作了 arr[0] = arr[0].replaceAll(key, value); } });
下面看下C++ lambda 捕獲模式與右值引用詳解
lambda 表達(dá)式和右值引用是 C++11 的兩個(gè)非常有用的特性。
lambda 表達(dá)式實(shí)際上會(huì)由編譯器創(chuàng)建一個(gè) std::function
對(duì)象,以值的方式捕獲的變量則會(huì)由編譯器復(fù)制一份,在 std::function 對(duì)象中創(chuàng)建一個(gè)對(duì)應(yīng)的類型相同的 const 成員變量,如下面的這段代碼:
int main(){ std::string str = "test"; printf("String address %p in main, str %s\n", &str, str.c_str()); auto funca = [str]() { printf("String address %p (main lambda), str %s\n", &str, str.c_str()); }; std::function<void()> funcb = funca; std::function<void()> funcc; funcc = funca; printf("funca\n"); funca(); std::function<void()> funcd = std::move(funca); printf("funca\n"); funca(); printf("funcb\n"); funcb(); std::function<void()> funce; funce = std::move(funcb); printf("funcb\n"); // funcb(); printf("funcc\n"); funcc(); printf("funcd\n"); funcd(); printf("funce\n"); funce(); // std::function<void(int)> funcf = funce; return 0; }
這段代碼的輸出如下:
Stringaddress0x7ffd9aaab720 in main, strtest
funca
Stringaddress0x7ffd9aaab740 (main lambda), strtest
funca
Stringaddress0x7ffd9aaab740 (main lambda), str
funcb
Stringaddress0x55bdd2160280 (main lambda), strtest
funcb
funcc
Stringaddress0x55bdd21602b0 (main lambda), strtest
funcd
Stringaddress0x55bdd21602e0 (main lambda), strtest
funce
Stringaddress0x55bdd2160280 (main lambda), strtest
由上面調(diào)用 funca 時(shí)的輸出,可以看到 lambda 表達(dá)式以值的方式捕獲的對(duì)象 str,其地址在 lambda 表達(dá)式內(nèi)部和外部是不同的。
std::function 類對(duì)象和普通的魔板類對(duì)象一樣,可以拷貝構(gòu)造,如:
std::function<void()> funcb = funca;
由調(diào)用 funcb 時(shí)的輸出,可以看到拷貝構(gòu)造時(shí)是做了逐成員的拷貝構(gòu)造。
std::function 類對(duì)象可以賦值,如:
std::function<void()> funcc;
funcc = funca;
由調(diào)用 funcc 時(shí)的輸出,可以看到賦值時(shí)是做了逐成員的賦值。
std::function 類對(duì)象可以移動(dòng)構(gòu)造,如:
std::function<void()> funcd = std::move(funca);
由移動(dòng)構(gòu)造之后,調(diào)用 funca 和 funcd 時(shí)的輸出,可以看到移動(dòng)構(gòu)造時(shí)是做了逐成員的移動(dòng)構(gòu)造。
std::function 類對(duì)象可以移動(dòng)賦值,如:
std::function<void()> funce; funce = std::move(funcb); printf("funcb\n"); // funcb();
這里把移動(dòng)賦值之后對(duì) funcb 的調(diào)用注釋掉了,這是因?yàn)椋鳛樵吹?nbsp;funcb 在移動(dòng)賦值之后被調(diào)用是,會(huì)拋出異常,如:
String address 0x562334c34280 (main lambda), str test funcb terminate called after throwing aninstanceof 'std::bad_function_call' what(): bad_function_call
同時(shí),由調(diào)用 funce 時(shí)的輸出可以看到,該輸出與 funcb 在移動(dòng)賦值之前被調(diào)用時(shí)的輸出完全相同。 即移動(dòng)賦值是將對(duì)象整體 move 走了,這與移動(dòng)構(gòu)造時(shí)的行為不太一樣。
std::function 類對(duì)象的拷貝構(gòu)造或者賦值,也需要滿足類型匹配原則,如:
std::function<void(int)> funcf = funce;
這行代碼會(huì)造成編譯失敗,編譯錯(cuò)誤信息如下:
../src/DemoTest.cpp: In function ‘intmain()':
../src/DemoTest.cpp:64:36: error: conversion from ‘std::function<void()>' to non-scalar type ‘std::function<void(int)>' requested
std::function<void(int)> funcf = funce;
^~~~~
make: *** [src/DemoTest.o] Error 1
src/subdir.mk:18: recipe for target 'src/DemoTest.o' failed
在 lambda 中以值的方式捕獲的右值對(duì)象,只是在 lambda 的 std::function 對(duì)象中做了一份被捕獲的右值對(duì)象的拷貝,而原來(lái)的右值則沒有任何改變。
接下來(lái)再來(lái)看一段示例代碼:
#include<iostream> #include<functional> #include<string> using namespace std; void funcd(std::string&&str){ printf("String address %p in funcd A, str %s\n", &str, str.c_str()); string strs = std::move(str); printf("String address %p in funcd B, str %s, strs %s\n", &str, str.c_str(), strs.c_str()); } void funcc(std::stringstr){ printf("String address %p in funcc, str %s\n", &str, str.c_str()); } void funcb(std::string&str){ printf("String address %p in funcb, str %s\n", &str, str.c_str()); } void funca(std::string&&str){ printf("String address %p in funca A, str %s\n", &str, str.c_str()); std::string stra = str; printf("String address %p in funca B, str %s, stra %s\n", &str, str.c_str(), stra.c_str()); } int main(){ std::string str = "test"; printf("String address %p in main A, str %s\n", &str, str.c_str()); funca(std::move(str)); printf("String address %p in main B, str %s\n", &str, str.c_str()); // funcb(std::move(str)); printf("String address %p in main C, str %s\n", &str, str.c_str()); funcc(std::move(str)); printf("String address %p in main D, str %s\n", &str, str.c_str()); std::string stra = "testa"; printf("String address %p in main E, stra %s\n", &stra, stra.c_str()); funcd(std::move(stra)); printf("String address %p in main F, stra %s\n", &stra, stra.c_str()); return 0; }
上面這段代碼在執(zhí)行時(shí),輸出如下:
String address 0x7ffc833f4660 in main A, str test
String address 0x7ffc833f4660 in funca A, str test
String address 0x7ffc833f4660 in funca B, str test, stra test
String address 0x7ffc833f4660 in main B, str test
String address 0x7ffc833f4660 in main C, str test
String address 0x7ffc833f4680 in funcc, str test
String address 0x7ffc833f4660 in main D, str
String address 0x7ffc833f4680 in main E, stra testa
String address 0x7ffc833f4680 in funcd A, str testa
String address 0x7ffc833f4680 in funcd B, str , strs testa
String address 0x7ffc833f4680 in main F, stra
funca 函數(shù)接收右值引用作為參數(shù),由 funca 函數(shù)內(nèi)部及函數(shù)調(diào)用前后的輸出可以看到, std::move()
本身什么都沒做,單單調(diào)用 std::move() 并不會(huì)將原來(lái)的對(duì)象的內(nèi)容移動(dòng)到任何地方。 std::move()
只是一個(gè)簡(jiǎn)單的強(qiáng)制類型轉(zhuǎn)換,將左值轉(zhuǎn)為右值引用。同時(shí)可以看到,用右值引用作為參數(shù)構(gòu)造對(duì)象,也并沒有對(duì)右值引用所引用的對(duì)象產(chǎn)生任何影響。
funcb 函數(shù)接收左值引用作為參數(shù),上面的代碼中,如下這一行注釋掉了:
// funcb(std::move(str));
這是因?yàn)椋?nbsp;funcb 不能用一個(gè)右值引用作為參數(shù)來(lái)調(diào)用。用右值引用作為參數(shù),調(diào)用接收左值引用作為參數(shù)的函數(shù) funcb 時(shí),會(huì)編譯失敗:
g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"src/DemoTest.d" -MT"src/DemoTest.o" -o "src/DemoTest.o" "../src/DemoTest.cpp"
../src/DemoTest.cpp: In function ‘int main()':
../src/DemoTest.cpp:34:18: error: cannot bind non-const lvalue reference of type ‘std::__cxx11::string& {aka std::__cxx11::basic_string<char>&}' to an rvalue of type ‘std::remove_reference<std::__cxx11::basic_string<char>&>::type {aka std::__cxx11::basic_string<char>}'
funcb(std::move(str));
~~~~~~~~~^~~~~
../src/DemoTest.cpp:17:6: note: initializing argument 1 of ‘void funcb(std::__cxx11::string&)'
void funcb(std::string &str) {
^~~~~
src/subdir.mk:18: recipe for target 'src/DemoTest.o' failed
make: *** [src/DemoTest.o] Error 1
不過(guò),如果 funcb 接收 const 左值引用作為參數(shù),如 void funcb(const std::string &str)
,則在調(diào)用該函數(shù)時(shí),可以用右值引用作為參數(shù),此時(shí) funcb 的行為與 funca 基本相同。
funcc 函數(shù)接收左值作為參數(shù),由 funcc 函數(shù)內(nèi)部及函數(shù)調(diào)用前后的輸出可以看到,由于有了左值作為接收者,傳入的右值引用所引用的對(duì)象的值被 move 走,進(jìn)入函數(shù)的參數(shù)棧對(duì)象中了。
funcd 函數(shù)與 funca 函數(shù)一樣,接收右值引用作為參數(shù),但 funcd 的特別之處在于,在函數(shù)內(nèi)部,右值構(gòu)造了一個(gè)新的對(duì)象,因而右值引用原來(lái)引用的對(duì)象的值被 move 走,進(jìn)入了新構(gòu)造的對(duì)象中。
再來(lái)看一段示例代碼:
#include<iostream> #include<functional> #include<string> using namespace std; void bar(std::string&&str){ printf("String address %p in bar A, str %s\n", &str, str.c_str()); string strs = std::move(str); printf("String address %p in bar B, str %s, strs %s\n", &str, str.c_str(), strs.c_str()); } std::function<void()> bar_bar(std::string &&str) { auto funf = [&str]() { printf("String address %p (foo lambda) F, stra %s\n", &str, str.c_str()); }; return funf; } std::function<void()> foo(std::string &&str) { printf("String address %p in foo A, str %s\n", &str, str.c_str()); // auto funa = [str]() { // printf("String address %p (foo lambda) A, str %s\n", &str, str.c_str()); // bar(str); // }; // funa(); // // auto funb = [str]() { // printf("String address %p (foo lambda) B, str %s\n", &str, str.c_str()); // bar(std::move(str)); // }; // funb(); // auto func = [str]() mutable { // printf("String address %p (foo lambda) C, str %s\n", &str, str.c_str()); // bar(str); // }; // func(); auto fund = [str]() mutable { printf("String address %p (foo lambda) D, str %s\n", &str, str.c_str()); bar(std::move(str)); }; fund(); auto fune = [&str]() { printf("String address %p (foo lambda) E, str %s\n", &str, str.c_str()); bar(std::move(str)); }; fune(); std::string stra = "testa"; return bar_bar(std::move(stra)); } int main(){ std::string str = "test"; printf("String address %p in main A, str %s\n", &str, str.c_str()); auto funcg = foo(std::move(str)); printf("String address %p in main B, str %s\n", &str, str.c_str()); funcg(); return 0; }
上面這段代碼的輸出如下:
Stringaddress0x7ffc9fe7c5c0 in main A, strtest
Stringaddress0x7ffc9fe7c5c0 in foo A, strtest
Stringaddress0x7ffc9fe7c540 (foo lambda) D, strtest
Stringaddress0x7ffc9fe7c540 in barA, strtest
Stringaddress0x7ffc9fe7c540 in barB,str, strstest
Stringaddress0x7ffc9fe7c5c0 (foo lambda) E, strtest
Stringaddress0x7ffc9fe7c5c0 in barA, strtest
Stringaddress0x7ffc9fe7c5c0 in barB,str, strstest
Stringaddress0x7ffc9fe7c5c0 in main B,str
Stringaddress0x7ffc9fe7c560 (foo lambda) F, stra����
在函數(shù) foo() 中定義的 funa 及對(duì) funa 的調(diào)用被注釋掉了,這是因?yàn)檫@段代碼會(huì)導(dǎo)致編譯失敗,具體的錯(cuò)誤信息如下:
Invoking: GCC C++ Compiler
g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"src/DemoTest.d" -MT"src/DemoTest.o" -o "src/DemoTest.o" "../src/DemoTest.cpp"
../src/DemoTest.cpp: In lambda function:
../src/DemoTest.cpp:25:12: error: cannot bind rvalue reference of type ‘std::__cxx11::string&& {aka std::__cxx11::basic_string<char>&&}' to lvalue of type ‘const string {aka const std::__cxx11::basic_string<char>}'
bar(str);
^
../src/DemoTest.cpp:7:6: note: initializing argument 1 of ‘void bar(std::__cxx11::string&&)'
void bar(std::string &&str) {
^~~
src/subdir.mk:18: recipe for target 'src/DemoTest.o' failed
make: *** [src/DemoTest.o] Error 1
如我們前面提到的,在 lambda 表達(dá)式中,以值的方式捕獲右值引用時(shí),會(huì)在編譯器為該 lambda 表達(dá)式生成的 std::function 類中生成一個(gè) const 對(duì)象,const 對(duì)象是不能作為右值引用來(lái)調(diào)用接收右值引用為參數(shù)的函數(shù)的。
在函數(shù) foo() 中定義的 funb ,相對(duì)于 funa ,在調(diào)用 bar() 時(shí),為 str 裹上了 std::move() 。不過(guò)此時(shí)還是會(huì)編譯失敗。錯(cuò)誤信息如下:
Invoking: GCC C++ Compiler
g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"src/DemoTest.d" -MT"src/DemoTest.o" -o "src/DemoTest.o" "../src/DemoTest.cpp"
../src/DemoTest.cpp: In lambda function:
../src/DemoTest.cpp:31:18: error: binding reference of type ‘std::__cxx11::string&& {aka std::__cxx11::basic_string<char>&&}' to ‘std::remove_reference<const std::__cxx11::basic_string<char>&>::type {aka const std::__cxx11::basic_string<char>}' discards qualifiers
bar(std::move(str));
~~~~~~~~~^~~~~
../src/DemoTest.cpp:7:6: note: initializing argument 1 of ‘void bar(std::__cxx11::string&&)'
void bar(std::string &&str) {
^~~
make: *** [src/DemoTest.o] Error 1
src/subdir.mk:18: recipe for target 'src/DemoTest.o' failed
在 funb 中, str 是個(gè) const 對(duì)象,因而還是不行。
在函數(shù) foo() 中定義的 func ,相對(duì)于 funa ,加了 mutable 修飾。此時(shí)還是會(huì)編譯失敗。錯(cuò)誤信息如下:
Invoking: GCC C++ Compiler
g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"src/DemoTest.d" -MT"src/DemoTest.o" -o "src/DemoTest.o" "../src/DemoTest.cpp"
../src/DemoTest.cpp: In lambda function:
../src/DemoTest.cpp:37:12: error: cannot bind rvalue reference of type ‘std::__cxx11::string&& {aka std::__cxx11::basic_string<char>&&}' to lvalue of type ‘std::__cxx11::string {aka std::__cxx11::basic_string<char>}'
bar(str);
^
../src/DemoTest.cpp:7:6: note: initializing argument 1 of ‘void bar(std::__cxx11::string&&)'
void bar(std::string &&str) {
^~~
make: *** [src/DemoTest.o] Error 1
src/subdir.mk:18: recipe for target 'src/DemoTest.o' failed
無(wú)法將左值綁定到一個(gè)右值引用上。
在函數(shù) foo() 中定義的 fund ,相對(duì)于 func ,在調(diào)用 bar() 時(shí),為 str 裹上了 std::move()
。此時(shí)終于可以編譯成功,可以 move const 的 str 。
在函數(shù) foo() 中定義的 fune ,相對(duì)于 funb ,以引用的方式捕獲了右值引用。在 fune 中調(diào)用 bar() ,就如同 foo() 直接調(diào)用 bar() 一樣。
在函數(shù) foo() 中調(diào)用接收一個(gè)右值引用作為參數(shù)的函數(shù) bar_bar() 生成一個(gè)函數(shù)。在函數(shù) bar_bar() 中用 lambda 定義的函數(shù)對(duì)象 funf ,以引用的方式捕獲一個(gè)右值,并在 lambda 中訪問(wèn)改對(duì)象。該 lambda 作為 bar_bar() 函數(shù)生成的函數(shù)對(duì)象。 foo() 中調(diào)用 bar_bar() 時(shí)傳入函數(shù)棧上定義的臨時(shí)對(duì)象 stra ,并將 bar_bar() 返回的函數(shù)對(duì)象作為返回值返回。在 main() 函數(shù)中用 funcg 接收 foo() 函數(shù)返回的函數(shù)對(duì)象,并調(diào)用 funcg ,此時(shí)會(huì)發(fā)生 crash 或能看到亂碼。crash 或亂碼是因?yàn)?,?funf 中,訪問(wèn)的 str 對(duì)象實(shí)際上是 foo() 函數(shù)中定義的棧上臨時(shí)對(duì)象 stra , foo() 函數(shù)調(diào)用結(jié)束之后,棧上的臨時(shí)對(duì)象被釋放, main() 函數(shù)中調(diào)用 funcg 實(shí)際在訪問(wèn)一個(gè)無(wú)效的對(duì)象,因而出現(xiàn)問(wèn)題。
總結(jié)
到此這篇關(guān)于Lambda表達(dá)式里面修改外部變量問(wèn)題的文章就介紹到這了,更多相關(guān)C++ lambda 表達(dá)式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++實(shí)現(xiàn)字符串元音字母反轉(zhuǎn)的兩種方法
在處理字符串問(wèn)題時(shí),我們經(jīng)常需要對(duì)其中的字符進(jìn)行操作,例如反轉(zhuǎn)、替換等,本文將詳細(xì)討論如何在C++中實(shí)現(xiàn)僅反轉(zhuǎn)字符串中的所有元音字母,并返回結(jié)果字符串,需要的朋友可以參考下2024-07-07Qt實(shí)現(xiàn)簡(jiǎn)單動(dòng)態(tài)時(shí)鐘
這篇文章主要為大家詳細(xì)介紹了Qt實(shí)現(xiàn)簡(jiǎn)單動(dòng)態(tài)時(shí)鐘,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-07-07嵌入式C語(yǔ)言二級(jí)指針在鏈表中的應(yīng)用
這篇文章主要為大家介紹了嵌入式C語(yǔ)言二級(jí)指針在鏈表中的應(yīng)用,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04C++ OpenCV實(shí)現(xiàn)白平衡之灰度世界算法
灰度世界算法是白平衡各種算法中最基本的一種。本文將利用C++和OpenCV實(shí)現(xiàn)白平衡中的灰度世界算法,文中示例代碼講解詳細(xì),感興趣的可以了解一下2022-05-05C++靜態(tài)庫(kù)與動(dòng)態(tài)庫(kù)文件的生成和使用教程
庫(kù)文件是計(jì)算機(jī)上的一類文件,可以簡(jiǎn)單的把庫(kù)文件看成一種代碼倉(cāng)庫(kù),它提供給使用者一些可以直接拿來(lái)用的變量、函數(shù)和類,下面這篇文章主要給大家介紹了關(guān)于C++靜態(tài)庫(kù)與動(dòng)態(tài)庫(kù)文件的生成和使用的相關(guān)資料,需要的朋友可以參考下2023-03-03C++實(shí)現(xiàn)LeetCode(104.二叉樹的最大深度)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(104.二叉樹的最大深度),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07詳解VS2019+OpenCV-4-1-0+OpenCV-contrib-4-1-0
這篇文章主要介紹了詳解VS2019+OpenCV-4-1-0+OpenCV-contrib-4-1-0,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04