使用函數的正確姿式

在Go語言中,函數是一等公民,函數類型也是一等的數據類型。簡單來講,這意味着:函數不但能夠用於封裝代碼,分割功能,解耦邏輯,還能夠化身爲普通的值,在其餘函數間傳遞、賦予變量、作類型判斷和轉換等等。編程

package main

import "fmt"

type Printer func(contents string) (n int, err error)

func printToStd(contents string) (bytesNum int, err error) {
	return fmt.Println(contents)
}

func main() {
	var p Printer
	p = printToStd
	p("something")
}

函數的什麼使用type關鍵字。type後面是函數類型名稱,緊接着是func關鍵字。func右邊的就是這個函數類型的參數列表和結果列表。數組

函數的簽名就是函數的參數列表和結果列表的統稱。各個參數和結果的名稱不能算作函數簽名的一部分,甚至結果列表能夠沒有名稱。嚴格來講,函數的名稱也不能算做函數簽名的一部分,它只是咱們在調用函數時須要給定的標識符而已。閉包

只要兩個函數的參數列表和結果列表中的元素順序及其類型是一致的,咱們就能夠說它們是同樣的函數,或者說它們是實現了同一個函數類型的函數。函數式編程

上面的代碼中printToStd的簽名和Printer的是一致的,所以前者是後者的一個實現。函數

高階函數

Go語言在語言層面支持了函數式編程。指針

什麼是高階函數?

知足下面的兩個條件的任意一個的函數就是高階函數:code

  1. 接受其餘的函數做爲參數傳入。
  2. 把其餘的函數做爲結果返回。
type operate func(x, y int) int

func add(x,y int)int  {
	return x + y
}


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

上面的calculate函數就是一個高階函數。編譯器

實現閉包

自由變量:

在一個函數中,有一個外來標識符,它既不是函數的參數,也不是函數的結果,更不是函數內部聲明的。它是直接從外邊拿過來的。專業屬於稱呼爲「自由變量」string

閉包函數就是由於引用了自由變量,而呈現出一種「不肯定」的狀態,也叫「開放」狀態。也就是說閉包的內部邏輯並非完整的,有一部分邏輯須要這個自由變量參與完成,而自由變量到底表明了什麼在閉包函數被定義的時候是未知的。在Go語言定義閉包函數的時候最多知道自由變量的類型。io

type operate func(x, y int) int
type calculateFunc func(x, y int) (int, error)

func genCalculator(op operate) calculateFunc {
	return func(x, y int) (i int, e error) {
		if op == nil {
			return 0, errors.New("invalid opration")
		}
		return op(x, y), nil
	}
}
func main(){
	op := func(x, y int) int {
		return x + y
	}
	add := genCalculator(op)
	result, err := add(3, 4)
	fmt.Printf("the result: %d (error: %v)\n",result,err)
}

genCalculator函數內部就實現了閉包:那就是定義一個匿名的calculateFunc類型的函數並把它做爲結果值返回。這個匿名的函數就是一個閉包函數。由於這個匿名函數裏使用的變量op既不是它的任何參數或結果,也不是它本身聲明的,而是定義它的genCalculator函數的參數,因此op在匿名函數中是一個自由變量。

這個自由變量究竟表明了什麼,這一點並非在定義這個閉包函數的時候肯定的,而是在genCalculator函數被調用的時候肯定的。只有給定了該參數op,咱們才直到它返回給咱們的閉包函數能夠用於什麼計算。

if op == nil

Go語言編譯器讀到這裏的時候會試圖去尋找op鎖表明的東西,它會發現op表明的是genCalculator函數的參數,而後,編譯器就會把二者聯繫起來,這時能夠說:自由變量op被「捕獲」了。當程序運行到這裏,op就是genCalculator的參數值了,這時,閉包函數的狀態就由「不肯定」變爲了「肯定」,或者說轉到了「閉合」狀態,至此,也就真正造成了一個閉包。

閉包的意義

表面上看,咱們只是延遲實現了一部分程序邏輯或功能而已。

實際上,咱們是在動態地生成那部分邏輯功能。

傳入函數的參數

package main

import "fmt"

func main() {
	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)
}

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

原數組不變。

緣由是全部傳遞給函數的參數值都會被複制,函數在其內部使用的並非參數值的原值,而是它的副本。

注意,對於引用類型,切片,字典,通道,複製的是它們自己的值,不會複製它們引用的底層數據,也就是說,這只是淺表複製,不是深層複製。Go語言沒有深層複製。

對於值類型的數組,若是它的元素類型是引用類型,當這種數組被傳入函數的話,函數中對該參數值的修改會影響到參數嗎?例如[3][]string

若是修改數組中的切片的某個元素,會影響原數組;若是修改數組的某個元素就不會影響原數組。

函數返回給調用方的結果值會被複制嗎?

當函數返回指針類型時,不會發生拷貝。當函數返回非指針類型並把結果賦值給其餘變量時,會發生拷貝。

相關文章
相關標籤/搜索