培訓(xùn)的網(wǎng)站建設(shè)鳴蟬智能建站
方法
- 概述
- 1. 方法定義
- 2. 值方法、指針方法
- 3. 方法集合
- 匿名字段
- 表達(dá)式
- 自定義 error
上一篇:延遲調(diào)用(defer)
概述
1. 方法定義
func (receiver T) 方法名(參數(shù)列表) (返回值列表){}receiver:接收者參數(shù)名T:方法所屬類型
Golang 方法總是綁定對(duì)象實(shí)例,并隱式地將實(shí)例作為第一實(shí)參(receiver)。
1. 只能為當(dāng)前包內(nèi)(local)命名類型定義方法。
2. 參數(shù) receiver 可以任意命名;若方法中未使用,可省略參數(shù)名。
3. 參數(shù) receiver 類型可以是 T(值方法)或 *T(指針方法);基類型 T 不能是接口或指針。
4. 不支持方法重載,receiver 只是參數(shù)簽名的組成部分。
5. 可用實(shí)例 value(值)或 pointer(指針)調(diào)用全部方法,編譯器自動(dòng)轉(zhuǎn)換。
package mainimport "fmt"type Person struct {name string
}// 定義指針方法(*T)
func (p *Person) SetName(name string) {p.name = name
}// 定義值方法(T)
func (p Person) GetName() string {return p.name
}func (p *Person) String() string {return fmt.Sprintf("Person: %s", p.name)
}// 1.只能為當(dāng)前包內(nèi)命名類型定義方法
// 報(bào)錯(cuò):cannot define new methods on non-local type float64
func (f float64) Add(i, j float64) float64 {return i + j
}type Iter interface{}
// 3.receiver 類型不能是 poiter 或 interface
// 報(bào)錯(cuò) invalid receiver type iter (pointer or interface type)
func (Iter) SayType() {}func main() {p := Person{} // 值類型(調(diào)用類型方法前,需要定義一個(gè)類型的值或指針)p.SetName("國慶") // 值類型調(diào)用指針方法fmt.Println(p.name)p2 := &p // 指針類型p.name = "國強(qiáng)"fmt.Println(p2.GetName()) // 指針類型調(diào)用值方法
}
2. 值方法、指針方法
-
值方法
(接收者 receiver 是一個(gè)值,而非指針)該方法操作對(duì)應(yīng) receiver 的值的副本,即時(shí)使用了指針調(diào)用方法,但方法的接受者是值類型,所以方法內(nèi)部操作還是對(duì)副本的操作,而不是指針操作。
package mainimport ("fmt" )type Person struct {name string }// 指針方法 func (p *Person) SetName(name string) {fmt.Printf("指針 - addr: %p, %T\n", p, p)p.name = name }// 值方法 func (p Person) SetNameByValue(name string) {fmt.Printf("值: %p, %T \n", &p, p)p.name = name }func main() {p := &Person{"中華"} // 指針值fmt.Printf("P addr: %p, \n\n", p) p.SetNameByValue("華夏") // 調(diào)用值方法// p.name = 中華,并未改變,說明 p.SetNameByValue 復(fù)制了 p 的副本進(jìn)行操作。fmt.Printf("name = %s\n\n", p.name)p.SetName("國慶")fmt.Printf("name = %s\n", p.name) // name = 國慶 }
以上代碼輸出如下,可以看出:
- p.SetName(指針方法)中 p 的地址和 main 中 p 的地址相同,為同一變量,且該方法給 p.name 重新賦值后,main 中 p.name 跟著改變。
- p.SetNameByValue(值方法)中 p 的地址和 main 中 p 的地址不同,且調(diào)用該方法給 p.name 重新賦值后,main 中 p.name 并未改變。
P addr: 0xc000024070,值: 0xc000024080, test.Person name = 中華指針 - addr: 0xc000024070, *test.Person name = 國慶
-
指針方法
(接收者 receiver 是一個(gè)指針)與值方法相反,當(dāng)接受者是指針時(shí),即使用值類型調(diào)用那么方法內(nèi)部也是對(duì)指針的操作。
func main() {p := Person{"中華"} // 值類型fmt.Printf("P addr: %p, \n\n", p) p.SetNameByValue("華夏")fmt.Printf("name = %s\n\n", p.name) // name = 中華p.SetName("國慶")// 即時(shí) p 為值類型,調(diào)用指針類型時(shí),指針方法內(nèi)部也是對(duì)指針操作。fmt.Printf("name = %s\n", p.name) // name = 國慶 }
-
方法與函數(shù)
-
方法可以看做一種特殊(特定類型變量)的函數(shù)。
文章開頭講過,方法總是綁定對(duì)象實(shí)例,并隱式地將實(shí)例作為第一實(shí)參 receiver。
func (p *Person) SetName(name string) {}
等同于
func SetName(p *Person, name string) { }
-
方法與函數(shù)的區(qū)別
- 函數(shù)不屬于任何類型,而方法屬于特定的類型變量(receiver)的函數(shù)。
- 接收者 receiver 無論是值類型或指針類型,都可以調(diào)用全部方法。但函數(shù)中,參數(shù)類型為值類型,則只能將值類型作為參數(shù)傳遞;參數(shù)類型為指針,則只能將指針類型作為參數(shù)傳遞。
type Person struct {name string }// 指針方法 func (p *Person) SetName(name string) {p.name = name } // 該函數(shù)與方法 p.SetName 相同 func SetName(p *Person, name string) {p.name = name }// 值方法 func (p Person) GetName() {return p.name } // 該函數(shù)與方法 p.GetName 相同 func GetName(p Person) string {return p.name }func main() {p := Person{}// 接收者 p 為值類型,可以調(diào)用指針類型方法。反之亦然。p.SetName("中國") fmt.Println(p.GetName())// 函數(shù)中,參數(shù)為指針類型,只能以指針類型作為參數(shù),反之同理。SetName(&p, "華夏") fmt.Println(p.GetName())fmt.Println(p.GetName(), GetName(p)) }
-
3. 方法集合
每個(gè)類型都有與之關(guān)聯(lián)的方法集,這會(huì)影響到接口實(shí)現(xiàn)規(guī)則。
- 一個(gè)類型的值類型的方法集合中僅包含它的所有值方法:
類型 T 方法集包含全部 receiver T 方法
。 - 一個(gè)類型的指針類型的方法集合囊括了所有值方法和所有指針方法:
類型 *T 方法集包含全部 receiver T + *T 方法
。
func main() {p := Person{} // 值類型m := reflect.TypeOf(p)for i := 0; i < m.NumMethod(); i++ {method := m.Method(i)fmt.Println("Value Method:", method.Name, method.Type)}p2 := &Person{} // 指針類型m = reflect.TypeOf(p2)for i := 0; i < m.NumMethod(); i++ {method := m.Method(i)fmt.Println("Pointer Method:", method.Name, method.Type)}
}
輸出:
Value Method: GetName func(test.Person) string
Pointer Method: GetName func(*test.Person) string
Pointer Method: SetName func(*test.Person, string)
Pointer Method: String func(*test.Person) string
匿名字段
參考結(jié)構(gòu)體章節(jié):8.結(jié)構(gòu)體匿名字段
Golang 中,類型的成員字段在聲明時(shí)沒有字段名而只有類型,那么它就是一個(gè)嵌入字段,也可以被稱為匿名字段。
-
匿名字段默認(rèn)采用類型作為字段名,要求字段名稱必須唯一,且一個(gè)類型中同種類型的匿名字段只能有一個(gè)。
-
任何類型都可以作為匿名字段。
-
匿名結(jié)構(gòu)體成員可直接訪問;當(dāng)多個(gè)匿名結(jié)構(gòu)體內(nèi)存在相同字段時(shí),為了避免歧義需要指定具體的內(nèi)嵌結(jié)構(gòu)體的字段。
-
推薦使用匿名方式嵌套結(jié)構(gòu)體。
-
通過匿名字段可以實(shí)現(xiàn)繼承,實(shí)現(xiàn)“override”。
-
不可遞歸循環(huán)嵌套(你中有我,我中有你)。
表達(dá)式
package mainimport "fmt"type Person struct {name string
}func (p *Person) SetName(name string) {p.name = name
}func (p Person) SetNameByValue(name string) {p.name = name
}func (p Person) GetName() string {return p.name
}func main() {p := Person{"Tom"}fmt.Println(p.GetName()) // 直接調(diào)用mVal := p.GetName // 隱式傳遞,此時(shí)復(fù)制了pmExp := (*Person).GetName // 顯式傳遞p.SetName("Jerry")fmt.Println(mVal(), mExp(&p), p.GetName()) // Tom Jerry JerrymExp2 := (*Person).SetNameByValue // 值方法,此時(shí)依然會(huì)復(fù)制 pmExp2(&p, "Lucy")fmt.Println(mExp(&p), p.GetName()) // Jerry JerrymExp3 := (*Person).SetNamemExp3(&p, "Lucy")fmt.Println(mExp(&p), p.GetName()) // Lucy Lucy
}
自定義 error
package mainimport ("fmt""os""time"
)type CustomError struct {path stringop stringcreateTime stringmessage string
}func (e *CustomError) Error() string {return fmt.Sprintf("%s %s %s: %s", e.createTime, e.op, e.path, e.message)
}func newError(path, op, msg string) *CustomError {return &CustomError{path: path,op: op,createTime: time.Now().Format("2006-01-02 15:04:05"),message: msg,}
}func Open(fname string) (err error) {f, err := os.Open(fname)if err != nil {err = newError(fname, "open", err.Error())return}defer func() {if f.Close() != nil {err = newError(fname, "close", err.Error())}}()return nil
}func main() {err := Open("./test/test.txt")fmt.Println(err)
}