15、【C++】C++11新特性:Lamda表示式/可變引數模板
一、Lamda表示式
Lamda表示式是C++11中引入的一項新技術,利用Lamda表示式可以編寫內嵌的匿名函式,用以替換獨立函式或者函式物件,並且使得程式碼更可讀。是一種匿名函式,即沒有函式名的函式;Lamda函式的語法定義如下:
[capture] :捕捉列表,捕捉列表總是作為lambda的開始,即出現於lambda的開始處。它是lambda的引出符(即開始標誌)。編譯器可以根據該“標誌”來作出判斷出該函式是否為lambda函式。同時“捕捉列表”能夠捕捉上下文中的變數以作為lambda函式使用。
(parameters):引數列表。和C/C++中的普通函式引數意義一樣。該部分是可選的,意味著如果我們不需要進行引數傳遞時,可以連同括號“()”一起省略掉
mutable:該關鍵字為一個修飾符。在預設的情況下,lambda函式總是返回一個const,而當我們在引數列表後面註明了“mutable”關鍵字之後,則可以取消其常量性質。若在lambda中使用了mutable修飾符,則“引數列表”是不可省略掉的(即使是引數為空)。
->return-type: 函式的返回值型別。和C/C++中的普通函式返回值型別的性質一樣。主要目的是用來追蹤lambda函式(有返回值情況下)的返回型別。若lambda函式不需要返回值,則可以直接將這部分省略掉。
{statement}:函式體。在該函式體中,除了可以使用引數列表中的變數外,還可以使用所有捕獲到的變數(即[capture] 中的變數)。
【示例】
// 指明返回型別
auto add = [](int a, int b) -> int { return a + b; };
// 自動推斷返回型別
auto multiply = [](int a, int b) { return a * b; };
int sum = add(2, 5); // 輸出:7
int product = multiply(2, 5); // 輸出:10
Lamda表示式中“捕捉列表”詳解
C++11中的lambda函式,其中的“捕捉列表”是由0個或多個“捕捉項”組成,並以逗號“,”分隔。捕捉列表有如下幾種形式:
(1)[]預設不捕獲任何變數;
(2)[var]表示值傳遞方式捕捉變數var;
(3)[=]表示值傳遞方式捕捉所有副作用域的變數(包括this);
(4)[&var]表示引用傳遞捕捉所有變數var;
(5)[&] 表示引用傳遞捕捉所有父作用域的比哪裡(包括this);
(6)[=, &x]表示以值捕獲所有變數,當x例外,通過引用捕獲;
(7)[&, x]表示以引用捕獲所有變數,但x例外,通過值捕獲;
(8)[this] 表示引用捕獲當前物件(其實是複製指標);
(9)[*this]表示通過值方式捕獲當前物件;
【示例1】
#include <iostream>
#include <string>
#include <stdio.h>
using namespace std;
int main()
{
int a = 1,b =2, c =3;
auto retVal = [=,&a,&b]() mutable->int //父作用域內變數a、b以引用方式捕獲,其餘變數以值捕獲方式捕獲
{
printf("inner c[%d]\n",c);
a = 10;
b = 20;
c = 30;
printf("inner c2[%d]\n",c);
return a+b;
};
printf("sum[%d]\n",retVal());
printf("a[%d] b[%d] c[%d]\n",a,b,c);
return 0;
}
執行結果:
inner c[3]
inner c2[30]
sum[30]
a[10] b[20] c[3]
【示例2】
#include <iostream>
#include <string>
#include <stdio.h>
using namespace std;
int main()
{
int a = 1,b =2, c =3;
auto retVal = [&]() mutable->int //預設引用捕獲方式捕獲所有父作用域變數
{
printf("inner a[%d] b[%d] c[%d]\n",a,b,c);
a = 10;
b = 20;
c = 30;
return a+b;
};
printf("sum[%d]\n",retVal());
printf("a[%d] b[%d] c[%d]\n",a,b,c);
return 0;
}
執行結果:
inner a[1] b[2] c[3]
sum[30]
a[10] b[20] c[30]
關於C++11中Lamda表示式更詳盡的內容參見:https://www.cnblogs.com/Braveliu/p/4231818.html
二、可變引數模板
在C++11之前,類模板和函式模板只能含有固定數量的模板引數。C++11增強了模板功能,允許模板定義中包含0到任意個模板引數,這就是可變引數模板。
可變引數模板和普通模板的語義是一樣的,只是寫法上稍有區別,宣告可變引數模板時需要在typename或class後面帶上省略號“…”:
template<class ... T>
void func(T ... args)//T叫模板引數包,args叫函式引數包
{
//可變引數模板函式
}
func(); // OK:args不含有任何實參
func(1); // OK:args含有一個實參:int
func(2, 1.0); // OK:args含有兩個實參int和double
1、可變引數模板函式
定義
一個可變引數模板函式的定義如下:
#include <iostream>
using namespace std;
template<class ... T>
void func(T ... args)
{
//可變引數模板函式
//sizeof...(sizeof後面有3個小點)計算變參個數
cout << "num = " << sizeof...(args) << endl;
}
int main()
{
func(); // num = 0
func(1); // num = 1
func(2, 1.0); // num = 2
return 0;
}
執行結果:
num = 0
num = 1
num = 2
引數包的展開
(1)遞迴方式展開
通過遞迴函式展開引數包,需要提供一個引數包展開的函式和一個遞迴終止函式。
#include <iostream>
using namespace std;
//遞迴終止函式
void debug()
{
cout << "empty\n";
}
//展開函式
template <class T, class ... Args>
void debug(T first, Args ... last)
{
cout << "parameter " << first << endl; debug(last...);
}
int main()
{
debug(1, 2, 3, 4);
return 0;
}
執行結果:
parameter 1
parameter 2
parameter 3
parameter 4
empty
(2)非遞迴方式展開
#include <iostream>
using namespace std;
template <class T>
void print(T arg)
{
cout << arg << endl;
}
template <class ... Args>
void expand(Args ... args)
{
int a[] = { (print(args), 0)... };
}
int main()
{
expand(1, 2, 3, 4);
return 0;
}
執行結果:
1
2
3
4
【示例】通過可變引數模板實現列印函式
#include <iostream>
#include <stdexcept>
using namespace std;
void Debug(const char* s)
{
while (*s)
{
if (*s == '%' && *++s != '%')
{
throw runtime_error("invalid format string: missing arguments");
}
cout << *s++;
}
}
template<typename T, typename... Args>
void Debug(const char* s, T value, Args... args)
{
while (*s)
{
if (*s == '%' && *++s != '%')
{
cout << value; return Debug(++s, args...);
}
cout << *s++;
}
throw runtime_error("extra arguments provided to Debug");
}
int main()
{
Debug("a = %d, b = %c, c = %s\n", 250, 'm', "mike");
return 0;
}
執行結果:
a = 250, b = m, c = mike
2、可變引數模板類
(1)繼承方式展開引數包
可變引數模板類的展開一般需要定義2 ~ 3個類,包含類宣告和特化的模板類:
#include <iostream>
#include <typeinfo>
using namespace std;
template<typename... A> class BMW{}; // 變長模板的宣告
template<typename Head, typename... Tail> // 遞迴的偏特化定義
class BMW<Head, Tail...> : public BMW<Tail...>
{//當例項化物件時,則會引起基類的遞迴構造
public:
BMW()
{
printf("type: %s\n", typeid(Head).name());
}
Head head;
};
template<> class BMW<>{}; // 邊界條件
int main()
{
BMW<int, char, float> car;
return 0;
}
執行結果:
type:f
type:c
tepy:i
(2)模板遞迴和特化方式展開引數包
#include <iostream>
using namespace std;
template <long... nums> struct Multiply;// 變長模板的宣告
template <long first, long... last>
struct Multiply<first, last...> // 變長模板類
{
static const long val = first * Multiply<last...>::val;
};
template<> struct Multiply<> // 邊界條件
{
static const long val = 1;
};
int main()
{
cout << Multiply<2, 3, 4, 5>::val << endl; // 120
return 0;
}
執行結果:
120