Go 語言中的一等公民:看似普通的函數(shù),憑什么?
本文轉(zhuǎn)載自微信公眾號「腦子進(jìn)煎魚了」,作者陳煎魚 。轉(zhuǎn)載本文請聯(lián)系腦子進(jìn)煎魚了公眾號。
大家好,我是煎魚。
在 Go 語言中,一提函數(shù),大家提的最多的就是 “Go 語言的函數(shù)是一等公民”。這個定義來的非常突然,我們先了解一下什么是一等公民,他又憑什么?
根據(jù)維基百科的一等公民(First-class citizen)的定義:
In programming language design, a first-class citizen (also type, object, entity, or value) in a given programming language is an entity which supports all the operations generally available to other entities. These operations typically include being passed as an argument, returned from a function, modified, and assigned to a variable.
在編程語言設(shè)計(jì)中,給定編程語言中的一等公民(也就是類型,對象,實(shí)體或值)可以把函數(shù)賦值給變量,也可以把函數(shù)作為其它函數(shù)的參數(shù)或者返回值來直接使用。
Go 語言的函數(shù)也滿足這個定義,因此常被稱為 “一等公民”,非常有意思。了解清楚背景后,接下來進(jìn)一步展開。
普通函數(shù)
在 Go 語言中普通函數(shù)的定義格式為 func [函數(shù)名](入?yún)?(出參),如下:
- func callFuncA(x, y string) (s string, err error) {
- return x + y, nil
- }
- func main() {
- callFuncA("炸", "煎魚")
- }
在示例代碼中聲明了一個函數(shù)名為 callFuncA 的方法,他只允許在包內(nèi)調(diào)用,因此首字母為小寫。
其具有兩個入?yún)?,分別是 x 和 y,類型都為 string。而出參為變量 s 和 err,類型分別為 string 和 error。
另外在函數(shù)體內(nèi)返回值時,也可以采用快捷返回的方式:
- func callFuncA(x, y string) (s string, err error) {
- s = x + y
- return
- }
在出參時所聲明的變量名稱,是可以應(yīng)用到自身函數(shù)的。因此若直接執(zhí)行 return 則會隱式返回已經(jīng)聲明的出參變量。
在函數(shù)定義時,其入?yún)⑦€支持可變參數(shù)的語法:
- func callFuncA(x ...string) (s string, err error) {
- s = strings.Join(x, ",")
- return
- }
- func main() {
- fmt.Println(callFuncA("炸", "煎魚"))
- }
在入?yún)⒆兞可下暶鳛?x ...string,則表示變量 x 是 string 類型的可變變量,能夠在入?yún)r傳入多個 string 參數(shù)。
可變變量所傳入的格式為切片(slice)類型,該類型我們會在后面的章節(jié)進(jìn)行講解,你可以理解為不受長度限制的動態(tài)數(shù)組:
- [0: 炸 1: 煎魚]
一般對可變變量的常見后續(xù)操作多是循環(huán)遍歷處理,又或是進(jìn)行拼接等操作。
匿名函數(shù)
Go 語言也默認(rèn)支持匿名函數(shù)的聲明,聲明的方式與普通函數(shù)幾乎一樣:
- func main() {
- s := func(x, y string) (s string, err error) {
- return x + y, nil
- }
- s("炸", "煎魚")
- }
匿名函數(shù)可以在任意地方聲明,且不需要定義函數(shù)名,如果在函數(shù)體后馬上跟 () 則表示聲明后立即執(zhí)行:
- func main() {
- s, _ := func(x, y string) (s string, err error) {
- return x + y, nil
- }("炸", "煎魚")
- }
而在所有的函數(shù)類使用中,有一點(diǎn)非常重要,那就是函數(shù)變量作用域的理解:
- func main() {
- x, y := "炸", "煎魚"
- s, _ := func() (s string, err error) {
- return x + y, nil
- }()
- fmt.Println(s)
- }
該匿名函數(shù)沒有形參,函數(shù)內(nèi)部沒有定義相應(yīng)的變量,此時其讀取的是全局的 x、y 變量的值,輸出結(jié)果是 “炸煎魚”。
- func main() {
- x, y := "炸", "煎魚"
- _, _ = func(x, y string) (s string, err error) {
- x = "吃"
- return x + y, nil
- }(x, y)
- fmt.Println(x, y)
- }
該匿名函數(shù)有形參,但是在函數(shù)內(nèi)部又重新賦值了變量 x。那么最終外部所輸出的變量 x 的值是什么呢?輸出結(jié)果是 “炸 煎魚”。
為什么明明在函數(shù)內(nèi)已經(jīng)對變量 x 重新賦值,卻依然沒有改變?nèi)肿兞?x 的值呢?
其本質(zhì)原因是作用域不同,函數(shù)內(nèi)部所修改的變量 x 是函數(shù)內(nèi)的局部變量。而外部的是全局的變量,所歸屬的作用域不同。
結(jié)構(gòu)方法
在結(jié)合結(jié)構(gòu)體(struct)的方式下,可以聲明歸屬于該結(jié)構(gòu)體下的方法:
- type T struct{}
- func NewT() *T {
- return &T{}
- }
- func (t *T) callFuncA(x, y string) (s string, err error) {
- return x + y, nil
- }
- func main() {
- NewT().callFuncA("炸", "煎魚")
- }
具體的函數(shù)的使用方法與普通函數(shù)一樣,無其他區(qū)別。
而與結(jié)構(gòu)體有關(guān)的值傳遞、引用傳遞的方法調(diào)用將在具體后面的章節(jié)再展開。
內(nèi)置函數(shù)
Go 語言本身有支持一些內(nèi)置函數(shù),這些內(nèi)置函數(shù)的調(diào)用不需要引用第三方標(biāo)準(zhǔn)庫。內(nèi)置函數(shù)的作用是用于配合 Go 語言的常規(guī)使用,數(shù)量非常少。如下:
- 用于獲取某些類型的長度和容量:len、cap。
- 用于創(chuàng)建并分配某些類型的內(nèi)存:new、make。
- 用于錯誤處理機(jī)制(異??只?、異常捕獲):panic、recover。
- 用于復(fù)制和新增切片(slice):copy、append。
- 用于簡單輸出信息:print、println。
- 用于處理復(fù)數(shù):complex、real、imag。
針對每個內(nèi)置函數(shù)的真實(shí)使用場景,我們會在后續(xù)的章節(jié)再進(jìn)一步展開,因?yàn)槊總€內(nèi)置函數(shù)本質(zhì)上都對應(yīng)著各類型的使用場景。
總結(jié)
在本章節(jié)中,我們介紹了 Go 語言的函數(shù)為什么稱是一等公民,并且針對函數(shù)的各類變形:普通函數(shù)、匿名函數(shù)、結(jié)構(gòu)方法、內(nèi)置函數(shù)進(jìn)行了基本的說明。
面對新手入門最容易犯錯的函數(shù)作用域問題,也進(jìn)行了基本的梳理。這塊建議大家要多多深入思考、理解,避免日后踩坑。