Go語言學習函數(shù)+結構體+方法+接口
1. 函數(shù)
Go語言的函數(shù)屬于“一等公民”(first-class),也就是說:
- 函數(shù)本身可以作為值進行傳遞。
- 支持匿名函數(shù)和閉包(closure)。
- 函數(shù)可以滿足接口。
1.1 函數(shù)返回值
同一種類型返回值
func typedTwoValues() (int, int) { return 1, 2 } a, b := typedTwoValues() fmt.Println(a, b)
帶變量名的返回值
func named Ret Values() (a, b int) { a = 1 b = 2 return }
函數(shù)使用命名返回值時,可以在return中不填寫返回值列表,如果填寫也是可行的
函數(shù)中的參數(shù)傳遞
Go語言中傳入和返回參數(shù)在調用和返回時都使用 值傳遞 ,這里需要注意的是 指針、切片和map等引用型對象指向的內容在參數(shù)傳遞中不會發(fā)生復制,而是將指針進行復制,類似于創(chuàng)建一次引用。
函數(shù)變量
在Go語言中,函數(shù)也是一種類型,可以和其他類型一樣被保存在變量中
func fire() { fmt.Println("fire") } func main() { var f func() //將變量f聲明為func()類型,此時f就被俗稱為“回調函數(shù)”。此時f的值為nil。 f = fire f() }
1.2 匿名函數(shù)——沒有函數(shù)名字的函數(shù)
Go語言支持匿名函數(shù),即在需要使用函數(shù)時,再定義函數(shù),匿名函數(shù)沒有函數(shù)名, 只有函數(shù)體 ,函數(shù)可以被作為一種類型被賦值給函數(shù)類型的變量, 匿名函數(shù)也往往以變量方式被傳遞 。
在定義時調用匿名函數(shù)
匿名函數(shù)可以在聲明后調用,例如:
func(data int) { fmt.Println("hello", data) }(100)
將匿名函數(shù)賦值給變量
匿名函數(shù)體可以被賦值,例如:
// 將匿名函數(shù)體保存到f()中 f := func(data int) { fmt.Println("hello", data) } // 使用f()調用 f(100)
匿名函數(shù)用作回調函數(shù)
使用時再定義匿名函數(shù),不使用先在被調用函數(shù)里面進行聲明,這就是回調精髓
// 遍歷切片的每個元素,通過給定函數(shù)進行元素訪問? func visit(list []int, f func(int)) { for _, v := range list { f(v) } } func main() { // 使用匿名函數(shù)打印切片內容 visit([]int{1, 2, 3, 4}, func(v int) { fmt.Println(v) }) }
可變參數(shù)——參數(shù)數(shù)量不固定的函數(shù)形式
所有參數(shù)都是可變參數(shù):fmt.Println
func Println(a ...interface{}) (n int, err error) { return Fprintln(os.Stdout, a...) }
fmt.Println在使用時,傳入的值類型不受限制,例如:
fmt.Println(5, "hello", &struct{ a int }{1}, true)
當可變參數(shù)為 interface{}
類型時,可以傳入任何類型的值
部分參數(shù) 是可變參數(shù):fmt.Printf
fmt.Printf的第一個參數(shù)為參數(shù)列表,后面的參數(shù)是可變參數(shù):
func Printf(format string, a ...interface{}) (n int, err error) { return Fprintf(os.Stdout, format, a...) } ------------------------------------------------------ fmt.Printf("pure string\n") fmt.Printf("value: %v %f\n", true, math.Pi)
1.3 閉包
閉包可以理解成定義在函數(shù)內部的一個函數(shù)。本質上,閉包是函數(shù)內部和函數(shù)外部連接起來的橋梁。簡單來說,閉包=函數(shù)+引用環(huán)境
func main() { var f = add() fmt.Printf("f(10): %v\n", f(10)) fmt.Printf("f(20): %v\n", f(20)) // f(10): 10 // f(20): 30 } func add() func(int) int { var x int return func(y int) int { x += y return x } }
1.4 defer語句
defer語句將其后面跟隨的語句進行延遲處理,被defer的語句按先進后出的方式執(zhí)行(最先defer的語句最后執(zhí)行,后被defer的語句先執(zhí)行)。
特性:
- 關鍵字defer用于注冊延遲調用
- 直到調用return之前才執(zhí)行(故可用來作資源清理)
- 多個defer語句,F(xiàn)ILO方式執(zhí)行
- defer中的變量,在defer聲明時就定義了
用途:
- 關閉文件句柄
- 鎖資源釋放
- 數(shù)據(jù)庫連接釋放
處理運行時發(fā)生的錯誤
Go語言的錯誤處理思想及設計包含以下特征:
- 一個可能造成錯誤的函數(shù),需要返回值中返回一個 錯誤接口(error )。如果調用是成功的,錯誤接口將返回nil,否則返回錯誤。
- 在函數(shù)調用后需要檢查錯誤,如果發(fā)生錯誤,進行必要的錯誤處理。
錯誤接口的定義格式
error是Go系統(tǒng)聲明的接口類型,代碼如下:
type error interface { Error() string // 返回錯誤的具體描述. }
所有符合Error() string格式的接口都能實現(xiàn)錯誤接口。
定義一個錯誤
在Go語言中,使用errors包進行錯誤的定義,格式如下:
var err = errors.New("this is an error")
錯誤字符串由于相對固定,一般在包作用域聲明, 應盡量減少在使用時直接使用errors.New返回。
宕機(panic)——程序終止運行
手動觸發(fā)宕機
Go語言可以在程序中手動觸發(fā)宕機,讓程序崩潰,這樣開發(fā)者可以及時地發(fā)現(xiàn)錯誤,同時減少可能的損失。
Go語言程序在宕機時,會將堆棧和goroutine信息輸出到控制臺,所以宕機也可以方便地知曉發(fā)生錯誤的位置。
package main func main() { panic("crash") }
panic()的參數(shù)可以是任意類型,
當panic()觸發(fā)的宕機發(fā)生時,panic()后面的代碼將不會被運行,但是在panic()函數(shù)前面已經(jīng)運行過的defer語句依然會在宕機發(fā)生時發(fā)生作用,
1.5 宕機恢復(recover)——防止程序崩潰
無論是代碼運行錯誤由Runtime層拋出的panic崩潰,還是主動觸發(fā)的panic崩潰,都可以配合defer和recover實現(xiàn)錯誤捕捉和恢復,讓代碼在發(fā)生崩潰后允許繼續(xù)運行。
Go沒有異常系統(tǒng),其使用panic觸發(fā)宕機類似于其他語言的拋出異常,那么recover的宕機恢復機制就對應try/catch機制。
panic和recover的關系:
- 有panic沒recover,程序宕機。
- 有panic也有recover捕獲,程序不會宕機。執(zhí)行完對應的defer后,從宕機點退出當前函數(shù)后繼續(xù)執(zhí)行。
提示:雖然panic/recover能模擬其他語言的異常機制,但并不建議代表編寫普通函數(shù)也經(jīng)常性使用這種特性。
2. 結構體
結構體成員是由一系列的成員變量構成,這些成員變量也被稱為“字段”。
字段有以下特性:
- 字段擁有自己的類型和值。
- 字段名必須唯一。
- 字段的類型也可以是結構體,甚至是字段所在結構體的類型。
Go語言中沒有“類”的概念,也不支持“類”的繼承等面向對象的概念。
Go語言的結構體與“類”都是復合結構體,但Go語言中結構體的內嵌配合接口比面向對象具有更高的擴展性和靈活性。
Go語言不僅認為結構體能擁有方法,且每種自定義類型也可以擁有自己的方法。
2.1 定義與給結構體賦值
基本形式:
type Point struct { X int Y int } var p Point p.X = 10 p.Y = 20
結構體的定義只是一種內存布局的描述,只有當結構體實例化時,才會真正地分配內存
創(chuàng)建指針類型的結構體:
type Player struct { name string age int } p = new(Player) p.name = "james" p.age = 40
取結構體的地址實例化:
//使用結構體定義一個命令行指令(Command),指令中包含名稱、變量關聯(lián)和注釋等 type Command struct { name string Var *int comment string } var version int = 1 cmd := &Command{} cmd.name = "version" cmd.Var = &version cmd.comment = "show version"
使用鍵值對填充結構體:
type People struct { name string child *People } relation := &People{ name: "爺爺" child: &People{ name: "爸爸" child: &People{ name: "我" }, } }
3. 方法
Go語言中的方法(Method)是一種作用于特定類型變量的函數(shù)。這種特定類型變量叫做接收器( Receiver )。
如果將特定類型理解為結構體或“類”時,接收器的概念就類似于其他語言中的 this
或者 self
。
3.1 結構體方法
創(chuàng)建一個背包 Bag
結構體為其定義把物品放入背包的方法 insert
:
type Bag struct { items[] int } func (b *Bag) insert(itemid int) { b.items = append(b.items, itemid) } func main() { b := new(Bag) b.insert(1001) }
(b*Bag)
表示接收器,即 Insert
作用的對象實例。每個方法只能有一個接收器。
3.2 接收器
接收器是方法作用的目標
接收器根據(jù)接收器的類型可分:
- 指針接收器
- 非指針接收器
- 兩種接收器在使用時會產生不同的效果。根據(jù)效果的不同,兩種接收器會被用于不同性能和功能要求的代碼中。
指針接收器
由于指針的特性,調用方法時,修改接收器指針的任意成員變量,在方法結束后,修改都是有效的。
// 定義屬性結構 type Property struct { value int } // 設置屬性值方法 func (p *Property) setVal(val int) { p.value = val } // 獲取屬性值方法 func (p *Property) getVal() int { return p.value } func main() { p := new(Property) p.value = 123 fmt.Println(p.getVal()) p.setVal(666) fmt.Println(p.getVal()) }
非指針類型接收器
當方法作用于非指針接收器時,Go語言會在代碼運行時 將接收器的值復制一份 。在非指針接收器的方法中可以獲取接收器的成員值, 但修改后無效 。
type Point struct { x, y int } func (p Point) add(other Point) Point { return Point{p.x + other.x, p.y + other.y} } func main() { // 初始化點 p1 := Point{1, 1} p2 := Point{2, 2} res := p1.add(p2) fmt.Println(res) p3 := Point{3, 3} p4 := p1.add(p2).add(p3) fmt.Println(p4) }
指針接收器和非指針接收器的使用:
指針和非指針接收器的使用在計算機中, 小對象 由于值復制時的速度較快,所以適合使用非指針接收器。 大對象 因為復制性能較低,適合使用指針接收器,在接收器和參數(shù)間傳遞時不進行復制,只是傳遞指針。
4. 接口
接口是雙方約定的一種合作協(xié)議。接口實現(xiàn)者不需要關心接口會被怎樣使用,調用者也不需要關心接口的實現(xiàn)細節(jié)。 接口是一種類型,也是一種抽象結構,不會暴露所含數(shù)據(jù)的格式、類型及結構。
4.1 聲明接口
type 接口類型名 interface { 方法1(參數(shù)列表) 返回值 ... }
Go語言的接口在命名時,一般會在單詞后面添加er,如有寫操作的接口叫Writer,有字符串功能的接口叫Stringer,有關閉功能的接口叫Closer等
方法名:當方法名首字母是大寫時,且這個接口類型名首字母也是大寫時,這個方法可以被接口所在的包(package)之外的代碼訪問。
io包中提供的Writer接口:
type Writer interface { Write(p []type) (n int, err error) }
4.2 實現(xiàn)接口
實現(xiàn)接口的條件:
- 接口的方法與實現(xiàn)接口的類型方法格式一致
- 接口中所有方法均被實現(xiàn)
例:為了抽象數(shù)據(jù)寫入的過程,定義Data Writer接口來描述數(shù)據(jù)寫入需要實現(xiàn)的方法。
// 定義一個數(shù)據(jù)寫入器接口 type DataWriter interface { WriteData(data interface{}) error } // 定義文件結構,用于實現(xiàn)DataWriter type file struct { } // 實現(xiàn)DataWriter接口的方法 func (d *file) WriteData(data interface{}) error { // 模擬寫入數(shù)據(jù) fmt.Println("Write Data:", data) return nil } func main() { // 實例化file f := new(file) // 聲明一個DataWriter接口 var writer DataWriter // 將接口賦值,也就是*file writer = f writer.WriteData("one line data") }
Go語言的接口實現(xiàn)是隱式的,無須讓實現(xiàn)接口的類型寫出實現(xiàn)了哪些接口。這個設計被稱為非侵入式設計。
到此這篇關于Go語言學習函數(shù)+結構體+方法+接口的文章就介紹到這了,更多相關Go 函數(shù)內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
go語言實現(xiàn)簡易比特幣系統(tǒng)錢包的原理解析
這篇文章主要介紹了go語言實現(xiàn)簡易比特幣系統(tǒng)錢包的原理解析,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04Go?處理大數(shù)組使用?for?range?和?for?循環(huán)的區(qū)別
這篇文章主要介紹了Go處理大數(shù)組使用for?range和for循環(huán)的區(qū)別,對于遍歷大數(shù)組而言,for循環(huán)能比for?range循環(huán)更高效與穩(wěn)定,這一點在數(shù)組元素為結構體類型更加明顯,下文具體分析感興趣得小伙伴可以參考一下2022-05-05