迭代的運行效率始終強於遞歸,遞歸始終比迭代方便開發。
變長模板屬於C++11中比較複雜的技術,在此簡單介紹下。
#include <iostream>
using namespace std;
template<class... Args>
int Sum (Args... args) {
return sizeof...(args);
}
int main (int argc, char* argv []) {
cout << Sum (1,2,23,124,4,23,43,24,32,4,23) << endl;
return 0;
}
這是一個最簡單的變長模板,首先是template定義,class後面加三個點就代表不固定長度。
接下來是Sum函數,在main函數的調用中實例化,被擴展爲如下形式:
int Sum(int,int,int,int,int,int,int,int,int,int,int)
傳了11個參數,就被實例化爲11個參數形式。然後是sizeof...,這個宏是用來獲取變長模板中元素的個數用的。我們調用時傳了11個參數,它就返回11。程序運行的結果也相應爲11。變長模板對於變長參數的優勢之一爲第一個參數不用定義參數個數(關於變長參數的訪問詳見深度研究C語言變長函數),但相應的劣勢爲無法直接訪問參數。文章後面將簡要介紹如何調用參數。
這是最簡單的用法,接下來看看稍微複雜點的。
將參數進行一一比較,如果不爲3,顯示“值 is not 3”,如果爲3則顯示“find 3”然後立即返回。首先我們用遞歸來實現:
#include <iostream>
using namespace std;
template<class T, class... Args>
bool check (T t, Args... args) {
if (t == 3) {
cout << "find 3" << endl;
} else {
cout << t << " is not 3" << endl;
}
return (t != 3) && check (args...);
}
template<class T>
bool check (T t) {
if (t == 3) {
cout << "find 3" << endl;
} else {
cout << t << " is not 3" << endl;
}
return (t != 3);
}
int main (int argc, char* argv []) {
check (1,2,5,7,9,3,5,6);
return 0;
}
程序執行結果如下:
這個例子的代碼還算比較簡潔的。但變長模板展開後是什麼情況呢?就像下面這樣:
#include <iostream>
using namespace std;
bool check (int i1) {
if (i1 == 3) {
cout << "find 3" << endl;
} else {
cout << i1 << " is not 3" << endl;
}
return (i1 != 3);
}
bool check (int i1, int i2) {
if (i1 == 3) {
cout << "find 3" << endl;
} else {
cout << i1 << " is not 3" << endl;
}
return (i1 != 3) && check (i2);
}
bool check (int i1, int i2, int i3) {
if (i1 == 3) {
cout << "find 3" << endl;
} else {
cout << i1 << " is not 3" << endl;
}
return (i1 != 3) && check (i2, i3);
}
bool check (int i1, int i2, int i3, int i4) {
if (i1 == 3) {
cout << "find 3" << endl;
} else {
cout << i1 << " is not 3" << endl;
}
return (i1 != 3) && check (i2, i3, i4);
}
bool check (int i1, int i2, int i3, int i4, int i5) {
if (i1 == 3) {
cout << "find 3" << endl;
} else {
cout << i1 << " is not 3" << endl;
}
return (i1 != 3) && check (i2, i3, i4, i5);
}
bool check (int i1, int i2, int i3, int i4, int i5, int i6) {
if (i1 == 3) {
cout << "find 3" << endl;
} else {
cout << i1 << " is not 3" << endl;
}
return (i1 != 3) && check (i2, i3, i4, i5, i6);
}
bool check (int i1, int i2, int i3, int i4, int i5, int i6, int i7) {
if (i1 == 3) {
cout << "find 3" << endl;
} else {
cout << i1 << " is not 3" << endl;
}
return (i1 != 3) && check (i2, i3, i4, i5, i6, i7);
}
bool check (int i1, int i2, int i3, int i4, int i5, int i6, int i7, int i8) {
if (i1 == 3) {
cout << "find 3" << endl;
} else {
cout << i1 << " is not 3" << endl;
}
return (i1 != 3) && check (i2, i3, i4, i5, i6, i7, i8);
}
int main (int argc, char* argv []) {
check (1,2,5,7,9,3,5,6);
return 0;
}
乍一看,簡直嚇尿。完全是新手寫的代碼。事實就是這樣,所有通過遞歸實現的變長模板展開都是這樣。上面的例子調用8個參數的重載,然後它調用7個函數的重載,然後調用6個函數的重載……
效果是實現了,可是這代碼被擴展了這麼多次,太浪費了。有沒更好的實現方式呢?有,通過迭代。以下示例通過迭代展開變長模板:
#include <iostream>
#include <vector>
#include <functional>
using namespace std;
template<class... Args>
void fun (Args... args) {
function<bool (int)> check = [] (int t) {
if (t == 3) {
cout << "find 3" << endl;
} else {
cout << t << " is not 3" << endl;
}
return t != 3;
};
vector<function<bool ()>> fun = { bind (check, args)... };
for (auto f : fun) {
if (!f ()) break;
}
}
int main (int argc, char* argv []) {
fun (1,2,5,7,9,3,5,6);
return 0;
}
代碼執行結果與上面的遞歸的執行結果相同。邏輯貌似比上面更不易懂,我們依次來分析。首先是fun函數,由於不是一次一次的取,所以不用寫兩次重載,比迭代稍微方便些。然後是一個check函數lambda表達式,這個表達式只是爲了讓check函數只能在fun函數內訪問,減少公開不必要的接口。重點不在這,重點在下面一行,vector這行。這行使用了三個C++11專有特性。
第一個是bind,用於函數的地址與參數綁定。爲何需要綁定?因爲變長模板擴的迭代擴展首先就是初始化逗號表達式。如果這那行像下面這樣寫:
bool val[] = { check(args)... };
變長模板將會將其擴展爲如下形式:bool val[] = { check(arg0), check(arg1), check(arg2), check(arg3), check(arg4), check(arg5), check(arg6), check(arg7) };
省略號將在擴展時一次性全部擴展,這樣導致的結果就是初始化時被全部調用,不能實現調用到3之後立即返回的功能。而上面的std::bind函數恰好解決了這個問題,它可以將函數或仿函數與參數綁定,返回一個內部仿函數,這樣,調用這個內部仿函數,就能調用原始的函數並傳入綁定的參數了。
上面的代碼模板擴展之後長這樣:
#include <iostream>
#include <vector>
#include <functional>
using namespace std;
void fun (int i1, int i2, int i3, int i4, int i5, int i6, int i7, int i8) {
function<bool (int)> check = [] (int t) {
if (t == 3) {
cout << "find 3" << endl;
} else {
cout << t << " is not 3" << endl;
}
return t != 3;
};
vector<function<bool ()>> fun = {
bind (check, i1),
bind (check, i2),
bind (check, i3),
bind (check, i4),
bind (check, i5),
bind (check, i6),
bind (check, i7),
bind (check, i8)
};
for (auto f : fun) {
if (!f ()) break;
}
}
int main (int argc, char* argv []) {
fun (1,2,5,7,9,3,5,6);
return 0;
}
這兒還涉及到一個問題,那就是C++11的初始化列表。能讓vector或自定義類實現{xxx,xxx}這樣的初始化。這樣就不用一個一個push_back了。然後,接下來是基於範圍的for循環了。依次取每個綁定,由於綁定自帶參數,所以這兒就不用傳參了(注意vector的聲明,函數返回值爲bool但沒有參數)。如果bool返回假那麼退出循環,將不會調用後面的代碼。
這只是一個簡單的實現,可見迭代的代碼效率優勢在任何時候都是強於遞歸的。
最後說一個需要注意到的地方。變長模板擴展不是你想擴展就能擴展,比如擴展成這樣 check(0)&&check(1)&&check(2)…… 貌似很好看,實際上你是想多了。一次性擴展還只能用逗號實現,否則上面代碼就不會拐個彎了。如果C++17能實現這樣的擴展就好玩了……