這篇文章主要介紹“go語言的函數基礎語法和高級特性總結”,在日常操作中,相信很多人在go語言的函數基礎語法和高級特性總結問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”go語言的函數基礎語法和高級特性總結”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
創(chuàng)新互聯是一家專業(yè)提供洪湖企業(yè)網站建設,專注與成都網站建設、網站制作、H5網站設計、小程序制作等業(yè)務。10年已為洪湖眾多企業(yè)、政府機構等服務。創(chuàng)新互聯專業(yè)網站設計公司優(yōu)惠進行中。
雖然是同一個世界,但是不同的人站在各自立場看問題,結果自然會千人千面,各有不同.
生物學家會下意識對動植物進行分類歸納,面向對象編程也是如此,用一系列的抽象模型去模擬現實世界的行為規(guī)律.
數學家向來以嚴謹求學著稱,作為最重要的基礎科學,數學規(guī)律以及歸納演繹方法論對應的就是函數式編程,不是模擬現實而是描述規(guī)律更有可能創(chuàng)造規(guī)律.
標準的函數式編程具有濃厚的數學色彩,幸運的是,Go
并不是函數式語言,所以也不必受限于近乎苛責般的條條框框.
簡單來說,函數式編程具有以下特點:
不可變性: 不用狀態(tài)變量和可變對象
函數只能有一個參數
純函數沒有副作用
摘自維基百科中關于函數式編程中有這么一段話:
In computer science, functional programming is a programming paradigm—a style of building the structure and elements of computer programs—that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data.
上述的英文的大致意思是說:函數式編程將計算機程序看成是數學函數的推演,不用狀態(tài)變量也不用可變對象來表達數與數之間的關系.
如需了解詳情,可點擊訪問維基百科關于函數式編程Functional programming 的相關介紹.
函數式編程的立足點和出發(fā)點是函數,復雜函數是基本函數經過一定組合規(guī)律形成的,所以描述復雜函數的過程就是如何拆解重組的過程.
所以接下來我們一邊復習一邊學習函數的基本特點,為接下來理解函數式編程打下基礎,關于函數的基礎語言可參考 go 學習筆記之值得特別關注的基礎語法有哪些
下面以最基本四則運算為例,貫穿全文講解函數的基本語法和高級特性,力求做到知其然知其所以然.
func
定義普通函數
eval
函數定義了加減乘除基本運算規(guī)則,若不支持操作類型則拋出異常,終止程序.
func eval(a, b int, op string) int { var result int switch op { case "+": result = a + b case "-": result = a - b case "*": result = a * b case "/": result = a / b default: panic("unsupported operator: " + op) } return result }
測試未定義操作取余 %
運算時,則拋出異常,unsupported operator: %
,說明僅僅支持加減乘除基本運算.
func TestEval(t *testing.T) { // 3 -1 2 0 unsupported operator: % t.Log( eval(1, 2, "+"), eval(1, 2, "-"), eval(1, 2, "*"), eval(1, 2, "/"), eval(1, 2, "%"), ) }
多返回值定義標準函數
Go
語言和其他主流的編程語言明顯不同的是,函數支持多返回值,通常第一個返回值表示真正結果,第二個返回值表示是否錯誤,這也是 Go
關于異常錯誤設計的獨特之處.
如果正常返回,則表示沒有錯誤,那么第一個返回值是正常結果而第二個返回值則是空
nil
;如果異常返回,第一個返回值設計無意義的特殊值,第二個返回值是具體的錯誤信息,一般非nil
.
func evalWithStandardStyle(a, b int, op string) (int, error) { switch op { case "+": return a + b, nil case "-": return a - b, nil case "*": return a * b, nil case "/": return a / b, nil default: return 0, fmt.Errorf("unsupported operator: %s", op) } }
改造 eval
函數以編寫真正 Go
程序,此時再次測試,結果顯示遇到沒有定義的操作符時不再拋出異常而是返回默認零值以及給出簡短的錯誤描述信息.
func TestEvalWithStandardStyle(t *testing.T) { // Success: 2 if result, err := evalWithStandardStyle(5, 2, "/"); err != nil { t.Log("Error:", err) } else { t.Log("Success:", result) } // Error: unsupported operator: % if result, err := evalWithStandardStyle(5, 2, "%"); err != nil { t.Log("Error:", err) } else { t.Log("Success:", result) } }
其他函數作為參數傳入
上例通過多返回值解決了遇到不支持的運算符會報錯終止程序的問題,但是并沒有真正解決問題,假如真的想要進行非預定義的運算時,同樣是無能為力!
誰讓你只是使用者而不是設計者呢!
那么舞臺交給你,你就是主角,你想要怎么處理輸入怎么輸出就怎么處理,全部邏輯轉移給使用者,這樣就不存在無法滿足需求的情況了.
func evalWithApplyStyle(a, b int, op func(int, int) (int, error)) (int, error) { return op(a, b) }
操作符由原來的字符串
string
更改成函數func(int, int) (int, error)
,舞臺交給你,全靠自由發(fā)揮!
evalWithApplyStyle
函數內部直接調用函數參數 op
并返回該函數的處理結果,當前演示示例中函數的控制權完全轉移給函數入參 op
函數,實際情況可按照實際需求決定如何處理 evalWithApplyStyle
邏輯.
func divide(a, b int) (int, error) { return a / b, nil } func mod(a, b int) (int, error) { return a % b, nil }
自己動手,豐衣足食,順手定義除法 divide
和取余 mod
運算,接下來測試下實現效果.
func TestEvalWithApplyStyle(t *testing.T) { // Success: 2 if result, err := evalWithApplyStyle(5, 2, divide); err != nil { t.Log("Error:", err) } else { t.Log("Success:", result) } // Success: 1 if result, err := evalWithApplyStyle(5, 2, mod); err != nil { t.Log("Error:", err) } else { t.Log("Success:", result) } }
測試結果很理想,不僅實現了減加乘除等基本運算,還可以實現之前一直沒法實現的取余運算!
這說明了這種函數作為參數的做法充分調動勞動人民積極性,媽媽再也不用擔心我無法實現復雜功能了呢!
匿名函數也可以作為參數
一般而言,調用函數時都是直接用函數名進行調用,單獨的函數具有可復用性,但如果本就是一次性函數的話,其實是沒必要定義帶函數名形式的函數.
依然是上述例子,這一次對兩個數的運算規(guī)則不再是數學運算了,這一次我們來比較兩個數的最大值,使用匿名函數的形式進行實現.
func TestEvalWithApplyStyle(t *testing.T) { // Success: 5 if result, err := evalWithApplyStyle(5, 2, func(a int, b int) (result int, e error) { if a > b { return a, nil } return b, nil }); err != nil { t.Log("Error:", err) } else { t.Log("Success:", result) } }
函數的返回值可以是函數
依然是上述示例,如果由于原因不需要立即返回函數的計算結果而是等待使用者自己覺得時機合適的時候再計算返回值,這時候函數返回值依然是函數就很有作用了,也就是所謂的惰性求值.
func evalWithFunctionalStyle(a, b int, op func(int, int) (int, error)) func() (int, error) { return func() (int, error) { return op(a, b) } }
上述函數看起來可能有點難以理解,實際上相對于上例僅僅更改了返回值,由原來的 (int, error)
更改成 func() (int, error)
,其余均保持不變喲!
evalWithFunctionalStyle
函數依然是使用者的主場,和上例相比的唯一不同之處在于,你的主場你做主,什么時候裁判完全自己說了算,并不是運行后就立馬宣布結果.
func pow(a, b int) (int, error) { return int(math.Pow(float64(a), float64(b))),nil } func TestEvalWithFunctionalStyle(t *testing.T) { ef := evalWithFunctionalStyle(5, 2, pow) time.Sleep(time.Second * 1) // Success: 25 if result, err := ef(); err != nil { t.Log("Error:", err) } else { t.Log("Success:", result) } }
time.Sleep(time.Second * 1)
演示代碼代表執(zhí)行 evalWithFunctionalStyle
函數后可以不立即計算最終結果,等待時機合適后由使用者再次調用 ef()
函數進行惰性求值.
// 1 1 2 3 5 8 13 21 34 55 // a b // a b func fibonacci() func() int { a, b := 0, 1 return func() int { a, b = b, a+b return a } }
函數可以充當類型
上述示例中講解了函數可以作為返回值,參數有函數,返回值也有參數,所以 evalWithFunctionalStyle
函數看起來比較費勁,而 Go
語言的類型別名就是為了簡化而生的,更何況函數是 Go
中的一等公民,當然也適合了.
func evalWithFunctionalStyle(a, b int, op func(int, int) (int, error)) func() (int, error) { return func() (int, error) { return op(a, b) } }
于是打算把入參函數 func(int, int) (int, error)
和返回值函數 func() (int, error)
進行統一,而入參函數和返回值函數唯一不同之處就是入參個數不同,所以順理成章想到了 Go
函數中的不定長參數相關語法.
type generateIntFunc func(base ...int) (int, error)
這樣入參函數和出參函數都可以用 generateIntFunc
類型函數進行替代,接著改造 evalWithFunctionalStyle
函數.
func evalWithObjectiveStyle(a, b int, op generateIntFunc) generateIntFunc { return func(base ...int) (i int, e error) { return op(a, b) } }
改造后的 evalWithObjectiveStyle
函數看起來比較簡潔,花花架子中看是否中用還不好說,還是用測試用例說話吧!
func TestEvalWithObjectiveStyle(t *testing.T) { ef := evalWithObjectiveStyle(5, 2, func(base ...int) (int,error) { result := 0 for i := range base { result += base[i] } return result,nil }) time.Sleep(time.Second * 1) // Success: 7 if result, err := ef(); err != nil { t.Log("Error:", err) } else { t.Log("Success:", result) } }
函數別名進行類型化后并不影響功能,依然是函數式編程,不過夾雜了些面向對象的味道.
類型化函數可以實現接口
函數通過別名形式進行類型化后可以實現接口,某些程度上可以視為一種類型,因此實現接口也是順理成章的事情.
func (g generateIntFunc) String() string { r,_ := g() return fmt.Sprint(r) }
此處示例代碼中為類型化函數
generateIntFunc
實現String
接口方法,可能并沒有太大實際意義,僅僅是為了講解這個知識點而硬湊上去的,實際情況肯定會有所不同.
func TestEvalWithInterfaceStyle(t *testing.T) { ef := evalWithObjectiveStyle(5, 2, func(base ...int) (int,error) { result := 0 for i := range base { result += base[i] } return result,nil }) time.Sleep(time.Second * 1) // String: 7 t.Log("String:", ef.String()) // Success: 7 if result, err := ef(); err != nil { t.Log("Error:", err) } else { t.Log("Success:", result) } }
惰性求值獲取的函數變量 ef
此時可以調用 String
方法,也就是具備對象化能力,得到的最終結果竟然和直接運行該函數的值一樣?
有點神奇,目前還不理解這是什么操作,如果有 Go
語言的大佬們不吝賜教的話,小弟感激不盡!
水到渠成的閉包
函數的參數,返回值都可以是另外的函數,函數也可以作為引用那樣傳遞給變量,也存在匿名函數等簡化形式,除此之外,類型化后的函數還可以用來實現接口等等特性應該足以闡釋一等公民的高貴身份地位了吧?
如此強大的函數特性,只要稍加組合使用就會擁有強大的能力,并且 Go
語言并不是嚴格的函數式語言,沒有太多語法層面的限制.
// 1 1 2 3 5 8 13 21 34 55 // a b // a b func fibonacci() func() int { a, b := 0, 1 return func() int { a, b = b, a+b return a } }
斐波那契數列函數 fibonacci
的返回值是真正的生成器函數,每次調用都會生成新的斐波那契數字.
這就是 Go
語言實現閉包的一種簡單示例,fibonacci
函數本身的變量 a,b
被內部匿名函數 func() int
所引用,而這種引用最終被使用者不斷調用就會導致最初的 a,b
變量一直被占用著,只要繼續(xù)調用這種生成器,裴波那契數列的數字就會一直遞增.
// 1 1 2 3 5 8 13 21 34 55 func TestFibonacci(t *testing.T) { f := fibonacci() for i := 0; i < 10; i++ { fmt.Print(f(), " ") } fmt.Println() }
func TestFibonacci(t *testing.T) { f := fibonacci() for i := 0; i < 10; i++ { fmt.Print(f(), " ") } fmt.Println() }
函數是一等公民,其中函數參數,變量,函數返回值都可以是函數.
高階函數是普通函數組合而成,參數和返回值可以是另外的函數.
函數是函數式編程的基礎,支持函數式編程但并不是函數式語言.
沒有純粹函數式編程的條條框框,更加靈活自由,良好的可讀性.
到此,關于“go語言的函數基礎語法和高級特性總結”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關知識,請繼續(xù)關注創(chuàng)新互聯網站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
標題名稱:go語言的函數基礎語法和高級特性總結
文章URL:http://www.rwnh.cn/article48/pgsghp.html
成都網站建設公司_創(chuàng)新互聯,為您提供企業(yè)建站、網站改版、移動網站建設、營銷型網站建設、品牌網站制作、微信公眾號
聲明:本網站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯