您好,登錄后才能下訂單哦!
先看下Lambda表達(dá)式里面修改外部變量問題
因?yàn)槠脚_用的是JDK8,而且發(fā)現(xiàn)自己對那些新特性好像一點(diǎn)都不了解,就例如Lambda表達(dá)式,所以就開始對自己的代碼進(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á)式呢,將會非常的簡單,
但是我們會發(fā)現(xiàn),result那里是會報(bào)錯的: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會將result的值作為參數(shù)傳遞給Lambda表達(dá)式,為Lambda表達(dá)式建立一個(gè)副本,它的代碼訪問的是這個(gè)副本,而不是外部聲明result變量??赡芎芏嗤瑢W(xué)會問為什么非要建立副本呢,直接訪問外部的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)){ //都是對數(shù)組進(jìn)行操作了 arr[0] = arr[0].replaceAll(key, value); } });
下面看下C++ lambda 捕獲模式與右值引用詳解
lambda 表達(dá)式和右值引用是 C++11 的兩個(gè)非常有用的特性。
lambda 表達(dá)式實(shí)際上會由編譯器創(chuàng)建一個(gè) std::function
對象,以值的方式捕獲的變量則會由編譯器復(fù)制一份,在 std::function 對象中創(chuàng)建一個(gè)對應(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á)式以值的方式捕獲的對象 str,其地址在 lambda 表達(dá)式內(nèi)部和外部是不同的。
std::function 類對象和普通的魔板類對象一樣,可以拷貝構(gòu)造,如:
std::function<void()> funcb = funca;
由調(diào)用 funcb 時(shí)的輸出,可以看到拷貝構(gòu)造時(shí)是做了逐成員的拷貝構(gòu)造。
std::function 類對象可以賦值,如:
std::function<void()> funcc;
funcc = funca;
由調(diào)用 funcc 時(shí)的輸出,可以看到賦值時(shí)是做了逐成員的賦值。
std::function 類對象可以移動構(gòu)造,如:
std::function<void()> funcd = std::move(funca);
由移動構(gòu)造之后,調(diào)用 funca 和 funcd 時(shí)的輸出,可以看到移動構(gòu)造時(shí)是做了逐成員的移動構(gòu)造。
std::function 類對象可以移動賦值,如:
std::function<void()> funce; funce = std::move(funcb); printf("funcb\n"); // funcb();
這里把移動賦值之后對 funcb 的調(diào)用注釋掉了,這是因?yàn)?,作為源?nbsp;funcb 在移動賦值之后被調(diào)用是,會拋出異常,如:
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 在移動賦值之前被調(diào)用時(shí)的輸出完全相同。 即移動賦值是將對象整體 move 走了,這與移動構(gòu)造時(shí)的行為不太一樣。
std::function 類對象的拷貝構(gòu)造或者賦值,也需要滿足類型匹配原則,如:
std::function<void(int)> funcf = funce;
這行代碼會造成編譯失敗,編譯錯誤信息如下:
../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 中以值的方式捕獲的右值對象,只是在 lambda 的 std::function 對象中做了一份被捕獲的右值對象的拷貝,而原來的右值則沒有任何改變。
接下來再來看一段示例代碼:
#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() 并不會將原來的對象的內(nèi)容移動到任何地方。 std::move()
只是一個(gè)簡單的強(qiáng)制類型轉(zhuǎn)換,將左值轉(zhuǎn)為右值引用。同時(shí)可以看到,用右值引用作為參數(shù)構(gòu)造對象,也并沒有對右值引用所引用的對象產(chǎn)生任何影響。
funcb 函數(shù)接收左值引用作為參數(shù),上面的代碼中,如下這一行注釋掉了:
// funcb(std::move(str));
這是因?yàn)椋?nbsp;funcb 不能用一個(gè)右值引用作為參數(shù)來調(diào)用。用右值引用作為參數(shù),調(diào)用接收左值引用作為參數(shù)的函數(shù) funcb 時(shí),會編譯失敗:
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
不過,如果 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)用前后的輸出可以看到,由于有了左值作為接收者,傳入的右值引用所引用的對象的值被 move 走,進(jìn)入函數(shù)的參數(shù)棧對象中了。
funcd 函數(shù)與 funca 函數(shù)一樣,接收右值引用作為參數(shù),但 funcd 的特別之處在于,在函數(shù)內(nèi)部,右值構(gòu)造了一個(gè)新的對象,因而右值引用原來引用的對象的值被 move 走,進(jìn)入了新構(gòu)造的對象中。
再來看一段示例代碼:
#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 及對 funa 的調(diào)用被注釋掉了,這是因?yàn)檫@段代碼會導(dǎo)致編譯失敗,具體的錯誤信息如下:
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í),會在編譯器為該 lambda 表達(dá)式生成的 std::function 類中生成一個(gè) const 對象,const 對象是不能作為右值引用來調(diào)用接收右值引用為參數(shù)的函數(shù)的。
在函數(shù) foo() 中定義的 funb ,相對于 funa ,在調(diào)用 bar() 時(shí),為 str 裹上了 std::move() 。不過此時(shí)還是會編譯失敗。錯誤信息如下:
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 對象,因而還是不行。
在函數(shù) foo() 中定義的 func ,相對于 funa ,加了 mutable 修飾。此時(shí)還是會編譯失敗。錯誤信息如下:
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
無法將左值綁定到一個(gè)右值引用上。
在函數(shù) foo() 中定義的 fund ,相對于 func ,在調(diào)用 bar() 時(shí),為 str 裹上了 std::move()
。此時(shí)終于可以編譯成功,可以 move const 的 str 。
在函數(shù) foo() 中定義的 fune ,相對于 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ù)對象 funf ,以引用的方式捕獲一個(gè)右值,并在 lambda 中訪問改對象。該 lambda 作為 bar_bar() 函數(shù)生成的函數(shù)對象。 foo() 中調(diào)用 bar_bar() 時(shí)傳入函數(shù)棧上定義的臨時(shí)對象 stra ,并將 bar_bar() 返回的函數(shù)對象作為返回值返回。在 main() 函數(shù)中用 funcg 接收 foo() 函數(shù)返回的函數(shù)對象,并調(diào)用 funcg ,此時(shí)會發(fā)生 crash 或能看到亂碼。crash 或亂碼是因?yàn)?,?funf 中,訪問的 str 對象實(shí)際上是 foo() 函數(shù)中定義的棧上臨時(shí)對象 stra , foo() 函數(shù)調(diào)用結(jié)束之后,棧上的臨時(shí)對象被釋放, main() 函數(shù)中調(diào)用 funcg 實(shí)際在訪問一個(gè)無效的對象,因而出現(xiàn)問題。
總結(jié)
到此這篇關(guān)于Lambda表達(dá)式里面修改外部變量問題的文章就介紹到這了,更多相關(guān)C++ lambda 表達(dá)式內(nèi)容請搜索億速云以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持億速云!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。