<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Go 經(jīng)典入門系列 17:方法

          共 9641字,需瀏覽 20分鐘

           ·

          2020-12-17 02:14

          點擊上方藍(lán)色“Go語言中文網(wǎng)”關(guān)注,每天一起學(xué) Go

          歡迎來到 Golang 系列教程[1] 的第 17 個教程。

          什么是方法?

          方法其實就是一個函數(shù),在 func 這個關(guān)鍵字和方法名中間加入了一個特殊的接收器類型。接收器可以是結(jié)構(gòu)體類型或者是非結(jié)構(gòu)體類型。接收器是可以在方法的內(nèi)部訪問的。

          下面就是創(chuàng)建一個方法的語法。

          func?(t?Type)?methodName(parameter?list)?{
          }

          上面的代碼片段創(chuàng)建了一個接收器類型為 Type 的方法 methodName

          方法示例

          讓我們來編寫一個簡單的小程序,它會在結(jié)構(gòu)體類型上創(chuàng)建一個方法并調(diào)用它。

          package?main

          import?(
          ????"fmt"
          )

          type?Employee?struct?{
          ????name?????string
          ????salary???int
          ????currency?string
          }

          /*
          ??displaySalary()?方法將?Employee?做為接收器類型
          */

          func?(e?Employee)?displaySalary()?{
          ????fmt.Printf("Salary?of?%s?is?%s%d",?e.name,?e.currency,?e.salary)
          }

          func?main()?{
          ????emp1?:=?Employee?{
          ????????name:?????"Sam?Adolf",
          ????????salary:???5000,
          ????????currency:?"$",
          ????}
          ????emp1.displaySalary()?//?調(diào)用?Employee?類型的?displaySalary()?方法
          }

          在線運行程序[2]

          在上面程序的第 16 行,我們在 Employee 結(jié)構(gòu)體類型上創(chuàng)建了一個 displaySalary 方法。displaySalary()方法在方法的內(nèi)部訪問了接收器 e Employee。在第 17 行,我們使用接收器 e,并打印 employee 的 name、currency 和 salary 這 3 個字段。

          在第 26 行,我們調(diào)用了方法 emp1.displaySalary()

          程序輸出:Salary of Sam Adolf is $5000

          為什么我們已經(jīng)有函數(shù)了還需要方法呢?

          上面的程序已經(jīng)被重寫為只使用函數(shù),沒有方法。

          package?main

          import?(
          ????"fmt"
          )

          type?Employee?struct?{
          ????name?????string
          ????salary???int
          ????currency?string
          }

          /*
          displaySalary()方法被轉(zhuǎn)化為一個函數(shù),把 Employee 當(dāng)做參數(shù)傳入。
          */

          func?displaySalary(e?Employee)?{
          ????fmt.Printf("Salary?of?%s?is?%s%d",?e.name,?e.currency,?e.salary)
          }

          func?main()?{
          ????emp1?:=?Employee{
          ????????name:?????"Sam?Adolf",
          ????????salary:???5000,
          ????????currency:?"$",
          ????}
          ????displaySalary(emp1)
          }

          在線運行程序[3]

          在上面的程序中,displaySalary 方法被轉(zhuǎn)化為一個函數(shù),Employee 結(jié)構(gòu)體被當(dāng)做參數(shù)傳遞給它。這個程序也產(chǎn)生完全相同的輸出:Salary of Sam Adolf is $5000

          既然我們可以使用函數(shù)寫出相同的程序,那么為什么我們需要方法?這有著幾個原因,讓我們一個個的看看。

          • Go 不是純粹的面向?qū)ο缶幊陶Z言[4],而且Go不支持類。因此,基于類型的方法是一種實現(xiàn)和類相似行為的途徑。

          • 相同的名字的方法可以定義在不同的類型上,而相同名字的函數(shù)是不被允許的。假設(shè)我們有一個 SquareCircle 結(jié)構(gòu)體。可以在 SquareCircle 上分別定義一個 Area 方法。見下面的程序。

          package?main

          import?(
          ????"fmt"
          ????"math"
          )

          type?Rectangle?struct?{
          ????length?int
          ????width??int
          }

          type?Circle?struct?{
          ????radius?float64
          }

          func?(r?Rectangle)?Area()?int?{
          ????return?r.length?*?r.width
          }

          func?(c?Circle)?Area()?float64?{
          ????return?math.Pi?*?c.radius?*?c.radius
          }

          func?main()?{
          ????r?:=?Rectangle{
          ????????length:?10,
          ????????width:??5,
          ????}
          ????fmt.Printf("Area?of?rectangle?%d\n",?r.Area())
          ????c?:=?Circle{
          ????????radius:?12,
          ????}
          ????fmt.Printf("Area?of?circle?%f",?c.Area())
          }

          在線運行程序[5]

          該程序輸出:

          Area?of?rectangle?50
          Area?of?circle?452.389342

          上面方法的屬性被使用在接口中。我們將在接下來的教程中討論這個問題。

          指針接收器與值接收器

          到目前為止,我們只看到了使用值接收器的方法。還可以創(chuàng)建使用指針接收器的方法。值接收器和指針接收器之間的區(qū)別在于,在指針接收器的方法內(nèi)部的改變對于調(diào)用者是可見的,然而值接收器的情況不是這樣的。讓我們用下面的程序來幫助理解這一點。

          package?main

          import?(
          ????"fmt"
          )

          type?Employee?struct?{
          ????name?string
          ????age??int
          }

          /*
          使用值接收器的方法。
          */

          func?(e?Employee)?changeName(newName?string)?{
          ????e.name?=?newName
          }

          /*
          使用指針接收器的方法。
          */

          func?(e?*Employee)?changeAge(newAge?int)?{
          ????e.age?=?newAge
          }

          func?main()?{
          ????e?:=?Employee{
          ????????name:?"Mark?Andrew",
          ????????age:??50,
          ????}
          ????fmt.Printf("Employee?name?before?change:?%s",?e.name)
          ????e.changeName("Michael?Andrew")
          ????fmt.Printf("\nEmployee?name?after?change:?%s",?e.name)

          ????fmt.Printf("\n\nEmployee?age?before?change:?%d",?e.age)
          ????(&e).changeAge(51)
          ????fmt.Printf("\nEmployee?age?after?change:?%d",?e.age)
          }

          在線運行程序[6]

          在上面的程序中,changeName 方法有一個值接收器 (e Employee),而 changeAge 方法有一個指針接收器 (e *Employee)。在 changeName 方法中對 Employee 結(jié)構(gòu)體的字段 name 所做的改變對調(diào)用者是不可見的,因此程序在調(diào)用 e.changeName("Michael Andrew") 這個方法的前后打印出相同的名字。由于 changeAge 方法是使用指針 (e *Employee) 接收器的,所以在調(diào)用 (&e).changeAge(51) 方法對 age 字段做出的改變對調(diào)用者將是可見的。該程序輸出如下:

          Employee?name?before?change:?Mark?Andrew
          Employee?name?after?change:?Mark?Andrew

          Employee?age?before?change:?50
          Employee?age?after?change:?51

          在上面程序的第 36 行,我們使用 (&e).changeAge(51) 來調(diào)用 changeAge 方法。由于 changeAge 方法有一個指針接收器,所以我們使用 (&e) 來調(diào)用這個方法。其實沒有這個必要,Go語言讓我們可以直接使用 e.changeAge(51)e.changeAge(51) 會自動被Go語言解釋為 (&e).changeAge(51)

          下面的程序[7]重寫了,使用 e.changeAge(51) 來代替 (&e).changeAge(51),它輸出相同的結(jié)果。

          package?main

          import?(
          ????"fmt"
          )

          type?Employee?struct?{
          ????name?string
          ????age??int
          }

          /*
          使用值接收器的方法。
          */

          func?(e?Employee)?changeName(newName?string)?{
          ????e.name?=?newName
          }

          /*
          使用指針接收器的方法。
          */

          func?(e?*Employee)?changeAge(newAge?int)?{
          ????e.age?=?newAge
          }

          func?main()?{
          ????e?:=?Employee{
          ????????name:?"Mark?Andrew",
          ????????age:??50,
          ????}
          ????fmt.Printf("Employee?name?before?change:?%s",?e.name)
          ????e.changeName("Michael?Andrew")
          ????fmt.Printf("\nEmployee?name?after?change:?%s",?e.name)

          ????fmt.Printf("\n\nEmployee?age?before?change:?%d",?e.age)
          ????e.changeAge(51)
          ????fmt.Printf("\nEmployee?age?after?change:?%d",?e.age)
          }

          在線運行程序[8]

          那么什么時候使用指針接收器,什么時候使用值接收器?

          一般來說,指針接收器可以使用在:對方法內(nèi)部的接收器所做的改變應(yīng)該對調(diào)用者可見時。

          指針接收器也可以被使用在如下場景:當(dāng)拷貝一個結(jié)構(gòu)體的代價過于昂貴時。考慮下一個結(jié)構(gòu)體有很多的字段。在方法內(nèi)使用這個結(jié)構(gòu)體做為值接收器需要拷貝整個結(jié)構(gòu)體,這是很昂貴的。在這種情況下使用指針接收器,結(jié)構(gòu)體不會被拷貝,只會傳遞一個指針到方法內(nèi)部使用。

          在其他的所有情況,值接收器都可以被使用。

          匿名字段的方法

          屬于結(jié)構(gòu)體的匿名字段的方法可以被直接調(diào)用,就好像這些方法是屬于定義了匿名字段的結(jié)構(gòu)體一樣。

          package?main

          import?(
          ????"fmt"
          )

          type?address?struct?{
          ????city??string
          ????state?string
          }

          func?(a?address)?fullAddress()?{
          ????fmt.Printf("Full?address:?%s,?%s",?a.city,?a.state)
          }

          type?person?struct?{
          ????firstName?string
          ????lastName??string
          ????address
          }

          func?main()?{
          ????p?:=?person{
          ????????firstName:?"Elon",
          ????????lastName:??"Musk",
          ????????address:?address?{
          ????????????city:??"Los?Angeles",
          ????????????state:?"California",
          ????????},
          ????}

          ????p.fullAddress()?//訪問?address?結(jié)構(gòu)體的?fullAddress?方法
          }

          在線運行程序[9]

          在上面程序的第 32 行,我們通過使用 p.fullAddress() 來訪問 address 結(jié)構(gòu)體的 fullAddress() 方法。明確的調(diào)用 p.address.fullAddress() 是沒有必要的。該程序輸出:

          Full?address:?Los?Angeles,?California

          在方法中使用值接收器 與 在函數(shù)中使用值參數(shù)

          這個話題很多Go語言新手都弄不明白。我會盡量講清楚。

          當(dāng)一個函數(shù)有一個值參數(shù),它只能接受一個值參數(shù)。

          當(dāng)一個方法有一個值接收器,它可以接受值接收器和指針接收器。

          讓我們通過一個例子來理解這一點。

          package?main

          import?(
          ????"fmt"
          )

          type?rectangle?struct?{
          ????length?int
          ????width??int
          }

          func?area(r?rectangle)?{
          ????fmt.Printf("Area?Function?result:?%d\n",?(r.length?*?r.width))
          }

          func?(r?rectangle)?area()?{
          ????fmt.Printf("Area?Method?result:?%d\n",?(r.length?*?r.width))
          }

          func?main()?{
          ????r?:=?rectangle{
          ????????length:?10,
          ????????width:??5,
          ????}
          ????area(r)
          ????r.area()

          ????p?:=?&r
          ????/*
          ???????compilation?error,?cannot?use?p?(type?*rectangle)?as?type?rectangle
          ???????in?argument?to?area
          ????*/

          ????//area(p)

          ????p.area()//通過指針調(diào)用值接收器
          }

          在線運行程序[10]

          第 12 行的函數(shù) func area(r rectangle) 接受一個值參數(shù),方法 func (r rectangle) area() 接受一個值接收器。

          在第 25 行,我們通過值參數(shù) area(r) 來調(diào)用 area 這個函數(shù),這是合法的。同樣,我們使用值接收器來調(diào)用 area 方法 r.area(),這也是合法的。

          在第 28 行,我們創(chuàng)建了一個指向 r 的指針 p。如果我們試圖把這個指針傳遞到只能接受一個值參數(shù)的函數(shù) area,編譯器將會報錯。所以我把代碼的第 33 行注釋了。如果你把這行的代碼注釋去掉,編譯器將會拋出錯誤 compilation error, cannot use p (type *rectangle) as type rectangle in argument to area.。這將會按預(yù)期拋出錯誤。

          現(xiàn)在到了棘手的部分了,在第35行的代碼 p.area() 使用指針接收器 p 調(diào)用了只接受一個值接收器的方法 area。這是完全有效的。原因是當(dāng) area 有一個值接收器時,為了方便Go語言把 p.area() 解釋為 (*p).area()

          該程序?qū)敵觯?/p>

          Area?Function?result:?50
          Area?Method?result:?50
          Area?Method?result:?50

          在方法中使用指針接收器 與 在函數(shù)中使用指針參數(shù)

          和值參數(shù)相類似,函數(shù)使用指針參數(shù)只接受指針,而使用指針接收器的方法可以使用值接收器和指針接收器。

          package?main

          import?(
          ????"fmt"
          )

          type?rectangle?struct?{
          ????length?int
          ????width??int
          }

          func?perimeter(r?*rectangle)?{
          ????fmt.Println("perimeter?function?output:",?2*(r.length+r.width))

          }

          func?(r?*rectangle)?perimeter()?{
          ????fmt.Println("perimeter?method?output:",?2*(r.length+r.width))
          }

          func?main()?{
          ????r?:=?rectangle{
          ????????length:?10,
          ????????width:??5,
          ????}
          ????p?:=?&r?//pointer?to?r
          ????perimeter(p)
          ????p.perimeter()

          ????/*
          ????????cannot?use?r?(type?rectangle)?as?type?*rectangle?in?argument?to?perimeter
          ????*/

          ????//perimeter(r)

          ????r.perimeter()//使用值來調(diào)用指針接收器
          }

          在線運行程序[11]

          在上面程序的第 12 行,定義了一個接受指針參數(shù)的函數(shù) perimeter。第 17 行定義了一個有一個指針接收器的方法。

          在第 27 行,我們調(diào)用 perimeter 函數(shù)時傳入了一個指針參數(shù)。在第 28 行,我們通過指針接收器調(diào)用了 perimeter 方法。所有一切看起來都這么完美。

          在被注釋掉的第 33 行,我們嘗試通過傳入值參數(shù) r 調(diào)用函數(shù) perimeter。這是不被允許的,因為函數(shù)的指針參數(shù)不接受值參數(shù)。如果你把這行的代碼注釋去掉并把程序運行起來,編譯器將會拋出錯誤 main.go:33: cannot use r (type rectangle) as type *rectangle in argument to perimeter.

          在第 35 行,我們通過值接收器 r 來調(diào)用有指針接收器的方法 perimeter。這是被允許的,為了方便Go語言把代碼 r.perimeter() 解釋為 (&r).perimeter()。該程序輸出:

          perimeter?function?output:?30
          perimeter?method?output:?30
          perimeter?method?output:?30

          在非結(jié)構(gòu)體上的方法

          到目前為止,我們只在結(jié)構(gòu)體類型上定義方法。也可以在非結(jié)構(gòu)體類型上定義方法,但是有一個問題。為了在一個類型上定義一個方法,方法的接收器類型定義和方法的定義應(yīng)該在同一個包中。到目前為止,我們定義的所有結(jié)構(gòu)體和結(jié)構(gòu)體上的方法都是在同一個 main 包中,因此它們是可以運行的。

          package?main

          func?(a?int)?add(b?int)?{
          }

          func?main()?{

          }

          在線運行程序[12]

          在上面程序的第 3 行,我們嘗試把一個 add 方法添加到內(nèi)置的類型 int。這是不允許的,因為 add 方法的定義和 int 類型的定義不在同一個包中。該程序會拋出編譯錯誤 cannot define new methods on non-local type int

          讓該程序工作的方法是為內(nèi)置類型 int 創(chuàng)建一個類型別名,然后創(chuàng)建一個以該類型別名為接收器的方法。

          package?main

          import?"fmt"

          type?myInt?int

          func?(a?myInt)?add(b?myInt)?myInt?{
          ????return?a?+?b
          }

          func?main()?{
          ????num1?:=?myInt(5)
          ????num2?:=?myInt(10)
          ????sum?:=?num1.add(num2)
          ????fmt.Println("Sum?is",?sum)
          }

          在線運行程序[13]

          在上面程序的第5行,我們?yōu)?int 創(chuàng)建了一個類型別名 myInt。在第7行,我們定義了一個以 myInt 為接收器的的方法 add

          該程序?qū)蛴〕?Sum is 15

          我已經(jīng)創(chuàng)建了一個程序,包含了我們迄今為止所討論的所有概念,詳見github[14]

          這就是Go中的方法。祝你有美好的一天。

          上一教程 - 結(jié)構(gòu)體

          下一教程 - 接口 - I[15]


          via: https://golangbot.com/methods/

          作者:Nick Coghlan[16]譯者:MDGSF[17]校對:rxcai[18]

          本文由 GCTT[19] 原創(chuàng)編譯,Go 中文網(wǎng)[20] 榮譽推出

          參考資料

          [1]

          Golang 系列教程: https://studygolang.com/subject/2

          [2]

          在線運行程序: https://play.golang.org/p/rRsI_sWAOZ

          [3]

          在線運行程序: https://play.golang.org/p/dFwObgCUU0

          [4]

          Go 不是純粹的面向?qū)ο缶幊陶Z言: https://golang.org/doc/faq#Is_Go_an_object-oriented_language

          [5]

          在線運行程序: https://play.golang.org/p/0hDM3E3LiP

          [6]

          在線運行程序: https://play.golang.org/p/tTO100HmUX

          [7]

          程序: https://play.golang.org/p/nnXBsR3Uc8

          [8]

          在線運行程序: https://play.golang.org/p/nnXBsR3Uc8

          [9]

          在線運行程序: https://play.golang.org/p/vURnImw4_9

          [10]

          在線運行程序: https://play.golang.org/p/gLyHMd2iie

          [11]

          在線運行程序: https://play.golang.org/p/Xy5wW9YZMJ

          [12]

          在線運行程序: https://play.golang.org/p/ybXLf5o_lA

          [13]

          在線運行程序: https://play.golang.org/p/sTe7i1qAng

          [14]

          github: https://github.com/golangbot/methods

          [15]

          接口 - I: https://studygolang.com/articles/12266

          [16]

          Nick Coghlan: https://golangbot.com/about/

          [17]

          MDGSF: https://github.com/MDGSF

          [18]

          rxcai: https://github.com/rxcai

          [19]

          GCTT: https://github.com/studygolang/GCTT

          [20]

          Go 中文網(wǎng): https://studygolang.com/



          推薦閱讀


          福利

          我為大家整理了一份從入門到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。關(guān)注公眾號 「polarisxu」,回復(fù) ebook 獲取;還可以回復(fù)「進(jìn)群」,和數(shù)萬 Gopher 交流學(xué)習(xí)。

          瀏覽 26
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲AV永久无码国产精品国产 | 日韩AV在线 东京热 | 欧美日韩色图 | 毛片网站大全免费在线观看 | 91另类视频 |