- 函數(shù)是組織良好且可重復(fù)使用的以蕴,用來實(shí)現(xiàn)單一或相關(guān)功能的代碼塊朝氓,用于提高模塊化和復(fù)用性皆刺。
- 編寫函數(shù)的目的是為了將需要多行代碼實(shí)現(xiàn)的復(fù)雜問題分解為一系列簡單的任務(wù)來解決针饥。
Go語言是編譯型的,因此函數(shù)的順序和位置是無關(guān)緊要的浪秘,鑒于可讀性推薦將main
主函數(shù)編寫在文件前面蒋情,其它函數(shù)按照一定邏輯順序向下編寫。
Go語言支持普通函數(shù)耸携、匿名函數(shù)棵癣、閉包,從設(shè)計(jì)上對函數(shù)進(jìn)行了優(yōu)化和改進(jìn)夺衍。
Go語言中函數(shù)是一等公民(first-class)狈谊,函數(shù)本身可作為值來傳遞,函數(shù)支持匿名函數(shù)和閉包,函數(shù)可以滿足接口河劝。
聲明函數(shù)
函數(shù)構(gòu)成了代碼執(zhí)行的邏輯結(jié)構(gòu)壁榕,是程序的基本模塊。
Go語言中函數(shù)由關(guān)鍵字func
赎瞎、函數(shù)名牌里、參數(shù)列表、返回值务甥、函數(shù)體二庵、返回語句構(gòu)成。
func fnname(argslist)(retlist){
//fnbody
}
- 形參列表
argslist
描述了函數(shù)的參數(shù)名以及參數(shù)類型缓呛,參數(shù)屬于局部變量催享,參數(shù)值由調(diào)用者提供。 - 返回值列表
retlist
描述了函數(shù)返回值的名稱以及類型哟绊,若無返回值或返回一個無名的變量因妙,返回值列表的括號可省略。 - 若函數(shù)聲明中不包括返回值列表則函數(shù)體執(zhí)行完畢后不會有返回值
例如:求直角三角形斜邊票髓,勾股定理攀涵,勾三股四弦五。
package main
import (
"fmt"
"math"
)
func hypot(x, y float64) float64{
return math.Sqrt(x*x + y*y)
}
func main() {
fmt.Println(hypot(3, 4))//5
}
若形參或返回值具有相同類型洽沟,不必針對每個形參都添加參數(shù)類型以故。
func fn(i, j, k int, s, t string){}
形參可使用_
空白標(biāo)識符用來強(qiáng)調(diào)參數(shù)未被使用
package main
import "fmt"
func add(x, y int) int {return x + y}
func sub(x, y int) (z int) {return x - y}
func first(x, _ int) int {return x}
func zero(int, int) int {return 0}
func main() {
fmt.Printf("x + y = %d\n", add(1, 2))//x + y = 3
fmt.Printf("x - y = %d\n", sub(1, 2))//x - y = -1
fmt.Printf("first = %d\n", first(1, 2))//first = 3
fmt.Printf("zero = %d\n", zero(1, 2))//zero = 0
}
當(dāng)函數(shù)執(zhí)行到代碼塊最后一行之前或是return
語句時退出
函數(shù)類型
Go語言中函數(shù)是一種類型,因此可以和其他類型一樣保存在變量中裆操。
package main
import "fmt"
//定義函數(shù)
func fire(){
fmt.Printf("fire")
}
func main() {
//將變量聲明為函數(shù)類型func()怒详,變量fn即回調(diào)函數(shù),此時fn的值為nil踪区。
var fn func()
fmt.Printf("fn = %v, type = %T\n", fn, fn)//fn = <nil>, type = func()
//將fire()函數(shù)作為值賦給函數(shù)變量fn昆烁,此時fn的只也就是fire()函數(shù)。
fn = fire
fmt.Printf("fn = %v, type = %T\n", fn, fn)//fn = 0x1076fe0, type = func()
//使用函數(shù)變量fn執(zhí)行函數(shù)調(diào)用缎岗,實(shí)際調(diào)用的是fire()函數(shù)
fn()//fire
}
函數(shù)類型又稱為函數(shù)的標(biāo)識符
package main
import "fmt"
func fn(x, y int) int {return x + y}
func main() {
fmt.Printf("%T\n", fn)//func(int, int) int
}
若兩個函數(shù)形參列表和返回值列表中的變量類型一一對應(yīng)静尼,則這兩個函數(shù)被認(rèn)為具有相同的類型和標(biāo)識符。
package main
import "fmt"
func add(x, y int) int {return x + y}
func sub(x, y int) int {return x - y}
func main() {
fmt.Printf("%T\n", add)//func(int, int) int
fmt.Printf("%T\n", sub)//func(int, int) int
}
形參和返回值的變量名并不會影響函數(shù)標(biāo)識符传泊,也不會影響參數(shù)類型的省略鼠渺。
函數(shù)在每次調(diào)用時必須按照聲明時的順序?yàn)樗袇?shù)提供實(shí)參(參數(shù)值)
Go語言中形參沒有默認(rèn)的參數(shù)值,也沒有任何方法通過參數(shù)名指定形參眷细,因此形參和返回值的變量名對于函數(shù)調(diào)用者而言是沒有意義的拦盹。
函數(shù)中實(shí)參通過值傳遞的方式進(jìn)行賦值,因此函數(shù)形參是實(shí)參的拷貝薪鹦,因此對形參進(jìn)行修改不會影響到實(shí)參掌敬。不過若實(shí)參包含引用類型,比如指針池磁、切片奔害、映射、函數(shù)地熄、通道等华临,實(shí)參則可能會由于函數(shù)的間接引用而被修改。
多返回值
Go語言中函數(shù)支持多返回值端考,多返回值能夠方便地獲得函數(shù)執(zhí)行后的多個參會參數(shù)雅潭。
Go語言會使用多返回值的最后一個返回參數(shù),返回函數(shù)執(zhí)行中可能發(fā)生的錯誤却特。
與其它語言的返回值相比
- C/C++ 語言中只支持一個返回值扶供,當(dāng)需要多值返回時可使用結(jié)構(gòu)體。也可在參數(shù)中使用指針變量裂明,然后在函數(shù)內(nèi)部修改外部傳入的變量值椿浓,實(shí)際返回計(jì)算結(jié)果。
- C++ 語言為了安全性闽晦,建議在參數(shù)返回?cái)?shù)據(jù)時使用引用替代指針扳碍。
- C# 語言中沒有多返回值,C#語言后期加入的
ref
和out
關(guān)鍵字能夠通過函數(shù)的調(diào)用參數(shù)以獲得函數(shù)體中修改的數(shù)據(jù)仙蛉。 - Lua語言中雖然沒有指針但支持多返回值笋敞,特別適用于大塊數(shù)據(jù)。
Go語言既支持安全指針荠瘪,也支持多返回值夯巷,因此在使用函數(shù)進(jìn)行邏輯編寫時更加方便。
- 同一類型返回值
若返回值是同一類型哀墓,則可使用括號()
將多個返回值類型括起來鞭莽,用逗號,
分隔每個返回值的類型。在函數(shù)體內(nèi)return
語句返回時麸祷,值列表的順序需要和函數(shù)聲明的返回值類型保持一致澎怒。
package main
import "fmt"
func swap(first, last string) (string, string){
return last, first
}
func main() {
surname, name := swap("Mahesh", "Kumar")
fmt.Printf("%s %s\n", surname, name)// Kumar Mahesh
}
純類型的返回值對于代碼的可讀性并不是很友好,特別是在同類型的返回值出現(xiàn)時阶牍,會無法區(qū)分每個返回值參數(shù)的含義喷面。
- 帶變量名的返回值
Go語言支持對返回值進(jìn)行命名,這樣返回值和參數(shù)一樣擁有參數(shù)變量名稱和類型走孽。
待命名的返回值變量的默認(rèn)值是其類型的默認(rèn)值
例如:根據(jù)矩形的長和寬計(jì)算周長和面積
package main
import "fmt"
//對函數(shù)返回值進(jìn)行命名
func rect(length, width float64) (area, perimeter float64){
//已命名返回值的變量與函數(shù)局部變量一樣惧辈,可以對返回值變量進(jìn)行賦值和值獲取。
area = length * width
perimeter = (length + width) * 2
//當(dāng)函數(shù)使用命名返回值時在return中可以不填寫返回值列表磕瓷,若填寫也可以盒齿。
return
}
func main() {
area, perimeter := rect(10, 20)
fmt.Printf("area = %f, perimeter = %f\n", area, perimeter)//area = 200.000000, perimeter = 60.000000
}
同一類型返回值和命名返回值兩種類型念逞,只能二選一不能混用典鸡,混用時將會發(fā)生編譯錯誤摆碉。
// syntax error: mixed named and unnamed function parameters
func rect(length, width float64) (area, perimeter float64, float64){
area = length * width
perimeter = (length + width) * 2
return
}
語法錯誤:在函數(shù)參數(shù)中混合使用了命名和非命名參數(shù)
函數(shù)調(diào)用
函數(shù)定義后可通過調(diào)用的方式讓當(dāng)前代碼跳轉(zhuǎn)到被調(diào)用的函數(shù)中去執(zhí)行,調(diào)用前的函數(shù)局部變量會被保存起來不會丟失衙伶,被調(diào)用的函數(shù)運(yùn)行結(jié)束后符匾,會恢復(fù)到調(diào)用函數(shù)的下一行繼續(xù)執(zhí)行代碼叨咖,之前的局部變量也能繼續(xù)訪問。
函數(shù)內(nèi)的局部變量只能在函數(shù)體中使用啊胶,函數(shù)調(diào)用結(jié)束后甸各,這些局部變量都會被釋放因此會失效。
Go語言中函數(shù)調(diào)用的格式
返回值變量列表 = 函數(shù)名(參數(shù)列表)
- 變量名:需要調(diào)用的函數(shù)的名稱
- 參數(shù)列表:參數(shù)變量以逗號分隔焰坪,尾部無須使用分號趣倾。
- 返回值變量列表:多個返回值時需使用逗號分隔
匿名函數(shù)
Go語言支持匿名函數(shù),即在需要使用函數(shù)時定義的函數(shù)某饰,匿名函數(shù)不包含函數(shù)名誊酌,可用于創(chuàng)建內(nèi)聯(lián)函數(shù)。
Go語言中匿名函數(shù)可形成閉包露乏,匿名函數(shù)又稱為函數(shù)字面量碧浊。
匿名函數(shù)沒有函數(shù)名只有函數(shù)體,函數(shù)可以作為一種類型被賦值給函數(shù)類型的變量瘟仿。
匿名函數(shù)往往會以變量的方式進(jìn)行傳遞箱锐,與C語言的回調(diào)函數(shù)類似,不同的是Go語言支持隨時在代碼中定義匿名函數(shù)劳较。
匿名函數(shù)不需要定義函數(shù)名稱的一種函數(shù)實(shí)現(xiàn)方式驹止,由一個不帶函數(shù)名的函數(shù)聲明和函數(shù)體組成。
定義匿名函數(shù)
func(參數(shù)列表) (返回值列表) {
函數(shù)體
}
匿名函數(shù)的定義簡單來說就是沒有名字的普通函數(shù)定義
在定義時調(diào)用匿名函數(shù)
匿名函數(shù)可以在聲明后調(diào)用观蜗,即自執(zhí)行函數(shù)臊恋。
package main
import "fmt"
func main() {
func(data int){
fmt.Printf("data = %v\n", data)// data = 1000
}(1000)
}
將匿名函數(shù)賦值給變量
package main
import "fmt"
func main() {
fn := func(data int){
fmt.Printf("data = %v\n", data)// data = 1000
}
fn(1000)
}
匿名函數(shù)本身就是一種值,可以方便地保存在各種容器中實(shí)現(xiàn)回調(diào)函數(shù)和操作封裝墓捻。
匿名函數(shù)當(dāng)作回調(diào)函數(shù)
例如:對切片遍歷時訪問每個元素
package main
import "fmt"
func loop(slice []int, fn func(int)){
for _,v := range slice{
fn(v)
}
}
func main() {
slice := []int{1, 2, 3, 4}
loop(slice, func(item int){
fmt.Printf("item = %v\n", item)
})
}
例如:對字符串左右兩邊的空格進(jìn)行去除
使用匿名函數(shù)實(shí)現(xiàn)操作封裝
例如:將匿名函數(shù)作為map
的鍵值抖仅,通過命令行參數(shù)動態(tài)調(diào)用匿名函數(shù)。
package main
import (
"flag"
"fmt"
)
//定義命令行參數(shù)type砖第,在命令行輸入--type可其后的字符串傳入params指針變量中撤卢。
var params = flag.String("type", "", "type comment")
func main() {
//解析命令行參數(shù),解析完畢后params指針變量將指向命令行傳入的值梧兼。
flag.Parse()
//定義從字符串映射到函數(shù)的map放吩,然后填充。
typeMap := map[string] func(){
//初始化map的鍵值對羽杰,值為匿名函數(shù)渡紫。
"fire":func(){
fmt.Printf("fire")
},
"run": func(){
fmt.Printf("run")
},
}
//typeMap是一個*string類型的指針變量
//使用*params獲取命令行傳入的值并在map中查找對應(yīng)命令行參數(shù)指定的字符串函數(shù)
if fn,ok := typeMap[*params]; ok{
//若在map定義中存在參數(shù)則調(diào)用函數(shù)
fn()
}else{
fmt.Printf("not found")
}
}
運(yùn)行測試
$ go run test.go --type fire
fire
使用函數(shù)實(shí)現(xiàn)接口
函數(shù)在數(shù)據(jù)類型中屬于一等公民到推,其他類型能夠?qū)崿F(xiàn)接口,函數(shù)也可以惕澎。
例如:使用結(jié)構(gòu)體實(shí)現(xiàn)接口
package main
import "fmt"
//定義調(diào)用器接口
type Invoker interface {
Call(interface{})//待實(shí)現(xiàn)調(diào)用方法
}
//定義結(jié)構(gòu)體類型
type Struct struct {
}
//實(shí)現(xiàn)Invoker調(diào)用程序的Call調(diào)用方法莉测,可傳入任意類型interface{}的值。
func (this *Struct) Call(param interface{}){
fmt.Println(param)
}
func main() {
//聲明接口變量
var invoker Invoker
//實(shí)例化結(jié)構(gòu)體類型
s := new(Struct)
//將結(jié)構(gòu)體實(shí)例賦值給接口
invoker = s
//使用接口變量調(diào)用結(jié)構(gòu)體實(shí)例方法
invoker.Call("hello")
}
例如:使用函數(shù)實(shí)現(xiàn)接口
函數(shù)聲明不能直接實(shí)現(xiàn)接口集灌,需要將函數(shù)定義為類型后,使用類型實(shí)現(xiàn)結(jié)構(gòu)體复哆。當(dāng)類型方法被調(diào)用時欣喧,還需調(diào)用函數(shù)本體。
package main
import "fmt"
//定義調(diào)用者接口
type Invoker interface {
Call(interface{})//待實(shí)現(xiàn)接口
}
//定義函數(shù)為類型
type Caller func(interface{})
//實(shí)現(xiàn)調(diào)用者方法
func (fn Caller) Call(param interface{}) {
fn(param)
}
func main() {
//聲明接口變量
var invoker Invoker
//將匿名函數(shù)轉(zhuǎn)換為Caller類型后再賦值給接口變量
invoker = Caller(func(v interface{}) {
fmt.Println(v)
})
//使用接口變量調(diào)用Caller類型的Call方法梯找,內(nèi)部會調(diào)用函數(shù)本體唆阿。
invoker.Call("hello")
}
閉包
閉包又稱為詞法閉包(lexical closure)或函數(shù)閉包(function closure),是函數(shù)式編程語言中用于實(shí)現(xiàn)詞法范圍的名稱綁定技術(shù)锈锤。 閉包并非某種語言特有的機(jī)制驯鳖,只是經(jīng)常會出現(xiàn)在函數(shù)式編程語言中,因?yàn)楹瘮?shù)式編程語言中函數(shù)是一等公民(first-class)久免。
從操作實(shí)現(xiàn)上來將浅辙,閉包是將函數(shù)及其運(yùn)行環(huán)境(引用環(huán)境)打包存儲的一條記錄。函數(shù)的運(yùn)行環(huán)境又稱為執(zhí)行上下文(execution context)阎姥,包括函數(shù)運(yùn)行時所處的內(nèi)部環(huán)境和所依賴的外部環(huán)境记舆。
Go語言中匿名函數(shù)可作為閉包,閉包和普通函數(shù)的區(qū)別在于呼巴,普通函數(shù)被調(diào)用者執(zhí)行完畢后會丟棄環(huán)境泽腮,而閉包則依然會保留運(yùn)行環(huán)境。
函數(shù)的運(yùn)行環(huán)境只是一種映射衣赶,它會將函數(shù)的每個自由變量與創(chuàng)建閉包時名稱綁定的值或引用相互關(guān)聯(lián)诊赊。函數(shù)的自由變量特指在本地使用,但卻在封閉的范圍內(nèi)定義的變量府瞄。
自由變量是相當(dāng)于閉包或匿名函數(shù)而言的外部變量碧磅,由于該變量的定義不受自身控制,因此對閉包自身來說是自由的遵馆,因?yàn)椴粫艿介]包的約束续崖。
與普通函數(shù)不同的是閉包允許函數(shù)通過閉包的值副本或引用來訪問那些被捕獲的變量,即使函數(shù)在其作用域之外被調(diào)用团搞。也就是說閉包提供了一種可持續(xù)訪問被捕獲變量的能力严望,進(jìn)而擴(kuò)大了變量的作用域。
閉包提供了持續(xù)暴露變量的機(jī)制逻恐,使外界能夠訪問原本應(yīng)該私有的變量像吻,實(shí)現(xiàn)了全局變量的作用域效果峻黍。由此可見,一旦變量被閉包捕獲后拨匆,外界使用者可以訪問被捕獲的變量值或引用姆涩,相當(dāng)于訪問了私有變量。
綜上所述惭每,閉包是函數(shù)式編程中實(shí)現(xiàn)名稱綁定的技術(shù)骨饿,直觀表現(xiàn)為函數(shù)嵌套以提升變量作用范圍,使原本壽命短暫的局部變量獲得了長生不老的能力台腥。只要被捕獲到的自由變量一直處于使用中宏赘,系統(tǒng)就不會回收其內(nèi)存空間。
閉包(Closure)又稱為Lambda表達(dá)式
閉包是由函數(shù)及其相關(guān)的引用環(huán)境共同組合而成的實(shí)體黎侈,即 “閉包 = 函數(shù) + 運(yùn)行(引用)環(huán)境”察署。因此有人又稱:”對象是附有行為的數(shù)據(jù),閉包是附有數(shù)據(jù)的行為“峻汉。
理解閉包首先需要了解函數(shù)的執(zhí)行環(huán)境(execution context)又稱為執(zhí)行上下文贴汪。
- 函數(shù)是指執(zhí)行的代碼塊,由于自由變量被包含在代碼塊中休吠,因此這些自由變量以及它們引用的對象沒有被釋放扳埂。
- 運(yùn)行環(huán)境是是指為自由變量提供綁定的計(jì)算環(huán)境,又稱為作用域瘤礁。
閉包包含了自由變量聂喇,自由變量是指沒有綁定到特定對象的變量。自由變量不是在當(dāng)前代碼塊內(nèi)或任何全局上下文中定義的蔚携,而是在定義代碼塊的環(huán)境中定義的局部變量希太。
閉包的體現(xiàn)形式在函數(shù)體內(nèi)返回另一個函數(shù)
閉包是指可以包含自由變量的代碼塊,自由變量特指沒有綁定到特定對象的變量酝蜒。
自由變量并不在代碼塊內(nèi)或全局上下文中定義誊辉,而會在定義代碼塊的環(huán)境中定義。
當(dāng)代碼塊所在的環(huán)境被外部調(diào)用時亡脑,代碼塊及其所引用的自由變量會構(gòu)成閉包堕澄。
要執(zhí)行的代碼塊會為自由變量提供綁定的計(jì)算環(huán)境(作用域),由于自由變量包含在代碼塊中霉咨,因此自由變量及其引用的對象不會被釋放掉蛙紫。
理解閉包最直觀的方法就是將閉包函數(shù)當(dāng)作一個類,一個閉包函數(shù)調(diào)用相當(dāng)于實(shí)例化類途戒,然后再從類的角度中區(qū)分那些是全局變量坑傅,那些是局部變量。實(shí)際上在Objective-C中閉包就是使用類來實(shí)現(xiàn)的喷斋。
閉包的價值在于可作為函數(shù)對象或匿名函數(shù)唁毒,對類型系統(tǒng)而言蒜茴,意味著不僅要表示數(shù)據(jù)還要表示代碼。
支持閉包的大多數(shù)語言都將函數(shù)作為第一公民(第一級對象)浆西,函數(shù)可以存儲到變量中作為參數(shù)傳遞給其他函數(shù)粉私,而且函數(shù)還可以被其他函數(shù)動態(tài)的創(chuàng)建和返回。
閉包一般是以匿名函數(shù)的形式出現(xiàn)近零,能夠動態(tài)且靈活的創(chuàng)建和傳遞诺核,由此體現(xiàn)出函數(shù)式編程的特點(diǎn)。
閉包的缺點(diǎn)在于函數(shù)中的變量會被保存在內(nèi)存中久信,因此內(nèi)存消耗很大窖杀,所以閉包不能濫用。
閉包是引用了自由變量的函數(shù)入篮,被引用的自由變量和函數(shù)會一同存在陈瘦,即使已經(jīng)離開了自由變量的環(huán)境也不會被釋放或刪除幌甘,在閉包中可以繼續(xù)使用這些自由變量潮售。
同一個函數(shù)與不同引用環(huán)境組合后可形成不同的實(shí)例
函數(shù)類型和結(jié)構(gòu)體一樣可以被實(shí)例化,由于函數(shù)本身不能存儲任何信息锅风,只有與引用環(huán)境結(jié)合后形成的閉包才具有記憶性酥诽。
Go語言不能在函數(shù)內(nèi)部聲明函數(shù),卻可以在函數(shù)體內(nèi)聲明函數(shù)類型的變量皱埠。
與普通變量聲明不同的是肮帐,Go語言不能在函數(shù)內(nèi)聲明另一個函數(shù),Go語言不支持在函數(shù)內(nèi)部顯式地嵌套定義函數(shù)边器,但卻可以定義匿名函數(shù)训枢。因此,Go語言不支持函數(shù)嵌套忘巧,比如在Go源文件中恒界,函數(shù)聲明都是出現(xiàn)在最外層的。
Go語言支持匿名函數(shù)砚嘴,匿名函數(shù)是沒有指定函數(shù)名稱的函數(shù)十酣。匿名函數(shù)相當(dāng)于一個內(nèi)聯(lián)語句或表達(dá)式,匿名函數(shù)的優(yōu)越性在于可以直接使用函數(shù)內(nèi)部的變量而無需事先聲明际长。
匿名函數(shù)是指不需要定義函數(shù)名的一種函數(shù)的實(shí)現(xiàn)方式耸采,匿名函數(shù)由一個不帶函數(shù)名的函數(shù)聲明和函數(shù)體構(gòu)成。
func(x, y int) int {
return x + y
}
Go語言中函數(shù)也是一種數(shù)據(jù)類型工育,因此可以聲明函數(shù)類型的變量虾宇,使用函數(shù)類型的變量來接收函數(shù)。
Go語言中所有的函數(shù)都是值類型的如绸,既可以作為參數(shù)傳遞也可以作為返回值傳遞文留。
Go語言中可以將匿名函數(shù)賦值給變量
fn := func() int {
}
匿名函數(shù)可作為閉包
閉包是內(nèi)層函數(shù)引用了外層函數(shù)中的變量好唯,也就是所謂的引用自由變量的函數(shù)。
閉包的返回值也是一個函數(shù)
閉包只是在形式和表現(xiàn)上像函數(shù)燥翅,但實(shí)際并不是函數(shù)骑篙。因?yàn)楹瘮?shù)是編譯期靜態(tài)的概念,閉包是運(yùn)行期動態(tài)的概念森书。函數(shù)只是一段可執(zhí)行的代碼靶端,這些代碼在函數(shù)被定義后就確定了,也就是說編譯后就固化了凛膏,因此不會在執(zhí)行時發(fā)生變化杨名。每個函數(shù)在內(nèi)存中只會存在一份實(shí)例,所以說一個函數(shù)只是一個實(shí)例猖毫,只要得到函數(shù)的入口點(diǎn)即可執(zhí)行函數(shù)台谍。而閉包在運(yùn)行時可以擁有多個實(shí)例,不同的引用環(huán)境和相同的函數(shù)組合可以產(chǎn)生不同的實(shí)例吁断。
所謂的引用環(huán)境是指在程序執(zhí)行過程中的某個點(diǎn)上所有處于活躍狀態(tài)的約束所組成的集合趁蕊,這里的約束是指一個變量的名字和其代表的對象之間的聯(lián)系。
函數(shù)式編程是一種編程模型仔役,將計(jì)算機(jī)運(yùn)算看作數(shù)學(xué)中函數(shù)的計(jì)算掷伙,以避免狀態(tài)以及變量的概念。
函數(shù)式編程中函數(shù)是一等公民(First-Class Value又兵,第一類對象)任柜,無需像命令式語言那樣借助函數(shù)指針,委托操作函數(shù)沛厨。因此函數(shù)可以作為另一個函數(shù)的返回值或參數(shù)宙地,也可以作為某個變量的值賦給某個變量。另外逆皮,函數(shù)可以嵌套定義宅粥,即在函數(shù)內(nèi)部可以定義另一個函數(shù)。由于有了嵌套函數(shù)這種結(jié)構(gòu)页屠,也就產(chǎn)生了閉包問題粹胯。
函數(shù)式編程中可以將函數(shù)作為參數(shù)傳遞,因此又稱之為高階函數(shù)辰企。在數(shù)學(xué)和計(jì)算機(jī)科學(xué)中风纠,高階函數(shù)至少需要滿足下列兩個條件中的之一:
- 接受一個或多個函數(shù)作為輸入
- 輸出一個函數(shù)
為什么閉包需要將引用環(huán)境和函數(shù)組合起來呢?因?yàn)樵谥С智短鬃饔糜虻恼Z言中牢贸,有時不能簡單直接地確定函數(shù)的引用環(huán)境竹观。
閉包可以理解為定義在函數(shù)內(nèi)部的函數(shù),本質(zhì)上閉包是將函數(shù)內(nèi)部和外部連接的橋梁,是函數(shù)與其引用環(huán)境的組合臭增。
下面以計(jì)算斐波拉契數(shù)列為例懂酱,來分析下閉包。
非巴拉契數(shù)列是一個遞增的數(shù)列誊抛,形如1 1 2 3 5 8 13 21 34 55...列牺,即從第三個數(shù)開始后一個數(shù)字是前兩個數(shù)字之和。
使用函數(shù)實(shí)現(xiàn)斐波拉契數(shù)列生成器
使用閉包可實(shí)現(xiàn)擁有自身狀態(tài)的函數(shù)
變量作用域
- 全局變量:在
main()
函數(shù)執(zhí)行之前初始化拗窃,因此全局可見瞎领。 - 局部變量:在函數(shù)內(nèi)或
if
、for
等語句塊中有效随夸,使用后外部不可見九默。 - 全局變量和局部變量同名時局部變量優(yōu)先生效
變量可見性
- 包內(nèi)任何變量或函數(shù)都能訪問
- 包外首字母大寫的可被訪問,首字母小寫的表示私有因此不能被外部調(diào)用宾毒。
變參函數(shù)
變參函數(shù)是指使用不同數(shù)量的參數(shù)調(diào)用的函數(shù)驼修,變參函數(shù)允許用戶在可變函數(shù)中傳遞0或多個參數(shù)。比如fmt.Print()
可接受任意數(shù)量的參數(shù)诈铛。
變參函數(shù)聲明中乙各,最后一個參數(shù)的類型前帶有省略號...
以表明該函數(shù)可以調(diào)用任意數(shù)量此類型的參數(shù)。
func fname(args, ...type) type {}
...type
格式的類型只能作為函數(shù)的參數(shù)類型存在且必須是最后一個參數(shù)癌瘾,它是一個語法糖(syntax sugar)觅丰,即該語法對語言的功能并沒有影響饵溅,只是為了更方便開發(fā)人員使用妨退,使用語法糖能夠增加代碼的可讀性而從減少程序出錯的可能性。
從內(nèi)部實(shí)現(xiàn)來將蜕企,類型...type
本質(zhì)上是一個數(shù)組切片咬荷,也就是[]type
。
func fn(args ...int){
for k,v := range args {
fmt.Println(k, v)
}
}
fn(1, 2, 3)
func fn(args []int){
for k,v := range args {
fmt.Println(k, v)
}
}
fn([]int{1, 2, 3})
由于...type
可變參數(shù)本質(zhì)上是一個數(shù)組切片轻掩,因此為變參函數(shù)傳遞切片幸乒。
func fn(args ...int){
for k,v := range args {
fmt.Println(k, v)
}
}
fn([]int{1, 2, 3}...)
slice := []int{1,2,3}
fn(slice...)
由于可變參數(shù)變量本身是一個包含函數(shù)參數(shù)的切片,若需要將含有可變參數(shù)的變量傳遞給下一個可變參數(shù)函數(shù)唇牧,可在傳遞時在可變參數(shù)后添加...
罕扎,這樣即可將切片中的元素進(jìn)行傳遞,而無需傳遞可變參數(shù)變量本身丐重。
package main
import (
"bytes"
"fmt"
)
func concat(args ...string) string {
var buf bytes.Buffer//定義字節(jié)緩沖用于快速連接字符串
for _,v := range args {//遍歷可變參數(shù)列表腔召,類型為[]string
buf.WriteString(v)//將遍歷出的字符串連續(xù)寫入字節(jié)數(shù)組
}
return buf.String()//將連接好的字節(jié)數(shù)組轉(zhuǎn)化為字符串
}
func print(args ...string){
str := concat(args...)
fmt.Println(str)
}
func main() {
print("hell", "o")
}
可變參數(shù)使用...
進(jìn)行傳遞與切片間使用append
連接是同一種特性
可變參數(shù)不傳入任何值的時候,函數(shù)內(nèi)部會默認(rèn)為nil
扮惦。
func fn(args ...int){
fmt.Printf("args = %v, type = %T\n", args, args)
}
fn()//args = [], type = []int
使用interface{}
空接口可指定任意類型的可變參數(shù)
func fn(args ...interface{}){
fmt.Printf("args = %v, type = %T\n", args, args)
}
fn()//args = [], type = []interface {}
可變參數(shù)列表數(shù)量是不固定的臀蛛,傳入的參數(shù)是一個切片,如果需要獲得每個參數(shù)的具體值,可對可變參數(shù)變量進(jìn)行遍歷浊仆。
//快速拼接字符串
func concat(args ...string) string {
var buf bytes.Buffer//定義字節(jié)緩沖用于快速連接字符串
for _,v := range args {//遍歷可變參數(shù)列表客峭,類型為[]string
buf.WriteString(v)//將遍歷出的字符串連續(xù)寫入字節(jié)數(shù)組
}
return buf.String()//將連接好的字節(jié)數(shù)組轉(zhuǎn)化為字符串
}
fmt.Printf("%s", concat("ham", "mer"))//hammer
延遲執(zhí)行
Go語言中defer
語句會將其后面跟隨的語句進(jìn)行延遲處理
在defer
歸屬的函數(shù)即將返回時,將延遲處理的語句按defer
的逆序進(jìn)行執(zhí)行抡柿。先被defer
的語句最后被執(zhí)行舔琅,最后被defer
的語句最先執(zhí)行。
當(dāng)多個defer
行為被注冊時它們會議逆序執(zhí)行(類似棧洲劣,先進(jìn)后出搏明,LIFO)
func main() {
fmt.Println("defer begin")
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
fmt.Println("defer end")
}
defer begin
defer end
3
2
1
defer
關(guān)鍵字類似Java或C#中的finally
語句塊,用于釋放已被分配的資源闪檬,典型的例子是互斥解鎖或關(guān)閉文件星著。
處理業(yè)務(wù)或邏輯中涉及到成對操作時,比如文件的打開和關(guān)閉粗悯,請求的接受與回復(fù)虚循,加鎖與解鎖等,這些操作中样傍,最容易忽視的在每個函數(shù)退出時需釋放或關(guān)閉資源横缔。使用defer
語句正好是在函數(shù)退出時執(zhí)行的語句,可方便地解決資源釋放等容易被忽視的問題衫哥。
使用defer
延遲并發(fā)解鎖
當(dāng)在函數(shù)中并發(fā)讀寫map
時為防止競態(tài)問題茎刚,使用sync.Mutex
進(jìn)行加鎖。
package main
import "sync"
var (
dict = make(map[string]string)//map默認(rèn)并非并發(fā)安全
dictGuard sync.Mutex //為保證使用映射時的并發(fā)安全撤逢,使用互斥鎖膛锭。
)
//根據(jù)鍵讀取值
func read(key string) string{
dictGuard.Lock()//對共享資源加鎖,使用互斥量加鎖蚊荣。
defer dictGuard.Unlock()//對共享資源解鎖初狰,使用互斥量解鎖。延遲到函數(shù)結(jié)束時調(diào)用
return dict[key]//獲取共享資源
}
使用defer
延遲釋放文件句柄
//獲取文件大小
func filesize(filename string) int64 {
//打開文件返回文件句柄
fh, err := os.Open(filename)
if err != nil {
return 0
}
//延遲關(guān)閉文件
defer fh.Close()
//獲取文件狀態(tài)信息
fileinfo, err := fh.Stat()
if err != nil {
return 0
}
//獲取文件大小
filesize := fileinfo.Size()
return filesize
}
遞歸函數(shù)
遞歸函數(shù)是指函數(shù)內(nèi)部調(diào)用函數(shù)自身的函數(shù)互例,構(gòu)成遞歸需要具備以下條件
- 一個文件可以被拆分為多個子問題
- 拆分前的原問題與拆分后的子問題除了數(shù)據(jù)規(guī)模不同奢入,處理問題的思路是一樣的。
- 不能無限的調(diào)用本身媳叨,子問題需要擁有退出遞歸狀態(tài)的條件腥光。
遞歸函數(shù)編寫過程中,一定要有終止條件糊秆,否則函數(shù)會無限執(zhí)行下去直到內(nèi)存溢出武福。
例如:使用遞歸實(shí)現(xiàn)斐波那契數(shù)列
package main
func fib(n int) (result int) {
if n < 2 {
result = 1
}else{
result = fib(n - 1) + fib(n - 2)
}
return
}
func main() {
for i:=0; i<=10; i++{
println(fib(i))
}
}