Go Function

  • 函數(shù)是組織良好且可重復(fù)使用的,用來實現(xiàn)單一或相關(guān)功能的代碼塊,用于提高模塊化和復(fù)用性。
  • 編寫函數(shù)的目的是為了將需要多行代碼實現(xiàn)的復(fù)雜問題分解為一系列簡單的任務(wù)來解決。

Go語言是編譯型的,因此函數(shù)的順序和位置是無關(guān)緊要的,鑒于可讀性推薦將main主函數(shù)編寫在文件前面,其它函數(shù)按照一定邏輯順序向下編寫。

Go語言支持普通函數(shù)、匿名函數(shù)、閉包,從設(shè)計上對函數(shù)進行了優(yōu)化和改進。

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)識符用來強調(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)用,實際調(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)用時必須按照聲明時的順序為所有參數(shù)提供實參(參數(shù)值)

Go語言中形參沒有默認(rèn)的參數(shù)值,也沒有任何方法通過參數(shù)名指定形參,因此形參和返回值的變量名對于函數(shù)調(diào)用者而言是沒有意義的。

函數(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)部修改外部傳入的變量值,實際返回計算結(jié)果。
  • C++ 語言為了安全性,建議在參數(shù)返回數(shù)據(jù)時使用引用替代指針。
  • C# 語言中沒有多返回值,C#語言后期加入的refout關(guān)鍵字能夠通過函數(shù)的調(diào)用參數(shù)以獲得函數(shù)體中修改的數(shù)據(jù)。
  • Lua語言中雖然沒有指針但支持多返回值,特別適用于大塊數(shù)據(jù)。

Go語言既支持安全指針,也支持多返回值,因此在使用函數(shù)進行邏輯編寫時更加方便。

  • 同一類型返回值

若返回值是同一類型,則可使用括號()將多個返回值類型括起來,用逗號,分隔每個返回值的類型。在函數(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語言支持對返回值進行命名,這樣返回值和參數(shù)一樣擁有參數(shù)變量名稱和類型。

待命名的返回值變量的默認(rèn)值是其類型的默認(rèn)值

例如:根據(jù)矩形的長和寬計算周長和面積

package main

import "fmt"

//對函數(shù)返回值進行命名
func rect(length, width float64) (area, perimeter float64){
    //已命名返回值的變量與函數(shù)局部變量一樣,可以對返回值變量進行賦值和值獲取。
    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ù)運行結(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ù)往往會以變量的方式進行傳遞,與C語言的回調(diào)函數(shù)類似,不同的是Go語言支持隨時在代碼中定義匿名函數(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ù)本身就是一種值,可以方便地保存在各種容器中實現(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)
    })
}

例如:對字符串左右兩邊的空格進行去除

使用匿名函數(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")
    }
}

運行測試

$ go run test.go --type fire
fire

使用函數(shù)實現(xiàn)接口

函數(shù)在數(shù)據(jù)類型中屬于一等公民,其他類型能夠?qū)崿F(xiàn)接口,函數(shù)也可以。

例如:使用結(jié)構(gòu)體實現(xiàn)接口

package main

import "fmt"

//定義調(diào)用器接口
type Invoker interface {
    Call(interface{})//待實現(xiàn)調(diào)用方法
}

//定義結(jié)構(gòu)體類型
type Struct struct {

}

//實現(xiàn)Invoker調(diào)用程序的Call調(diào)用方法,可傳入任意類型interface{}的值。
func (this *Struct) Call(param interface{}){
    fmt.Println(param)
}

func main() {
    //聲明接口變量
    var invoker Invoker
    //實例化結(jié)構(gòu)體類型
    s := new(Struct)
    //將結(jié)構(gòu)體實例賦值給接口
    invoker = s
    //使用接口變量調(diào)用結(jié)構(gòu)體實例方法
    invoker.Call("hello")
}

例如:使用函數(shù)實現(xiàn)接口

函數(shù)聲明不能直接實現(xiàn)接口,需要將函數(shù)定義為類型后,使用類型實現(xiàn)結(jié)構(gòu)體。當(dāng)類型方法被調(diào)用時,還需調(diào)用函數(shù)本體。

package main

import "fmt"

//定義調(diào)用者接口
type Invoker interface {
    Call(interface{})//待實現(xiàn)接口
}

//定義函數(shù)為類型
type Caller func(interface{})

//實現(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ù)式編程語言中用于實現(xiàn)詞法范圍的名稱綁定技術(shù)。 閉包并非某種語言特有的機制,只是經(jīng)常會出現(xiàn)在函數(shù)式編程語言中,因為函數(shù)式編程語言中函數(shù)是一等公民(first-class)。

從操作實現(xiàn)上來將,閉包是將函數(shù)及其運行環(huán)境(引用環(huán)境)打包存儲的一條記錄。函數(shù)的運行環(huán)境又稱為執(zhí)行上下文(execution context),包括函數(shù)運行時所處的內(nèi)部環(huán)境和所依賴的外部環(huán)境。

Go語言中匿名函數(shù)可作為閉包,閉包和普通函數(shù)的區(qū)別在于,普通函數(shù)被調(diào)用者執(zhí)行完畢后會丟棄環(huán)境,而閉包則依然會保留運行環(huán)境。

函數(shù)的運行環(huán)境只是一種映射,它會將函數(shù)的每個自由變量與創(chuàng)建閉包時名稱綁定的值或引用相互關(guān)聯(lián)。函數(shù)的自由變量特指在本地使用,但卻在封閉的范圍內(nèi)定義的變量。

自由變量是相當(dāng)于閉包或匿名函數(shù)而言的外部變量,由于該變量的定義不受自身控制,因此對閉包自身來說是自由的,因為不會受到閉包的約束。

與普通函數(shù)不同的是閉包允許函數(shù)通過閉包的值副本或引用來訪問那些被捕獲的變量,即使函數(shù)在其作用域之外被調(diào)用。也就是說閉包提供了一種可持續(xù)訪問被捕獲變量的能力,進而擴大了變量的作用域。

閉包提供了持續(xù)暴露變量的機制,使外界能夠訪問原本應(yīng)該私有的變量,實現(xiàn)了全局變量的作用域效果。由此可見,一旦變量被閉包捕獲后,外界使用者可以訪問被捕獲的變量值或引用,相當(dāng)于訪問了私有變量。

綜上所述,閉包是函數(shù)式編程中實現(xiàn)名稱綁定的技術(shù),直觀表現(xiàn)為函數(shù)嵌套以提升變量作用范圍,使原本壽命短暫的局部變量獲得了長生不老的能力。只要被捕獲到的自由變量一直處于使用中,系統(tǒng)就不會回收其內(nèi)存空間。


閉包(Closure)又稱為Lambda表達式

閉包是由函數(shù)及其相關(guān)的引用環(huán)境共同組合而成的實體,即 “閉包 = 函數(shù) + 運行(引用)環(huán)境”。因此有人又稱:”對象是附有行為的數(shù)據(jù),閉包是附有數(shù)據(jù)的行為“。

理解閉包首先需要了解函數(shù)的執(zhí)行環(huán)境(execution context)又稱為執(zhí)行上下文。

  • 函數(shù)是指執(zhí)行的代碼塊,由于自由變量被包含在代碼塊中,因此這些自由變量以及它們引用的對象沒有被釋放。
  • 運行環(huán)境是是指為自由變量提供綁定的計算環(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í)行的代碼塊會為自由變量提供綁定的計算環(huán)境(作用域),由于自由變量包含在代碼塊中,因此自由變量及其引用的對象不會被釋放掉。

閉包

理解閉包最直觀的方法就是將閉包函數(shù)當(dāng)作一個類,一個閉包函數(shù)調(diào)用相當(dāng)于實例化類,然后再從類的角度中區(qū)分那些是全局變量,那些是局部變量。實際上在Objective-C中閉包就是使用類來實現(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ù)式編程的特點。

閉包的缺點在于函數(shù)中的變量會被保存在內(nèi)存中,因此內(nèi)存消耗很大,所以閉包不能濫用。


閉包是引用了自由變量的函數(shù),被引用的自由變量和函數(shù)會一同存在,即使已經(jīng)離開了自由變量的環(huán)境也不會被釋放或刪除,在閉包中可以繼續(xù)使用這些自由變量。

同一個函數(shù)與不同引用環(huán)境組合后可形成不同的實例

函數(shù)類型和結(jié)構(gòu)體一樣可以被實例化,由于函數(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)語句或表達式,匿名函數(shù)的優(yōu)越性在于可以直接使用函數(shù)內(nèi)部的變量而無需事先聲明。

匿名函數(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ù)是編譯期靜態(tài)的概念,閉包是運行期動態(tài)的概念。函數(shù)只是一段可執(zhí)行的代碼,這些代碼在函數(shù)被定義后就確定了,也就是說編譯后就固化了,因此不會在執(zhí)行時發(fā)生變化。每個函數(shù)在內(nèi)存中只會存在一份實例,所以說一個函數(shù)只是一個實例,只要得到函數(shù)的入口點即可執(zhí)行函數(shù)。而閉包在運行時可以擁有多個實例,不同的引用環(huán)境和相同的函數(shù)組合可以產(chǎn)生不同的實例。

閉包與函數(shù)引用

所謂的引用環(huán)境是指在程序執(zhí)行過程中的某個點上所有處于活躍狀態(tài)的約束所組成的集合,這里的約束是指一個變量的名字和其代表的對象之間的聯(lián)系。

函數(shù)式編程是一種編程模型,將計算機運算看作數(shù)學(xué)中函數(shù)的計算,以避免狀態(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é)和計算機科學(xué)中,高階函數(shù)至少需要滿足下列兩個條件中的之一:

  • 接受一個或多個函數(shù)作為輸入
  • 輸出一個函數(shù)

為什么閉包需要將引用環(huán)境和函數(shù)組合起來呢?因為在支持嵌套作用域的語言中,有時不能簡單直接地確定函數(shù)的引用環(huán)境。


閉包可以理解為定義在函數(shù)內(nèi)部的函數(shù),本質(zhì)上閉包是將函數(shù)內(nèi)部和外部連接的橋梁,是函數(shù)與其引用環(huán)境的組合。


下面以計算斐波拉契數(shù)列為例,來分析下閉包。

非巴拉契數(shù)列是一個遞增的數(shù)列,形如1 1 2 3 5 8 13 21 34 55...,即從第三個數(shù)開始后一個數(shù)字是前兩個數(shù)字之和。

非巴拉契數(shù)列

使用函數(shù)實現(xiàn)斐波拉契數(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)部實現(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ù)后添加...,這樣即可將切片中的元素進行傳遞,而無需傳遞可變參數(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ù)使用...進行傳遞與切片間使用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ù)變量進行遍歷。

//快速拼接字符串
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語句會將其后面跟隨的語句進行延遲處理

defer歸屬的函數(shù)即將返回時,將延遲處理的語句按defer的逆序進行執(zhí)行。先被defer的語句最后被執(zhí)行,最后被defer的語句最先執(zhí)行。

當(dāng)多個defer行為被注冊時它們會議逆序執(zhí)行(類似棧,先進后出,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進行加鎖。

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)存溢出。

例如:使用遞歸實現(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))
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Go 和其他語言很大的一個不同就是函數(shù)可以返回多個返回值。 Go 中聲明的變量必須被使用,否則就會報錯,因為Go認(rèn)...
    幣來幣往閱讀 492評論 0 0
  • 原創(chuàng)文章轉(zhuǎn)載請注明出處 今天看Martini文檔,其功能列表提到完全兼容http.HandlerFunc接口,就去...
    咕咕鷄閱讀 23,678評論 3 32
  • 作者 | 冉小龍 審校 | Anonymitaet 編輯 | Susan 閱讀本文需要約 8 分鐘。 - 導(dǎo)讀 -...
    StreamNative閱讀 444評論 0 0
  • 在學(xué)習(xí)如何編寫、部署 Go Function 之前,先向大家介紹一下 Go Function 的實現(xiàn)思路。 在 一...
    wolf4j閱讀 705評論 0 0
  • 函數(shù)是組織好的、可重復(fù)使用的、用于執(zhí)行指定任務(wù)的代碼塊。Go語言中支持函數(shù)、匿名函數(shù)和閉包,并且函數(shù)在Go語言中屬...
    Every_dawn閱讀 1,059評論 0 1

友情鏈接更多精彩內(nèi)容