函數(shù)就是一個(gè)可調(diào)用的代碼塊. C++中把它歸為可調(diào)用對(duì)象的一種, 而 Swift 中把函數(shù)歸為一種特殊的閉包.
由于 C++包含了大部分的 C, 在它其中的函數(shù)形式有多種, 但都沒(méi)有 Swift 中的函數(shù)簡(jiǎn)單強(qiáng)大: 簡(jiǎn)單是因?yàn)?Swift 中函數(shù)的形式都?xì)w為一種 —— 后置返回樣式; 強(qiáng)大是因?yàn)?Swift 中函數(shù)可以嵌套 —— 函數(shù)中也可以定義函.,函數(shù)可以嵌套的玄機(jī)大概是閉包可以嵌套吧.
而 C++中相當(dāng)于閉包的lamdba
表達(dá)式實(shí)際上卻是一個(gè)未命名類的未命名對(duì)象, 所以它也和C++的函數(shù)一樣被 C++歸為可調(diào)用對(duì)象的一種.(注意在 C++14對(duì) lamdba 表達(dá)式作了優(yōu)化升級(jí))
所以, 看著這里, 你大概有一個(gè)基本的看法: ** lamdba 和 閉包根本就是兩個(gè)東西, 但是在功能上, 它們的作用又很相似.** 注意名詞: 匿名函數(shù), lamdba, 閉包
形式 | Swift | C++ |
---|---|---|
函數(shù) | func 關(guān)鍵字開(kāi)頭 |
C 風(fēng)格函數(shù)和 auto 開(kāi)頭的尾置返回類型的函數(shù): -> |
閉包與 lamdba | 符號(hào){} 創(chuàng)建閉包 |
**[捕獲列表] (參數(shù)列表:可忽略) -> 返回類型:可忽略 {函數(shù)體} ** |
函數(shù)
Swift 中使用func
來(lái)聲明一個(gè)函數(shù),使用名字和參數(shù)來(lái)調(diào)用函數(shù), 使用->
來(lái)指定函數(shù)返回值的類型.
func greet(person: String, day: String) -> String {
return "Hello \(person), today is \(day)."
}
greet(person: "Bob", day: "Tuesday")
練習(xí): 刪除 day 參數(shù),添加一個(gè)參數(shù)來(lái)表示今天吃了什么午飯。
默認(rèn)情況下,函數(shù)使用它們的參數(shù)名稱作為其參數(shù)的標(biāo)簽。 在參數(shù)名稱前寫入自定義參數(shù)標(biāo)簽,或者寫入 _
以使用無(wú)參數(shù)標(biāo)簽。
func greet(_ person: String, on day: String) -> String {
return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday")
C++中的函數(shù)形式則跟經(jīng)典的 C 函數(shù)沒(méi)啥區(qū)別, C++11可以使用尾置返回類型,使用調(diào)用運(yùn)算符()
并傳入必須的實(shí)參來(lái)執(zhí)行函數(shù), 唯一 沒(méi)有 Swift
給力的就是沒(méi)有函數(shù)標(biāo)簽
, 造成的結(jié)果就是一般來(lái)說(shuō)閱讀別人的代碼很難推測(cè)出相應(yīng)函數(shù)的功能:
#include <iostream>
#include <string>
using namespace std;
auto greet(string person, string day) -> string { // 使用尾置返回類型定義一個(gè)函數(shù), auto 開(kāi)頭
return "Hello " + person + ", today is " + day + ".";
}
int main() { // 這里的main 函數(shù)就是一個(gè)經(jīng)典的有返無(wú)參的 C 風(fēng)格函數(shù)
greet("Bob", "Tuesday");
return 0;
}
Swift 中可以使用元組來(lái)創(chuàng)建一個(gè)復(fù)合值, 比如說(shuō)從一個(gè)函數(shù)中返回多個(gè)值. 元組中的元素可以通過(guò)名字或數(shù)字被引用:
func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
var min = scores[0]
var max = scores[0]
var sum = 0
for score in scores {
if score > max {
max = score
} else if score < min {
min = score
}
sum += score
}
return (min, max, sum)
}
let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9])
print(statistics.sum)
print(statistics.2)
C++中也可以使用元素來(lái)創(chuàng)建一個(gè)復(fù)合值, 利用它來(lái)返回多個(gè)值. 只是 C++元組的成員都是未命名的, 而且只能標(biāo)準(zhǔn)庫(kù)模板函數(shù)get
來(lái)引用其中的元素:
#include <iostream>
#include <tuple> // 包含 tuple 頭文件來(lái)創(chuàng)建元組
#include <vector>
using namespace std;
auto calculateStatistics(vector<int> scores) -> tuple<int, int, int> { // 返回元組的函數(shù)
auto min = scores[0];
auto max = scores.front(); // 等同于scores[0]
auto sum = 0;
for (auto score : scores) {
if (score > max) {
max = score;
}else if (score < min) {
min = score;
}
sum += score;
}
return make_tuple(min, max, sum); // 可以使用 make_tuple 標(biāo)準(zhǔn)庫(kù)函數(shù)生成元組對(duì)象
}
int main() {
vector<int> scores = {5, 3, 100, 3, 9};
auto statistics = calculateStatistics(scores);
cout << get<2>(statistics) << endl; // 使用標(biāo)準(zhǔn)庫(kù)函數(shù)模板來(lái)訪問(wèn)元組中的元素, 元組中的元素下標(biāo)從0開(kāi)始.
return 0;
}
Swift 中的函數(shù)可以帶有可變個(gè)數(shù)的參數(shù), 這些參數(shù)在函數(shù)內(nèi)表現(xiàn)為數(shù)組:
func sumOf(numbers: Int...) -> Int {
var sum = 0
for number in numbers {
sum += number
}
return sum
}
sumOf()
sumOf(numbers: 42, 597, 12)
C++中的函數(shù)同樣也可以帶有可變個(gè)數(shù)的參數(shù):
#include <iostream>
#include <initializer_list> // 包含initializer_list,來(lái)使用initializer_list類型定義含有可變形參的函數(shù)
using namespace std;
// 1, 所用實(shí)參類型相同, 可以使用initializer_list
auto sumOf(initializer_list<int> numbers = {}) -> int { // 此時(shí)參數(shù)都是 int 類型的值的序列, 可以在<>中間寫入?yún)?shù)的類型, 其中 = {}為默認(rèn)參數(shù)
auto sum = 0;
for (auto number : numbers) {
sum += number;
}
return sum;
}
int main() {
sumOf(); // 當(dāng)函數(shù)沒(méi)有傳入任何實(shí)參時(shí), 將使用默認(rèn)參數(shù)
sumOf({42, 597, 12}); // 在大括號(hào)中放入要傳遞的一個(gè)值的序列
return 0;
}
練習(xí): 分別用兩種語(yǔ)言寫一個(gè)計(jì)算參數(shù)平均值的函數(shù)。
Swift 中函數(shù)可以嵌套. 被嵌套的函數(shù)來(lái)重構(gòu)一個(gè)太長(zhǎng)或者太復(fù)雜的函數(shù). 函數(shù)是一等類型, 這意味著函數(shù)可以作為另一個(gè)函數(shù)的返回值. 函數(shù)也可以當(dāng)做參數(shù)傳入另一個(gè)函數(shù).
func returnFifteen() -> Int {
var y = 10
func add() { // 一個(gè)函數(shù)可以嵌套在另一個(gè)函數(shù)中
y += 5
}
add()
return y
}
returnFifteen()
func makeIncrementer() -> ((Int) -> Int) { // 函數(shù)可以返回另一個(gè)函數(shù)
func addOne(number: Int) -> Int {
return 1 + number
}
return addOne
}
var increment = makeIncrementer()
increment(7)
func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool { // 函數(shù)的參數(shù)可以是一個(gè)函數(shù)
for item in list {
if condition(item) {
return true
}
}
return false
}
func lessThanTen(number: Int) -> Bool {
return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen)
C++中除了不能在一個(gè)函數(shù)內(nèi)部嵌套定義函數(shù)之外, 它還不允許返回另一個(gè)函數(shù)、函數(shù)的參數(shù)也不能是函數(shù) —— 盡管如此, 但是我們?nèi)匀豢梢苑祷刂赶蚝瘮?shù)的指針,帶有指向函數(shù)的指針的形參來(lái)達(dá)到同樣的目的:
#include <iostream>
#include <vector>
using namespace std;
auto add(int y) -> int {
return y += 5;
}
auto returnFifteen(int y) -> int { // 函數(shù)不能嵌套, 但是函數(shù)內(nèi)部仍然可以調(diào)用另一個(gè)函數(shù)
return add(y);
}
auto addOne(int number) -> int {
return 1 + number;
}
// 函數(shù)可以返回指向函數(shù)的指針
auto makeIncrementer() -> int (*) (int) { // int (*) (int) 為指向返回int, 帶有一個(gè)參數(shù)為 int參數(shù)的函數(shù)指針
return addOne;
}
auto lessThanTen(int number) -> bool {
return number < 10;
}
// 函數(shù)可以帶有指向函數(shù)的形參
auto hasAnyMatches(vector<int> list, decltype(lessThanTen) *condition) -> bool { // decltype(lessThanTen) 是得到一個(gè)函數(shù)的類型的簡(jiǎn)單方法, 注意此時(shí)只是得到函數(shù)的類型, 寫函數(shù)指針則只需要在后面寫個(gè) * 即可
for (auto item : list) {
if (condition(item)) {
return true;
}
}
return false;
}
int main() {
returnFifteen(10);
auto increment = makeIncrementer();
increment(7);
vector<int> numbers = {20, 19, 7, 12};
hasAnyMatches(numbers, lessThanTen);
return 0;
}
Swift 閉包 和 C++ lamdba 表達(dá)式
Swift 中函數(shù)實(shí)際上是一種特殊的閉包: 它是一段能夠在之后被調(diào)用的代碼.閉包中的代碼能訪問(wèn)閉包所創(chuàng)建作用域中能得到的變量和函數(shù), 即使閉包是在不同的作用域被執(zhí)行的 —— 在嵌套函數(shù)例子中所看到的那樣. 你可以使用{}
來(lái)創(chuàng)建一個(gè)匿名的閉包. 使用in
進(jìn)參數(shù)和返回值類型聲明與閉包函數(shù)體進(jìn)行分離:
numbers.map({
(number: Int) -> Int in
let result = 3 * number
return result
})
練習(xí): 重寫閉包,對(duì)所有奇數(shù)返回0。
有幾種方法可以寫簡(jiǎn)潔的閉包. 當(dāng)閉包的類型已知—— 就像在代理的回調(diào) —— 你可以忽略它的參數(shù)類型、返回值類型之一或者都忽略。而只有單獨(dú)一條語(yǔ)句的閉包隱式地返回該語(yǔ)句的值:
let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)
你可以通過(guò)數(shù)字而不是通過(guò)名字來(lái)引用參數(shù) —— 在非常短的閉包中非常有用. 當(dāng)一個(gè)閉包作為最后一個(gè)參數(shù)傳遞給一個(gè)函數(shù)時(shí), 它可以直接跟在括號(hào)后面. 當(dāng)一個(gè)閉包是傳給函數(shù)的唯一參數(shù)時(shí), 你可以完全忽略括號(hào):
let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)
C++中的 lamdba
表達(dá)式實(shí)際上是一個(gè)未命名類的未命名對(duì)象 —— 是一個(gè)可調(diào)用的函數(shù)對(duì)象.一個(gè) lamdba
表達(dá)式表示一個(gè)可調(diào)用代碼單元.形式如: [捕獲列表] (參數(shù)列表) -> 返回類型 { 函數(shù)體 }
, lamdba
必須使用尾置返回來(lái)指定返回類型. 雖然一個(gè) lamdba
可以出現(xiàn)在一個(gè)函數(shù)中, 使用其局部變量, 但它只能使用那些明確指明的變量. lamdba
通過(guò)捕獲列表來(lái)包含我們需要使用的局部變量, 當(dāng)然我們可以讓lamdba
隱式捕獲變量, 在捕獲列表中寫一個(gè)&
或=
. &
告訴編譯器采用捕獲引用方式, =
告訴編譯器采用值捕獲方式,C++14已經(jīng)給 lamdba 做出了升級(jí)和優(yōu)化
:
#include <iostream>
#include <vector>
using namespace std;
int main() {
auto otherCompareValue = 10;
vector<int> numbers = {20, 19, 7, 12};
sort(numbers.begin(), numbers.end(), [=](const int &a, const int &b) -> bool { // [=]表示采用值捕獲方式, const int &a 中 &a 表示引用這個(gè)參數(shù). 你可以把[=]中的=替換為otherCompareValue來(lái)明確指出我要捕獲otherCompareValue這個(gè)局部變量.
if (a < b && a < otherCompareValue) {
cout << a << "小于" << otherCompareValue << endl;
}
return a < b;
});
for (auto i : numbers) {
cout << i << " ";
}
return 0;
}```
我們可以忽略參數(shù)列表和返回類型, 但必須永遠(yuǎn)包含捕獲列表和函數(shù)體.如果忽略返回類型, `lambda `根據(jù)函數(shù)體中的代碼來(lái)推斷出返回類型 —— 先別覺(jué)得它智能 —— 如果函數(shù)只是一個(gè)` return `語(yǔ)句, 則返回類型從返回的表達(dá)式推斷而來(lái), 否則返回類型為 `void`:
include <iostream>
using namespace std;
int main() {
auto f = [] {return 42;};
cout << f() << endl;
return 0;
}