中文字幕日韩精品一区二区免费_精品一区二区三区国产精品无卡在_国精品无码专区一区二区三区_国产αv三级中文在线

go函數(shù)使用,閉包-創(chuàng)新互聯(lián)

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

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

創(chuàng)新互聯(lián)服務(wù)項(xiàng)目包括朝陽網(wǎng)站建設(shè)、朝陽網(wǎng)站制作、朝陽網(wǎng)頁制作以及朝陽網(wǎng)絡(luò)營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,朝陽網(wǎng)站推廣取得了明顯的社會(huì)效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到朝陽省份的部分城市,未來相信會(huì)繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!

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

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

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

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

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

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

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

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

package main

import "fmt"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

package main

import (
    "errors"
    "fmt"
)

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

// 方案1。calculate函數(shù)就是一個(gè)高階函數(shù)。該函數(shù)只展示了其中一個(gè)特點(diǎn),即:接受其他的函數(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作為一個(gè)普通的值賦給一個(gè)變量。
    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: <nil>)
The result: 0 (error: invalid operation)
The result: 134 (error: <nil>)

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

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

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

我們說的這個(gè)函數(shù)(以下簡稱閉包函數(shù))就是因?yàn)橐昧俗杂勺兞?,而呈現(xiàn)出了一種“不確定”的狀態(tài),也叫“開放”狀態(tài)。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

package main

import "fmt"

func main() {
    // 示例1。底層數(shù)組不會(huì)被修改,所有傳給函數(shù)的參數(shù)值都會(huì)被復(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。切片會(huì)被修改掉,但是切片底層的數(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ù)的話,這個(gè)函數(shù)中對該參數(shù)值的修改會(huì)影響到它的原值嗎?
如果修改了引用類型的值會(huì)受影響,1.數(shù)組的操作不影響原值 2.切片的操作會(huì)影響原值。
如果是進(jìn)行一層修改,即數(shù)組的某個(gè)完整元素進(jìn)行修改(指針變化),那么原有數(shù)組不變;如果進(jìn)行二層修改,即數(shù)組中某個(gè)元素切片內(nèi)的某個(gè)元素再進(jìn)行修改(指針未改變),那么原有數(shù)據(jù)也會(huì)跟著改變,傳參可以理解是淺copy,參數(shù)本身的指針是不同,但是元素指針相同,對元素指針?biāo)赶蚰康牡牟僮鲿?huì)影響傳參過程中的原始數(shù)據(jù);

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

另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)cdcxhl.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。

分享標(biāo)題:go函數(shù)使用,閉包-創(chuàng)新互聯(lián)
當(dāng)前網(wǎng)址:http://www.rwnh.cn/article6/cegoig.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)頁設(shè)計(jì)公司網(wǎng)站導(dǎo)航、動(dòng)態(tài)網(wǎng)站手機(jī)網(wǎng)站建設(shè)、標(biāo)簽優(yōu)化、做網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都網(wǎng)站建設(shè)
富蕴县| 瓦房店市| 德钦县| 阿拉善盟| 汉源县| 苗栗县| 淮滨县| 乾安县| 新巴尔虎左旗| 大邑县| 宁德市| 通山县| 思茅市| 邢台市| 荣昌县| 鹰潭市| 石首市| 太和县| 贵南县| 栾城县| 家居| 林甸县| 马公市| 弋阳县| 万盛区| 大英县| 儋州市| 河池市| 平安县| 射阳县| 玛沁县| 深泽县| 恩平市| 宁化县| 庄河市| 牙克石市| 鹤山市| 突泉县| 阿荣旗| 洛南县| 南和县|