這是『就要學習 Go 語言』系列的第 17 篇分享文章golang
剛接觸 Go 語言的函數和方法時,我產生過這樣的疑惑:爲何會嚴格區分這二者的概念?學完以後才知道,不像別的語言(Java、PHP等)函數即方法,方法即函數,Go 語言中二者仍是有很大區別的。函數
定義方法與函數相似,區別在於:方法定義時,在 func 和方法名之間會增長一個額外的參數。以下:學習
func (receiver Type) methodName(...Type) Type {
...
}
複製代碼
(receiver Type) 是增長的額外參數,receiver 稱爲接收者,Type 能夠是任意合法的類型,包括:結構體類型或新定義的類型。能夠說,方法 methodName 屬於類型 Type。方法名後面的參數和返回值是可選的。spa
type Employee struct {
FirstName,LastName string
}
func (e Employee) fullName() string {
return e.FirstName + " " + e.LastName
}
func main() {
e := Employee{
FirstName:"Jim",
LastName:"Green",
}
fmt.Println(e.fullName())
}
複製代碼
輸出:.net
Jim Green
複製代碼
採用 Type.methodName(...) 語法調用類型的方法。 上面的代碼,定義了一個不用傳參、返回值爲 string類型的方法 fullName。它屬於結構體類型 Employee,咱們能夠使用 e.fullName() 調用。fullName 中的 e 就是接收者,在方法內部能夠訪問結構體的每個成員。指針
如今,咱們應該很清楚,方法與函數的區別:方法屬於某一種類型,且有接收者。code
到目前爲止,建立的方法使用的都是值接收者,還能夠經過下面的語法建立指針接收者的方法:cdn
func (receiver *Type) methodName(...Type) Type {
...
}
複製代碼
值接收者和指針接收者,最大區別在於:在方法中修改指針接收者的值會影響到調用者的值,而值接收者就不會。一個是值的副本,一個是指針的副本,而指針的副本指向的仍是原來的值。對象
type Employee struct {
FirstName,LastName string
age int
}
func (e Employee)changeFirstName(name string) {
e.FirstName = name
fmt.Println("changeFirstName",e)
}
func (e *Employee)changeAge(age int) {
e.age = age
}
func main() {
e := Employee{
FirstName:"Jim",
LastName:"Green",
age:30,
}
fmt.Println("changebefore",e)
e.changeFirstName("firstName")
fmt.Println("changeName",e)
(&e).changeAge(18)
fmt.Println("changeAge",e)
}
複製代碼
輸出:內存
changebefore {Jim Green 30}
changeFirstName {firstName Green 30}
changeName {Jim Green 30}
changeAge {Jim Green 18}
複製代碼
上面的代碼,方法 changeFirstName() 使用的是值接收者,在方法中修改結構體的成員 FirstName 沒有影響到原來的值;而方法 changeAge() 使用的是指針接收者,在方法中修改結構體成員 age,原來的值也被改變了。
不知道你有沒有注意到,上面的代碼中,調用指針接收者的方法時使用的是指針:(&e).changeAge(18) 。其實,平時編寫代碼的時候,能夠寫成:e.changeAge(18),編譯器會自動幫咱們轉成指針,以知足接收者的要求。同理,e.changeFirstName("firstName") 也能夠寫成 (&e).changeFirstName("firstName") ,但這樣寫就複雜,通常不這麼作。
咱們應該考慮不一樣的場景使用值接收者仍是指針接收者,若是在方法中發生的改變對調用者可見或者變量拷貝成本比較高的,就應該考慮使用指針接收者,其餘狀況建議使用值接收者。例如:大變量 A,佔用內存大,使用值接收者的話拷貝成本高且效率低,這時就應該考慮使用指針接收者。
咱們這裏講雙層嵌套的結構體,外層稱爲父結構體,結構體成員稱爲子結構體,例如:
type Contact struct {
phone,adress string
}
type Employee struct {
FirstName,LastName string
contact Contact
}
複製代碼
Employee 是一個嵌套的結構體類型,稱爲父結構體,成員變量 contact 也是一個結構體,類型是 Contact,稱爲子結構體。
type Contact struct {
phone,adress string
}
type Employee struct {
FirstName,LastName string
contact Contact
}
func (e *Employee)changePhone(newPhone string){
e.contact.phone = newPhone // 注意訪問方式
}
func main() {
e := Employee{
FirstName:"Jim",
LastName:"Green",
contact:Contact{
phone:"111",
adress:"HangZhou",
},
}
fmt.Println("before:",e)
e.changePhone("222")
fmt.Println("after:",e)
}
複製代碼
輸出:
before: {Jim Green {111 HangZhou}}
after: {Jim Green {222 HangZhou}}
複製代碼
上面的代碼,e 是嵌套結構體,在方法 changePhone() 中修改 contact 的成員 phone,注意修改的代碼。
type Contact struct {
phone,adress string
}
type Employee struct {
FirstName,LastName string
Contact
}
func (e *Employee)changePhone(newPhone string){
// e.Contact.phone = newPhone // 方式一
e.phone = newPhone // 方式二
}
func main() {
e := Employee{
FirstName:"Jim",
LastName:"Green",
Contact:Contact{
phone:"111",
adress:"HangZhou",
},
}
fmt.Println("before:",e)
e.changePhone("222")
fmt.Println("after:",e)
}
複製代碼
輸出結果與上面的同樣。 上面的代碼,Contact 是一個匿名成員結構體。在方法 changePhone() 中修改爲員 phone,注意修改的兩種方式。
type Contact struct {
phone,adress string
}
type Employee struct {
FirstName,LastName string
contact Contact
}
func (c *Contact)changePhone(newPhone string){
c.phone = newPhone
}
func main() {
e := Employee{
FirstName:"Jim",
LastName:"Green",
contact:Contact{
phone:"111",
adress:"HangZhou",
},
}
fmt.Println("before:",e)
e.contact.changePhone("222") // 注意調用方式,採用 .
fmt.Println("after:",e)
}
複製代碼
輸出結果與上面的同樣。 上面的代碼,咱們基於結構體類型 Contact 建立了方法 changePhone(),在方法中修改爲員 phone,注意調用方法的方式。
type Contact struct {
phone,adress string
}
type Employee struct {
FirstName,LastName string
Contact
}
func (c *Contact)changePhone(newPhone string){
c.phone = newPhone
}
func main() {
e := Employee{
FirstName:"Jim",
LastName:"Green",
Contact:Contact{
phone:"111",
adress:"HangZhou",
},
}
fmt.Println(e)
// e.Contact.changePhone("222") // 方式一
e.changePhone("222") // 方式二
fmt.Println(e)
}
複製代碼
輸出結果與上面的同樣。 上面的代碼,成員結構體 Contact 是匿名的,在方法 changePhone() 中修改爲員 phone,注意調用方法的方式。
上面四個例子,但願可以幫助你們更好理解嵌套結構體的方法!好,咱們接着往下。
目前爲止,都是在結構體上定義方法。文章開始提到了,能夠在 Go 任一合法類型上定義方法,可是,有個問題:必須保證類型和方法定義在同一個包裏。以前,結構體和方法都定義在 main 包,因此能夠運行。
package main
import "fmt"
func (i int)echo(){
fmt.Println(i)
}
func main() {
}
複製代碼
上面的代碼,基於 int 類型建立了方法 echo(),因爲 int 類型與方法 echo() 定義在不一樣的包內,因此編譯出錯:cannot define new methods on non-local type int。 那如何解決呢?你可能會想到,在 main 包內建立 int 類型別名,對!就是這樣:
package main
import "fmt"
type myInt int
func (i myInt) echo () {
fmt.Println(i)
}
func main() {
var a myInt
a = 20
a.echo()
}
複製代碼
輸出:20 上面的代碼,基於類型別名 myInt 建立了方法 echo,保證了類型和方法都 main 包。
上面提到的例子,都是能夠經過函數的方法實現的,回頭想一想,Go 既然有了函數,爲什麼須要方法呢?
type Rect struct {
width int
height int
}
type Circle struct {
radius float64
}
func (r Rect) Area() int {
return r.width * r.height
}
func (c Circle) Area() float64 {
return math.Pi * c.radius * c.radius
}
func main() {
rect := Rect{5, 4}
cir := Circle{5.0}
fmt.Printf("Rect Area %d\n", rect.Area())
fmt.Printf("Circle Area %0.2f\n", cir.Area())
}
複製代碼
輸出:
Rect Area 20
Circle Area 78.54
複製代碼
上面的代碼,在結構體 Rect 和 Circle 分別定義了同名的 Area() 方法,計算矩形和圓的面積。
學完這篇文章以後,相信你已經學會如何使用方法了,咱們下一節再見!
原創文章,若需轉載請註明出處!
歡迎掃碼關注公衆號「Golang來啦」或者移步 seekload.net ,查看更多精彩文章。
公衆號「Golang來啦」給你準備了一份神祕學習大禮包,後臺回覆【電子書】領取!