1. 程式人生 > >15、【C++】C++11新特性:Lamda表示式/可變引數模板

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