函數包含兩個要素:函數簽名和函數體。程序員
其中函數簽名肯定了函數的類型;函數體肯定了它的功能。編程
說到函數式編程,核心就是咱們能夠把函數當作「一等公民」:能夠聲明函數變量、能夠賦值、能夠當作參數傳遞給函數、也能夠做爲函數返回的類型。數組
當咱們定義一個函數類型時,函數名、形參列表、返回值、函數體缺一不可。bash
當咱們聲明一個函數變量時,則不須要指定函數體,且以;
結尾:ide
// 如下是一個函數定義
int func(int a, int b) {
return a + b;
}
// 如下是函數聲明
int func(int a, int b);
複製代碼
C++中變量的類型包括:函數式編程
對於函數的形參和返回值而言,它們能夠是除數組類型或函數類型以外其餘的任意類型。函數
那麼若是確實要返回數組類型或者函數類型怎麼辦呢?這就須要藉助到指針了:指向數組的指針,和指向函數的指針。ui
// 定義一個指向int[10]類型的數組的指針
int a[10];
int (*pa) [10] = a;
// 定義一個指向 int (int)類型的函數的指針
int (*pf) (int, int) = func;
複製代碼
使用數組指針訪問數組時,必須寫上解指針符號:this
(*pa)[0] = 1
複製代碼
使用函數指針調用函數時,能夠省略解指針符號:spa
pf(a, b);
複製代碼
接下來看看如何定義一個函數,返回一個數組指針:
int (*func1(int val))[10]
{
int (*pa)[10] = (int(*)[10])(new(int)[10]);
for(auto i = 0; i < 10; ++i) {
(*pa)[i] = val + i;
}
return pa;
}
int main() {
auto pa = func1(3);
// 由於func1是在堆上分配的數組,因此須要delete它
delete (int *)pa;
}
複製代碼
再看如何返回一個函數指針:
// func2 形參列表爲空,而後返回一個函數指針:須要2個int形參,返回int
int (*func2())(int, int)
{
return func;
}
複製代碼
當咱們把一個函數名稱當作值使用時(即除了調用函數以外的其它用法),它會自動轉換成函數指針。
tips
auto func2() -> int (*)(int, int);
複製代碼
decltype
定義函數指針類型。可是decltype
一個函數名稱時,獲得的是函數類型,而不是函數指針類型:// 定義一個函數
int retfunc(const int& a, const int& b);
// 定義一個函數,返回指向int(const int&, const int&)函數類型的指針
// 如下兩種寫法等價
int(*getFunc(const int& x))(const int&, const int&);
decltype(retfunc)* getFunc(const int& x);
複製代碼
lambda表達式,就是傳說中的匿名函數:即沒有名字的「函數」。
int main() {
int a = 10;
auto fl = [&a](int x) -> int { a++; return x > a ? a : x };
std::cout << a << " " << fl(3) << " " << a << std::endl;
return 0;
}
複製代碼
例如,上例中,咱們定義了一個lambda對象fl
:它按引用捕獲了調用它的函數的局部變量a,須要傳入一個參數,並返回int值。
在lambda表達式中,僅能也是隻須要捕獲定義它的函數的自動局部變量。對於靜態局部變量或函數外部變量,不用捕獲也是能夠訪問的。
對於在類的成員函數中定義的lambda表達式,除了能夠捕獲局部變量以外,還能夠捕獲這個類的非靜態的成員變量(跟捕獲局部變量同樣)。對成員變量,還有個額外的規則:若是捕獲了this
指針,那麼自動獲取全部成員變量的訪問權限。
若是須要在lambda表達式中修改按值捕獲的變量,須要在參數列表和尾置返回類型之間加上mutable
關鍵字:
auto fl = [a](int x) mutable -> int {
return x + a;
}
複製代碼
bind
綁定參數auto newCallable = bind(callable, arg_list);
複製代碼
bind
能夠看作是從一個可調用對象到另一個可調用對象的映射。跟lambda表達式同樣,bind
返回的也是一個可調用對象。
callable
和newCallable
這兩個可調用對象的形參列表,以及實參的順序都是能夠隨意調整的。
在調用bind
時,咱們在arg_list
中,不只能夠傳入任意具體的實參變量,也能夠傳入形如_n
的「佔位符」。佔位符的做用,就是將調用newCallable
時的參數,映射到callable
時的參數:_1
就是映射成newCallable
的第一個參數,_2
就是第二個參數,依次類推。有多少個「佔位符」,就表示在調用newCallable
時須要傳入多少個參數。
舉個例子:
// 咱們有個須要傳入2個參數的函數funcA
int funcA(int x, int y);
int a;
// 有一個佔位符,因此調用funcB時,須要傳入一個參數
auto funcB = bind(funcA, a, _1);
int b;
funcB(b); // 等價於 funcA(a, b)
複製代碼
並且在arg_list
中,_n
的順序和位置是任意的,好比_2
能夠在_1
前面:
int funcA(int x, int y, int z);
int a;
auto funcB = bind(funcA, _2, a, -1);
int b, c;
funcB(b, c); // 等價於 funcA(c, a, b);
複製代碼
注:
_n
是定義在名字空間std::placeholders
中的,因此須要先using namespace std::placeholders
。
在使用bind
作函數映射時,對於那些不是佔位符的參數,是將其拷貝到bind
返回的可調用對象中的。若是某些參數不支持拷貝呢?好比ostream
。
可使用標準庫裏的ref
函數返回一個變量的引用類型:
ostream& print(ostream& os, const string& s, char c);
ostream os;
auto f = bind(print, ref(os), _1, ' ');
f("hello, world");// 等價於 print(os, "hello, world", ' ');
複製代碼
其實這沒有改變bind
的拷貝行爲,由於ref()
返回的就是一個可拷貝的對象,只不過它的內部定義了一個原來參數的引用類型,而且保證拷貝後都引用同一個變量。
不信,咱們能夠本身實現一個類myref
(爲了簡單起見,沒有實現成模板類,只能轉ostream
引用):
class myref {
public:
// 包含了引用類型的成員變量,只能在構造函數裏面顯式初始化
myref(ostream& os) : os_(os) {}
// 保證能夠將它轉換成一個ostream引用類型
operator ostream& ()
{
return os_;
}
private:
ostream& os_;
};
複製代碼
除了ref
以外,還能夠用cref
返回變量的const
引用類型。
bind針對成員函數,提供了特別的支持,只要你把指向類實例的指針做爲第二個參數傳遞便可。
class Test {
public:
int func(int v);
};
Test t;
auto f = bind(&Test::func, &t, std::placeholders::_1);
複製代碼
注意,對普通函數,當咱們把函數名字當作值使用時,會自動轉換成函數指針;可是對於成員函數,咱們必須顯式寫上取址符。
若是一個類實現了函數調用運算符operator()
,那麼它的對象就是一個函數對象。若是這個類還定義了其它的成員變量,那麼它的對象就是一個有狀態的函數對象,比普通的函數擁有更強大的能力。
知識點:lambda表達式就是一個函數對象:
- 它定義了函數調用運算符
operator()
;- 若是它按值捕獲了外部變量,那麼它就定義了相應的成員變量,並在構造函數中初始化這些成員變量;
- 若是它按引用捕獲了外部變量,那麼編譯器會直接使用這些引用,而不會在類中建立相應的成員變量。因此須要程序員保證在
lambda
對象生存期間,它捕獲的引用變量要一直可訪問;- 默認
operator()
是const
的,若是它被定義成mutable
,那麼它的operator()
就不是const
的。
函數/函數指針、bind返回值、lambda表達式、函數對象等,這5種對象都有一個特色就是咱們均可以對它執行函數調用。咱們將其稱爲「可調用對象」。
「可調用對象」的一個重要屬性,就是它的調用形式(或函數簽名):包括返回類型和一個實參類型列表。
雖然這5種可調用對象的類型是不同的,可是他們可能擁有相同的調用形式。
例如,如下對象都實現了相同的調用形式int (int, int)
:
// 普通函數和函數指針
int add(int a, int b) { return a + b; }
int (*padd)(int, int) = add;
// lambda表達式
auto mod = [](int a, int b) -> int { return a - b; }
// 函數對象
struct divide {
int operator()(int den, int div) {
return den / div;
}
};
複製代碼
若是咱們要把這些對象放進同一個容器呢?由於它們類型不一樣,是無法作到的:
std::map<std::string,int(*)(int,int)> binops;
binops.insert(make_pair("add", add)); // OK
binops.insert(make_pair("mod", mod)); // 錯誤,類型不匹配
binops.insert(make_pair("divide", divide())); // 錯誤,類型不匹配
複製代碼
咱們須要有一種類型,全部這些可調用對象都能自動轉換成這種類型。標準庫提供的function
類就是啦!
function<int(int,int)> f;
f = add; // OK
f = mod; // OK
f = divide(); // OK
f = bind(add, _1, _2); // OK
複製代碼
只要咱們定義一個調用形式同樣的function
對象,就能夠保存全部調用形式同樣的可調用對象。
Q:如何實現將類A自動轉換成類B?
A: 有兩種方法:在類A中重載類型轉換運算符;在類B中重載複製構造函數和賦值運算符。可是不要兩種方法同時用,會產生二義性,致使編譯失敗。