真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

go函數(shù)使用,閉包

函數(shù)類型也是一等的數(shù)據(jù)類型。這是什么意思呢?

這意味著函數(shù)不但可以用于封裝代碼、分割功能、解耦邏輯,還可以化身為普通的值,在其他函數(shù)間傳遞、賦予變量、做類型判斷和轉(zhuǎn)換等等,就像切片和字典的值那樣。
而更深層次的含義就是:函數(shù)值可以由此成為能夠被隨意傳播的獨立邏輯組件(或者說功能模塊)。
對于函數(shù)類型來說,它是一種對一組輸入、輸出進行模板化的重要工具,它比接口類型更加輕巧、靈活,它的值也借此變成了可被熱替換的邏輯組件。

成都創(chuàng)新互聯(lián)公司長期為1000+客戶提供的網(wǎng)站建設(shè)服務(wù),團隊從業(yè)經(jīng)驗10年,關(guān)注不同地域、不同群體,并針對不同對象提供差異化的產(chǎn)品和服務(wù);打造開放共贏平臺,與合作伙伴共同營造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為阿榮企業(yè)提供專業(yè)的網(wǎng)站設(shè)計、成都做網(wǎng)站,阿榮網(wǎng)站改版等技術(shù)服務(wù)。擁有十余年豐富建站經(jīng)驗和眾多成功案例,為您定制開發(fā)。

我先聲明了一個函數(shù)類型,名叫Printer,注意這里的寫法,在類型聲明的名稱右邊的是func關(guān)鍵字,我們由此就可知道這是一個函數(shù)類型的聲明。

nc右邊的就是這個函數(shù)類型的參數(shù)列表和結(jié)果列表。其中,參數(shù)列表必須由圓括號包裹,而只要結(jié)果列表中只有一個結(jié)果聲明,并且沒有為它命名,我們就可以省略掉外圍的圓括號。

書寫函數(shù)簽名的方式與函數(shù)聲明的是一致的。只是緊挨在參數(shù)列表左邊的不是函數(shù)名稱,而是關(guān)鍵字func。這里函數(shù)名稱和func互換了一下位置而已

函數(shù)的簽名其實就是函數(shù)的參數(shù)列表和結(jié)果列表的統(tǒng)稱,它定義了可用來鑒別不同函數(shù)的那些特征,同時也定義了我們與函數(shù)交互的方式。

注意,各個參數(shù)和結(jié)果的名稱不能算作函數(shù)簽名的一部分,甚至對于結(jié)果聲明來說,沒有名稱都可以。只要兩個函數(shù)的參數(shù)列表和結(jié)果列表中的元素順序及其類型是一致的,我們就可以說它們是一樣的函數(shù),或者說是實現(xiàn)了同一個函數(shù)類型的函數(shù)。嚴格來說,函數(shù)的名稱也不能算作函數(shù)簽名的一部分,它只是我們在調(diào)用函數(shù)時,需要給定的標(biāo)識符而已。

聲明的函數(shù)printToStd的簽名與Printer的是一致的,因此前者是后者的一個實現(xiàn),即使它們的名稱以及有的結(jié)果名稱是不同的。

通過main函數(shù)中的代碼,我們就可以證實這兩者的關(guān)系了,我順利地把printToStd函數(shù)賦給了Printer類型的變量p,并且成功地調(diào)用了它。

總之,“函數(shù)是一等的公民”是函數(shù)式編程(functional programming)的重要特征。Go 語言在語言層面支持了函數(shù)式編程

package main

import "fmt"

//先聲明了一個函數(shù)類型,名叫Printer,函數(shù)簽名:函數(shù)的參數(shù)列表和結(jié)果列表的統(tǒng)稱
type Printer func(contents string) (n int, err error)

//定義了一個函數(shù),printToStd的簽名與Printer的是一致的,因此printToStd是Printer的一個實現(xiàn),即使它們的名稱以及有的結(jié)果名稱是不同的
func printToStd(contents string) (bytesNum int, err error) {
    return fmt.Println(contents)
}

func main() {
    var p Printer //初始化一個Printer 類型的p
    p = printToStd //順利地把printToStd函數(shù)賦給了Printer類型的變量p,并且成功地調(diào)用了它
    p("something")
}
go run demo26.go 
something

怎樣編寫高階函數(shù)?

什么是高階函數(shù)?只要滿足了其中任意一個特點,我們就可以說這個函數(shù)是一個高階函數(shù)

  1. 接受其他的函數(shù)作為參數(shù)傳入;
  2. 把其他的函數(shù)作為結(jié)果返回。

我想通過編寫calculate函數(shù)來實現(xiàn)兩個整數(shù)間的加減乘除運算,但是希望兩個整數(shù)和具體的操作都由該函數(shù)的調(diào)用方給出,那么,這樣一個函數(shù)應(yīng)該怎樣編寫呢。

我們編寫calculate函數(shù)的簽名部分。這個函數(shù)除了需要兩個int類型的參數(shù)之外,還應(yīng)該有一個operate類型的參數(shù)。該函數(shù)的結(jié)果應(yīng)該有兩個,一個是int類型的,代表真正的操作結(jié)果,另一個應(yīng)該是error類型的,因為如果那個operate類型的參數(shù)值為nil,那么就應(yīng)該直接返回一個錯誤

函數(shù)類型屬于引用類型,它的值可以為nil,而這種類型的零值恰恰就是nil。

calculate函數(shù)實現(xiàn)起來就很簡單了。我們需要先用衛(wèi)述語句檢查一下參數(shù),如果operate類型的參數(shù)op為nil,那么就直接返回0和一個代表了具體錯誤的error類型值。

衛(wèi)述語句是指被用來檢查關(guān)鍵的先決條件的合法性,并在檢查未通過的情況下立即終止當(dāng)前代碼塊執(zhí)行的語句。在 Go 語言中,if 語句常被作為衛(wèi)述語句。如果檢查無誤,那么就調(diào)用op并把那兩個操作數(shù)傳給它,最后返回op返回的結(jié)果和代表沒有錯誤發(fā)生的nil。

calculate函數(shù)的其中一個參數(shù)是operate類型的,而且后者就是一個函數(shù)類型。在調(diào)用calculate函數(shù)的時候,我們需要傳入一個operate類型的函數(shù)值。這個函數(shù)值應(yīng)該怎么寫?

只要它的簽名與operate類型的簽名一致,并且實現(xiàn)得當(dāng)就可以了。我們可以像上一個例子那樣先聲明好一個函數(shù),再把它賦給一個變量,也可以直接編寫一個實現(xiàn)了operate類型的匿名函數(shù)。

calculate函數(shù)就是一個高階函數(shù)。但是我們說高階函數(shù)的特點有兩個,而該函數(shù)只展示了其中一個特點,即:接受其他的函數(shù)作為參數(shù)傳入。

那另一個特點,把其他的函數(shù)作為結(jié)果返回。這又是怎么玩的呢?你可以看看我在 demo27.go 文件中聲明的函數(shù)類型calculateFunc和函數(shù)genCalculator。其中,genCalculator函數(shù)的唯一結(jié)果的類型就是calculateFunc

package main

import (
    "errors"
    "fmt"
)

type operate func(x, y int) int //我們來聲明一個名叫operate的函數(shù)類型,它有兩個參數(shù)和一個結(jié)果,都是int類型的。

// 方案1。calculate函數(shù)就是一個高階函數(shù)。該函數(shù)只展示了其中一個特點,即:接受其他的函數(shù)作為參數(shù)傳入。
func calculate(x int, y int, op operate) (int, error) {
    if op == nil { //衛(wèi)述語句檢查op的合法性
        return 0, errors.New("invalid operation")
    }
    return op(x, y), nil
}

// 方案2。calculateFunc也是高階函數(shù),把其他的函數(shù)op作為結(jié)果返回
type calculateFunc func(x int, y int) (int, error)

func genCalculator(op operate) calculateFunc {
    return func(x int, y int) (int, error) {
        if op == nil {
            return 0, errors.New("invalid operation")
        }
        return op(x, y), nil
    }
}

func main() {
    // 方案1。
    x, y := 12, 23
    op := func(x, y int) int {
        return x + y
    }
    result, err := calculate(x, y, op) //把函數(shù)op作為一個普通的值賦給一個變量。
    fmt.Printf("The result: %d (error: %v)\n",
        result, err)
    result, err = calculate(x, y, nil)
    fmt.Printf("The result: %d (error: %v)\n",
        result, err)

    // 方案2。
    x, y = 56, 78
    add := genCalculator(op)
    result, err = add(x, y)
    fmt.Printf("The result: %d (error: %v)\n",
        result, err)
}
go run demo27.go 
The result: 35 (error: )
The result: 0 (error: invalid operation)
The result: 134 (error: )

如何實現(xiàn)閉包?

閉包又是什么?你可以想象一下,在一個函數(shù)中存在對外來標(biāo)識符的引用。所謂的外來標(biāo)識符,既不代表當(dāng)前函數(shù)的任何參數(shù)或結(jié)果,也不是函數(shù)內(nèi)部聲明的,它是直接從外邊拿過來的。

還有個專門的術(shù)語稱呼它,叫自由變量,可見它代表的肯定是個變量。實際上,如果它是個常量,那也就形成不了閉包了,因為常量是不可變的程序?qū)嶓w,而閉包體現(xiàn)的卻是由“不確定”變?yōu)椤按_定”的一個過程。

我們說的這個函數(shù)(以下簡稱閉包函數(shù))就是因為引用了自由變量,而呈現(xiàn)出了一種“不確定”的狀態(tài),也叫“開放”狀態(tài)。

也就是說,它的內(nèi)部邏輯并不是完整的,有一部分邏輯需要這個自由變量參與完成,而后者到底代表了什么在閉包函數(shù)被定義的時候卻是未知的。

即使對于像 Go 語言這種靜態(tài)類型的編程語言而言,我們在定義閉包函數(shù)的時候最多也只能知道自由變量的類型

在我們剛剛提到的genCalculator函數(shù)內(nèi)部,實際上就實現(xiàn)了一個閉包,而genCalculator函數(shù)也是一個高階函數(shù)。

genCalculator函數(shù)只做了一件事,那就是定義一個匿名的、calculateFunc類型的函數(shù)并把它作為結(jié)果值返回。

而這個匿名的函數(shù)就是一個閉包函數(shù)。它里面使用的變量op既不代表它的任何參數(shù)或結(jié)果也不是它自己聲明的,而是定義它的genCalculator函數(shù)的參數(shù),所以是一個自由變量。

這個自由變量究竟代表了什么,這一點并不是在定義這個閉包函數(shù)的時候確定的,而是在genCalculator函數(shù)被調(diào)用的時候確定的。只有給定了該函數(shù)的參數(shù)op,我們才能知道它返回給我們的閉包函數(shù)可以用于什么運算。

看到if op == nil {那一行了嗎?Go 語言編譯器讀到這里時會試圖去尋找op所代表的東西,它會發(fā)現(xiàn)op代表的是genCalculator函數(shù)的參數(shù),然后,它會把這兩者聯(lián)系起來。這時可以說,自由變量op被“捕獲”了。

當(dāng)程序運行到這里的時候,op就是那個參數(shù)值了。如此一來,這個閉包函數(shù)的狀態(tài)就由“不確定”變?yōu)榱恕按_定”,或者說轉(zhuǎn)到了“閉合”狀態(tài),至此也就真正地形成了一個閉包。

看出來了嗎?我們在用高階函數(shù)實現(xiàn)閉包。這也是高階函數(shù)的一大功用。

go 函數(shù)使用,閉包
(高階函數(shù)與閉包)

那么,實現(xiàn)閉包的意義又在哪里呢?表面上看,我們只是延遲實現(xiàn)了一部分程序邏輯或功能而已,但實際上,我們是在動態(tài)地生成那部分程序邏輯。

我們可以借此在程序運行的過程中,根據(jù)需要生成功能不同的函數(shù),繼而影響后續(xù)的程序行為。這與 GoF 設(shè)計模式中的“模板方法”模式有著異曲同工之妙,不是嗎?

傳入函數(shù)的那些參數(shù)值后來怎么樣了?

這個命令源碼文件(也就是 demo28.go示例一)在運行之后會輸出什么?
答案是:原數(shù)組不會改變。為什么呢?原因是,所有傳給函數(shù)的參數(shù)值都會被復(fù)制,函數(shù)在其內(nèi)部使用的并不是參數(shù)值的原值,而是它的副本。由于數(shù)組是值類型,所以每一次復(fù)制都會拷貝它,以及它的所有元素值。我在modify函數(shù)中修改的只是原數(shù)組的副本而已,并不會對原數(shù)組造成任何影響。

對于引用類型,比如:切片、字典、通道,像上面那樣復(fù)制它們的值,只會拷貝它們本身而已,并不會拷貝它們引用的底層數(shù)據(jù)。也就是說,這時只是淺表復(fù)制,而不是深層復(fù)制。

以切片值為例,如此復(fù)制的時候,只是拷貝了它指向底層數(shù)組中某一個元素的指針,以及它的長度值和容量值,而它的底層數(shù)組并不會被拷貝。

另外還要注意,就算我們傳入函數(shù)的是一個值類型的參數(shù)值,但如果這個參數(shù)值中的某個元素是引用類型的,那么我們?nèi)匀灰⌒摹?/p>

變量complexArray1是[3][]string類型的,也就是說,雖然它是一個數(shù)組,但是其中的每個元素又都是一個切片。這樣一個值被傳入函數(shù)的話,函數(shù)中對該參數(shù)值的修改會影響到complexArray1本身嗎?我想,這可以留作今天的思考題。

package main

import "fmt"

func main() {
    // 示例1。底層數(shù)組不會被修改,所有傳給函數(shù)的參數(shù)值都會被復(fù)制,函數(shù)在其內(nèi)部使用的并不是參數(shù)值的原值,而是它的副本
    array1 := [3]string{"a", "b", "c"}
    fmt.Printf("The array: %v\n", array1)
    array2 := modifyArray(array1)
    fmt.Printf("The modified array: %v\n", array2)
    fmt.Printf("The original array: %v\n", array1)
    fmt.Println()

    // 示例2。切片會被修改掉,但是切片底層的數(shù)組不變
    slice1 := []string{"x", "y", "z"}
    fmt.Printf("The slice: %v\n", slice1)
    slice2 := modifySlice(slice1)
    fmt.Printf("The modified slice: %v\n", slice2)
    fmt.Printf("The original slice: %v\n", slice1)
    fmt.Println()

    // 示例3。 /切片被修改,底層數(shù)組不變
    complexArray1 := [3][]string{
        []string{"d", "e", "f"},
        []string{"g", "h", "i"},
        []string{"j", "k", "l"},
    }
    fmt.Printf("The complex array: %v\n", complexArray1)
    complexArray2 := modifyComplexArray(complexArray1) //切片被修改,底層數(shù)組不變
    fmt.Printf("The modified complex array: %v\n", complexArray2)
    fmt.Printf("The original complex array: %v\n", complexArray1)
}

// 示例1。
func modifyArray(a [3]string) [3]string {
    a[1] = "x"
    return a
}

// 示例2。
func modifySlice(a []string) []string {
    a[1] = "i"
    return a
}

// 示例3。
func modifyComplexArray(a [3][]string) [3][]string {
    a[1][1] = "s"
    a[2] = []string{"o", "p", "q"}
    return a
}
 go run demo28.go 
The array: [a b c]
The modified array: [a x c]
The original array: [a b c]

The slice: [x y z]
The modified slice: [x i z]
The original slice: [x i z]

The complex array: [[d e f] [g h i] [j k l]]
The modified complex array: [[d e f] [g s i] [o p q]]   //切片被修改,底層數(shù)組不變
The original complex array: [[d e f] [g s i] [j k l]]

問題:
1、complexArray1被傳入函數(shù)的話,這個函數(shù)中對該參數(shù)值的修改會影響到它的原值嗎?
如果修改了引用類型的值會受影響,1.數(shù)組的操作不影響原值 2.切片的操作會影響原值。
如果是進行一層修改,即數(shù)組的某個完整元素進行修改(指針變化),那么原有數(shù)組不變;如果進行二層修改,即數(shù)組中某個元素切片內(nèi)的某個元素再進行修改(指針未改變),那么原有數(shù)據(jù)也會跟著改變,傳參可以理解是淺copy,參數(shù)本身的指針是不同,但是元素指針相同,對元素指針?biāo)赶蚰康牡牟僮鲿绊憘鲄⑦^程中的原始數(shù)據(jù);

2、函數(shù)真正拿到的參數(shù)值其實只是它們的副本,那么函數(shù)返回給調(diào)用方的結(jié)果值也會被復(fù)制嗎?比如你傳出去一個數(shù)組,它還會是函數(shù)中的那個數(shù)組嗎?
一般來說應(yīng)該是復(fù)制的,傳參和返回應(yīng)該是一個對稱的過程,本身對這一片內(nèi)存數(shù)據(jù)的操作只發(fā)生在函數(shù)內(nèi)部,脫離函數(shù)就應(yīng)該脫離這塊內(nèi)存區(qū)域


分享題目:go函數(shù)使用,閉包
標(biāo)題來源:http://weahome.cn/article/jopeoj.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部