這是接着Go語言學習筆記3講的一篇,仍是主要介紹Go語言數據類型。主要以下:golang
在Go語言中,函數類型是一等類型,能夠把函數當作一個值來傳遞和使用。函數類型的值(簡稱爲函數值)既能夠做爲其餘函數的參數,也能夠做爲其餘函數的結果(之一)。算法
函數類型指代了全部能夠接受若干參數並可以返回若干結果的函數。docker
聲明一個函數類型總會以關鍵字 func 做爲開始,緊跟在關鍵字 func 以後的應該是這個函數的簽名,包括了參數聲明列表(在左邊)和結果聲明列表(在右邊),二者用空格分隔。參數聲明列表必須由圓括號括起來,多個參數聲明之間需用逗號分隔。編程
參數聲明是參數名稱在前,參數類型在後,中間以空格分隔。若是有一個參數列表,除了一個名稱爲 name、類型爲 string 的參數以外,還包括一個名稱爲 age 、類型爲 int 的參數。參數列表以下:數組
(name string, age int)
注意:在同一個參數聲明列表中的全部參數名稱都必須是惟一的。閉包
若是相鄰的兩個參數屬於同一數據類型,那麼咱們只須要寫一次參數類型。在上面的參數類型中添加一個名稱爲 level 、類型爲 int 的參數:框架
(name string, age, level int)
這個就至關於:ide
(name string, age int, level int)
固然,這裏甚至能夠省略全部參數的名稱。可是強烈不推薦這種作法,它的可讀性不好。函數式編程
如今向參數聲明中添加一個名稱爲 informations 、類型爲 …string 的可變長參數:函數
(name string, age int, level int, informations ...string)
注意:可變參數必須是參數列表中的最後一個。
函數類型聲明的結果聲明列表中通常包含若干個結果聲明。結果聲明列表的編寫規則與參數聲明基本一致。不過有兩點區別:
只存在可變長參數的聲明而不存在可變長結果的聲明;
以下, bool 就是這個函數類型的惟一結果的類型聲明。該結果聲明獨自組成了該函數類型的結果聲明列表。
func (name string, age int, level int, informations ...string) bool
若是咱們須要命名這個結果爲 done,能夠以下編寫:
func (name string, age int, level int, informations ...string) (done bool)
注意:這時的結果聲明列表必須被圓括號括起來了。命名的結果其名稱能夠做爲附屬於該函數類型聲明的文檔的一部分,方便其餘閱讀的人員瞭解其含義。
一個函數類型能夠有一個結果聲明的列表,這是由於Go語言的函數類型能夠有多個結果,這是Go語言的先進特性之一。以下函數類型聲明:
func (name string, age int, level int, informations ...string) (effected uint, err error)
爲函數聲明多個結果可讓每一個結果的職責更加單一,這既易於理解又方便使用。如上能夠利用這一特性將錯誤值做爲結果(之一)返回給調用它的代碼,而不是包錯誤拋出來,而後再不得不在調用它的地方編寫若干代碼來抓住這個錯誤。(有關Go語言的錯誤處理機制後續博文會詳細討論)
函數類型的多個結果聲明能夠從不一樣的角度來體現函數的內部操做的結果。例如:
func (name string, age int, level int, informations ...string) (done bool, id uint, synchronized bool)
假設上面聲明的函數類型專門用於保存某項數據,它的3個結果的做用以下:
done : 用於表示數據是否被成功保存。
id : 數據被保存後的ID,此ID能夠被用來檢索數據。
這樣該函數的調用法會更加清晰明瞭地獲知具體的操做結果,處理這些操做的結果的代碼也會更加簡單和扁平化。
函數類型的零值是nil。未初始化的函數類型的變量的值就是nil。函數類型的值分爲兩類:命名函數值和匿名函數值。在Go語言中,不少時候一般稱命名函數值爲命名函數,稱匿名函數值爲匿名函數,可是它們都是值的一種。
命名函數
命名函數的聲明通常由關鍵字func、函數名稱、函數簽名(由參數聲明列表和結果聲明列表組成)和函數體組成。若是在函數的簽名中包含告終果聲明列表,那麼在該函數的函數體中的任何可到達的流程分支的最後一條語句都必須是終止語句。終止語句有不少種,好比以關鍵字return或goto開始的語句、僅包含針對內建函數panic(用於產生一個運行時恐慌)的調用表達式的語句。
定義了一個用於取模運算的 Module 函數:
func Module(x, y int) int { return x % y }
注意:在關鍵字 return 右邊的結果必須在數量上與該函數的結果聲明列表中的內容徹底一致,且在對應位置的結果的類型上存在可賦予的關係,不然將不能經過編譯。
爲 Module 函數的結果命名,例如:
func Module(x, y int) (result int){ return x % y }
爲函數的結果命名會使它們能過以常規變量的形式存在,就像函數的參數那樣。當結果被命名,它們在函數被調用時就會被初始化爲對應的數據類型的零值。若是這樣的函數的函數體中有一條不帶任何參數的 return 語句,那麼在執行到這條 return 語句的時候,做爲結果的變量的當前值就會被返回給函數調用方。例如:
func Module(x, y int) (result int){ result = x % y return }
如上面 Module 函數被調用時,變量 result 被初始化爲 int 類型的零值 0。當該函數的函數體中的第一條語句被執行時,變量 result 被賦予了表達式 x % y 的結果值。當該函數體中的無參數的 return 語句被執行時,result 的當前值就會做爲結果被返回給函數調用方。
知識點: Go語言命名函數的聲明還能夠省略掉函數體。這意味着,該函數會由外部程序(如彙編語言程序)實現,而不會由Go語言程序實現。
匿名函數
匿名函數由函數字面量表示。函數字面量也是表達式的一種。在聲明的內容上,匿名函數與命名函數的區別也只是少了一個函數名稱。以下匿名函數:
func (x, y int) (result int){ result = x % y return }
函數字面量也能夠看作是對某個函數類型的即時實現,它比函數類型聲明多了一個函數體。一個函數字面量能夠被賦給一個變量,也能夠被直接調用。
函數做爲Go語言的數據類型之一,能夠把函數做爲一個變量的類型。例如聲明一個變量:
var recorder func (name string, age int, level int)(done bool)
聲明事後,全部符合這個函數類型的實現均可以被賦給變量 recorder,以下:
recorder = func (name string, age int, level int) (done bool) { //省略若干實現語句 return }
注意:被賦給變量 recorder 的函數字面量必須與 recorder 的類型擁有相同的函數簽名。
能夠在一個函數類型的變量上直接應用函數表達式來調用它,例如:
done := recorder("Huazie", 23, 1)
注意:被賦值的變量在數量上必須與函數的結果聲明列表中的內容徹底一致,且對應位置的變量和結果的類型上存在可賦予的關係。一樣適用於對命名函數進行調用並賦值的狀況。
在函數字面量被編寫出來的時候直接調用它,例如:
recorder = func (name string, age int, level int) (done bool) { //省略若干實現語句 return }(「Huazie」, 23, 1)
如上所示函數既然能夠做爲變量的值,那麼也就能夠像其餘值同樣在函數之間傳遞(即做爲其餘函數的參數或其餘函數的結果)。
如今舉出一個例子,如今要聲明一個能夠對一段文本進行加密的函數,同時,要求能夠根據不一樣的應用場景實時地、頻繁地對加密算法進行變動。如上,咱們應該聲明一個可以生成加密函數的函數,而後在程序運行期間,根據不一樣的要求使用這個函數來生成須要的加密函數。此外,全部用於封裝加密算法的函數都應該是同一個函數類型的,這有利於加密算法的無縫替換。
首先聲明一個以下的函數類型:
type Encipher func(plaintext string) []byte
如上Encipher是函數類型 func(plaintext string) []byte 的別名,這個函數接收一個 string 類型的參數,而且返回一個元素類型爲 byte 的切片類型的結果,這分別表明了一類比較通用的加密算法的輸入數據和輸出數據。
有了這個用於封裝加密算法的函數類型以後,以下聲明能夠生成加密函數的函數:
func GenEncryptionFunc(encrypt Encipher) func(string) (ciphertext string) { return func(plaintext string) string { return fmt.Sprintf("%x", encrypt(plaintext)) } }
如上看着比較複雜的函數 GenEncryptionFunc 的簽名中包括了一個參數聲明和一個結果聲明。其中,參數聲明中的參數類型就是以前定義的用於封裝加密算法的函數類型,結果聲明表示了一個函數類型的結果。而這個函數類型正是 GenEncryptionFunc 函數所生成的加密函數的類型,它接收一個 string 類型的明文做爲參考,並返回一個 string 類型的密文做爲結果。
在 GenEncryptionFunc 函數的函數體內直接返回了複合加密函數類型的匿名函數。這個匿名函數的函數體內這一條語句首先調用了名稱爲 encrypt 的函數,對匿名函數的參數的明文加密;而後,它使用了標準庫代碼包 fmt 中的 Sprintf 函數,把 encrypt 函數的調用結果轉換爲字符串。該字符串的內容其實是用十六進制數表示的加密結果,而這個加密結果其實是 []byte 類型的。
每一次調用 GenEncryptionFunc 函數時,傳遞給他的那個加密算法函數都會一直被對應的加密函數引用這。只要生成的加密函數還能夠被訪問,其中的加密算法函數就會一直存在,而不會被Go語言的垃圾回收器回收。
理解GenEncryptionFunc函數所涉及到的一些概念:
知識點: 閉包這個詞源自於經過「捕獲」自由變量的綁定對函數文本執行的「閉合」動做。
只有當函數類型是一等類型而且其值能夠做爲其餘函數的參數或結果的時候,纔可以編寫出實現閉包的代碼。函數類型是Go語言支持函數式編程範式的重要體現,也就是咱們編寫函數式風格代碼的主要手段。函數還能夠附屬於任何自定義的數據類型,或者與接口類型和結構體類型相結合做爲針對某個或某些數據類型的操做方法。
方法就是附屬於某個自定義的數據類型的函數。一個方法就是一個與某個接受者關聯的函數。方法的聲明中包含了關鍵字func、接收者聲明、方法名稱、參數聲明列表、結果聲明列表和方法體。其中的接收者聲明、參數聲明列表和結果聲明列表統稱爲方法簽名,而方法體能夠在某些狀況下被忽略。例如:
type MyIntSlice []int func (self MyIntSlice) Max() (result int) { //省略若干實現語句 return }
如上,咱們首先自定義了一個數據類型MyIntSlice,能夠看作 []int 的別名類型。同時,這裏還聲明瞭一個方法。在這個名稱爲 Max 的方法中,接收者聲明爲(self MyIntSlice)。右邊的標識符表示該方法所屬的數據類型,即 MyIntSlice ; 左邊的接收者標識符則表明了 MyIntSlice 類型的值在方法 Max 中的名稱。
方法聲明中的接收者聲明有關的幾條編寫規則:
接收者聲明中的類型必須是某個自定義的數據類型,或者是一個與某個自定義數據類型對應的指針類型。但不論接收者的類型是哪種,接收者的基本類型都會是那個自定義數據類型。接收者的基本類型既不能是一個指針類型,也不能是一個接口類型。例如, 方法聲明:
func (self *MyIntSlice) Min() (result int)//接收者的類是*MyIntSlice,而其基本類型是MyIntSlice.
接收者聲明中的類型必須由非限定標識符表明。方法所屬的數據類型的聲明必須與該方法聲明處在同一個代碼包內。
接收者標識符不能是空標識符「_」, 而且必須在其所在的方法簽名中是惟一的。
在Go語言中,經常把接收者類型是某個自定義數據類型的方法叫作該數據類型的值方法,而把接收者類型是某個自定義數據類型對應的指針類型的方法叫做該數據類型的指針方法。
對於一個接收者的基本類型來講,它所包含的方法的名稱之間不能有重複。若是這個接收者的基本類型是一個結構體類型,還須要保證它包含的字段和方法的名稱之間不能出現重複。
定義一個方法:
func (self *MyIntSlice) Min() (result int)
該方法的類型:
func Min() (self *MyIntSlice, result int)
注意:形如上述方法的類型表示的函數的值只能算是一個函數,而不能叫做方法。這樣的函數並無與任何自定義數據類型相關聯。
在接收者的基本類型肯定的狀況下,如何在值方法和指針方法作出選擇:
在某個自定義數據類型的值上,只可以調用與這個數據類型相關聯的值方法,而在指向這個值的指針值上,卻可以調用與其數據類型關聯的值方法和指針方法。雖然自定義數據類型的方法集合中不包含與它關聯的指針類型,可是咱們仍可以經過這個類型的值調用它的指針方法,這裏須要使用取地址符&。
一個Go語言的接口由一個方法的集合表明。只要一個數據類型(或與其對應的指針類型)附帶的方法集合是某一個接口的方法集合的超集,那麼就能夠斷定該類型實現了這個接口。
接口類型的聲明由若干個方法的聲明組成。方法的聲明由方法名稱和方法簽名構成。在一個接口類型的聲明中不容許出現重複的方法名稱。
接口類型是全部自定義的接口類型的統稱。以標準庫代碼包 sort 中的接口類型 Interface 爲例,聲明以下:
type Interface interface { Len() int Less(i, j int) bool Swap(I, j int) }
在Go語言中能夠將一個接口類型嵌入到另外一個接口類型中。以下接口類型聲明:
type Sortable interface { sort.Interface Sort() }
如上接口類型 Sortable 實際包含了4個方法聲明,分別是Len、Less、Swap 和 Sort。
Go語言並不提供典型的類型驅動的子類化方法,可是卻利用這種嵌入的方式實現了一樣的效果。類型嵌入一樣體現了非嵌入式的風格,一樣適用於下面要講的結構體類型。
注意:一個接口類型只接受其餘接口類型的嵌入。
對於接口的嵌入,一個約束就是不能嵌入自身,包括直接嵌入和間接嵌入。
直接嵌入以下:
type Interface1 interface { Interface1 }
而間接嵌入以下:
type Interface2 interface { Interface3 } type Interface3 interface { Interface2 }
錯誤的接口嵌入會形成編譯錯誤。另外,當前接口類型中聲明的方法也不能與任何被嵌入其中的接口類型的方法重名,不然也會形成編譯錯誤。
至於Go語言的自身定義的一個特殊的接口類型----空類型 interface{},前面也提到過,就是不包含任何方法聲明的接口。而且,Go語言中全部數據類型都是它的實現。
Go語言的接口類型沒有相應的值表示法,由於接口是規範而不是實現。但一個接口類型的變量能夠被賦予任何實現了這個接口類型的數據類型的值,所以接口類型的值能夠由任何實現了這個接口類型的其餘數據類型的值來表示。
接口的最基本屬性就是它們的方法集合。
實現一個接口類型的能夠是任何自定義的數據類型,只要這個數據類型附帶的方法集合是該接口類型的方法集合的超集。編寫一個自定義的數據類型 SortableStrings ,以下:
type SortableStrings [3]string
如上這個自定義的數據類型至關於 [3]string 類型的一個別名類型。如今想讓這個自定義數據類型實現 sort.Interface 接口類型,就須要實現sort.Interface 中聲明的所有方法,這些方法的實現都須要以類型 SortableStrings 爲接收者的類型。這些方法的聲明以下:
func (self SortableStrings) Len() int { return len(self) } func (self SortableStrings) Less(i, j int) bool { return self[i] < self[j] } func (self SortableStrings) Swap(i, j int) { self[i], self[j] = self[j], self[i] }
有了上面三個方法的聲明,SortableStrings類型就已是一個sort.Interface接口類型的實現了。使用 Go語言學習筆記2 中講的類型斷言表達式驗證,編寫代碼以下:
_, ok := interface{}(SortableStrings{}).(sort.Interface)
注意: 想要讓這條語句編譯經過,首先須要導入標準代碼包sort。
在如上賦值語句的右邊是一個類型斷言表達式,左邊的兩個標識符表明了這個表達式的求值結果。這裏不關心轉換後的結果,只關注類型轉換是否成功,所以第一個標識符爲空標識符「_」;第二個標識符 ok 表明了一個布爾類型的變量,true 表示轉換成功,false 表示轉換失敗。以下圖,顯示 ok 的結果爲 true,由於 SortableStrings 類型確實實現了接口類型 sort.Interface 中聲明的全部方法。
一個接口類型能夠被任意數量的數據類型實現。一個數據類型也能夠同時實現多個接口類型。
如上的自定義數據類型 SortableStrings 也能夠實現接口類型 Sortable,以下再編寫一個方法聲明:
func (self SortableStrings) Sort() { sort.Sort(self) }
如今,SortableStrings 類型在實現了接口類型 sort.Interface 的同時也實現了接口類型 Sortable。類型斷言表達式驗證以下:
_, ok2 := interface{}(SortableStrings{}).(Sortable)
ok2的結果爲true,以下圖:
如今,把 SortableStrings 類型包含的 Sort 方法中的接收者類型由 SortableStrings 改成 *SortableStrings,以下:
func (self *SortableStrings) Sort() { sort.Sort(self) }
這個函數的接收者類型改成了與 SortableStrings 類型對應的指針類型。方法Sort再也不是一個值方法了,已經變成了一個指針方法。只有與 SortableStrings 類型的值對應的指針值纔可以經過上面的類型斷言,以下:
_, ok3 := interface{}(&SortableStrings{}).(Sortable)
這時 ok2 的值爲 false,ok3 的值爲 true,以下圖:
再添加以下測試代碼:
ss := SortableStrings("2", "3", "1") ss.Sort() fmt.Printf("Sortable strings: %v\n", ss)
以上出現的關於標準庫代碼包 fmt 的用法,親們能夠參考 http://docscn.studygolang.com/pkg/fmt
測試結果以下圖:
上面打印的信息中的 [2, 3, 1] 是 SortableStrings 類型值的字符串表示,從上面的結果能夠看見,變量 ss 的值並無排序,但在打印前已經調用了 Sort 方法。
下面且聽解釋:
: 上面講到,在值方法中,對接收者的值的改變在該方法以外是不可見的。SortableStrings 類型的 Sort 方法其實是經過函數 sort.Sort 來對接收者的值進行排序的。sort.Sort 函數接受一個類型爲 sort.Interface 的參數值,並利用這個值的方法Len、Less 和 Swap來修改其參數中的各個元素的位置以完成排序工做。對於 SortableStrings 類型,雖然它實現了接口類型 sort.Interface 中聲明的所有方法,可是這些方法都是值方法,從而這些方法中對接收者值的改變並不會影響到它的源值,只是改變了源值的複製品而已。
對於上面的問題,目前的解決方案是將 SortableStrings 類型的方法Len、Less 和 Swap的接收者類型都改成 *SortableStrings,以下圖展現的運行結果:
但這時的 SortableStrings 類型就再也不是接口類型 sort.Interface 的實現,*SortableStrings 纔是接口類型 sort.Interface 的實現,如上圖中 ok 的值爲 false。
如今咱們再考慮一種方案,對 SortableStrings 類型的聲明稍做改動:
type SortableStrings []string //去掉了方括號中的3
這個時候其實是將 SortableStrings 有數組類型的別名類型改成了切片類型的別名類型,可是又使得如今與之相關的方法沒法經過編譯。主要的錯誤以下圖:
上面顯示的主要錯誤有兩個,一是內建函數 len 的參數不能是指向切片值的指針類型值;二是索引表達式不能被應用在指向切片值的指針類型值上。
下面對於此的解決方法就是將方法Len、Less、Swap 和 Sort 的接收者類型都由*SortableStrings改回SortableStrings。這裏是由於改動後的SortableStrings是切片類型,而切片類型是引用類型;對於引用類型來講,值方法對接收者值的改變也會反映在其源值上。以下圖爲修改過的結果:
親們,對於上面的接口出現的代碼,能夠點擊下載 Go源碼文件,本身修改修改,好好體會體會接口的用法。只須要在本身的工做區的src目錄中的任意包中(這些包有意義便可)放入如下源碼文件,進入命令行該文件目錄輸入上面的命令便可,固然首先你的Go語言環境變量要配好。
本篇就聊到這裏,下篇繼續未完的Go語言數據類型…
最後附上知名的Go語言開源框架(每篇更新一個):
Docker: 一個軟件部署解決方案,也是一個輕量級的應用容器框架。使用 Docker,咱們能夠輕鬆地打包、發佈和運行任何應用。如今,Docker 已經成爲了名副其實的 Go 語言殺手級應用框架。其官網:http://www.docker.com。非官方的中文網站 : http://www.docker.org.cn