1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 9641字,需瀏覽 20分鐘

         ·

        2020-12-17 02:14

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

        歡迎來(lái)到 Golang 系列教程[1] 的第 17 個(gè)教程。

        什么是方法?

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

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

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

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

        方法示例

        讓我們來(lái)編寫一個(gè)簡(jiǎn)單的小程序,它會(huì)在結(jié)構(gòu)體類型上創(chuàng)建一個(gè)方法并調(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()?方法
        }

        在線運(yùn)行程序[2]

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

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

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

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

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

        package?main

        import?(
        ????"fmt"
        )

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

        /*
        displaySalary()方法被轉(zhuǎn)化為一個(gè)函數(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)
        }

        在線運(yùn)行程序[3]

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

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

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

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

        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())
        }

        在線運(yùn)行程序[5]

        該程序輸出:

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

        上面方法的屬性被使用在接口中。我們將在接下來(lái)的教程中討論這個(gè)問(wèn)題。

        指針接收器與值接收器

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

        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)
        }

        在線運(yùn)行程序[6]

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

        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) 來(lái)調(diào)用 changeAge 方法。由于 changeAge 方法有一個(gè)指針接收器,所以我們使用 (&e) 來(lái)調(diào)用這個(gè)方法。其實(shí)沒(méi)有這個(gè)必要,Go語(yǔ)言讓我們可以直接使用 e.changeAge(51)。e.changeAge(51) 會(huì)自動(dòng)被Go語(yǔ)言解釋為 (&e).changeAge(51)。

        下面的程序[7]重寫了,使用 e.changeAge(51) 來(lái)代替 (&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)
        }

        在線運(yùn)行程序[8]

        那么什么時(shí)候使用指針接收器,什么時(shí)候使用值接收器?

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

        指針接收器也可以被使用在如下場(chǎng)景:當(dāng)拷貝一個(gè)結(jié)構(gòu)體的代價(jià)過(guò)于昂貴時(shí)。考慮下一個(gè)結(jié)構(gòu)體有很多的字段。在方法內(nèi)使用這個(gè)結(jié)構(gòu)體做為值接收器需要拷貝整個(gè)結(jié)構(gòu)體,這是很昂貴的。在這種情況下使用指針接收器,結(jié)構(gòu)體不會(huì)被拷貝,只會(huì)傳遞一個(gè)指針到方法內(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()?//訪問(wèn)?address?結(jié)構(gòu)體的?fullAddress?方法
        }

        在線運(yùn)行程序[9]

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

        Full?address:?Los?Angeles,?California

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

        這個(gè)話題很多Go語(yǔ)言新手都弄不明白。我會(huì)盡量講清楚。

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

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

        讓我們通過(guò)一個(gè)例子來(lái)理解這一點(diǎn)。

        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()//通過(guò)指針調(diào)用值接收器
        }

        在線運(yùn)行程序[10]

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

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

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

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

        該程序?qū)?huì)輸出:

        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()//使用值來(lái)調(diào)用指針接收器
        }

        在線運(yùn)行程序[11]

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

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

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

        在第 35 行,我們通過(guò)值接收器 r 來(lái)調(diào)用有指針接收器的方法 perimeter。這是被允許的,為了方便Go語(yǔ)言把代碼 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)體類型上定義方法,但是有一個(gè)問(wèn)題。為了在一個(gè)類型上定義一個(gè)方法,方法的接收器類型定義和方法的定義應(yīng)該在同一個(gè)包中。到目前為止,我們定義的所有結(jié)構(gòu)體和結(jié)構(gòu)體上的方法都是在同一個(gè) main 包中,因此它們是可以運(yùn)行的。

        package?main

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

        func?main()?{

        }

        在線運(yùn)行程序[12]

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

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

        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)
        }

        在線運(yùn)行程序[13]

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

        該程序?qū)?huì)打印出 Sum is 15。

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

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

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

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


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

        作者:Nick Coghlan[16]譯者:MDGSF[17]校對(duì):rxcai[18]

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

        參考資料

        [1]

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

        [2]

        在線運(yùn)行程序: https://play.golang.org/p/rRsI_sWAOZ

        [3]

        在線運(yùn)行程序: https://play.golang.org/p/dFwObgCUU0

        [4]

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

        [5]

        在線運(yùn)行程序: https://play.golang.org/p/0hDM3E3LiP

        [6]

        在線運(yùn)行程序: https://play.golang.org/p/tTO100HmUX

        [7]

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

        [8]

        在線運(yùn)行程序: https://play.golang.org/p/nnXBsR3Uc8

        [9]

        在線運(yùn)行程序: https://play.golang.org/p/vURnImw4_9

        [10]

        在線運(yùn)行程序: https://play.golang.org/p/gLyHMd2iie

        [11]

        在線運(yùn)行程序: https://play.golang.org/p/Xy5wW9YZMJ

        [12]

        在線運(yùn)行程序: https://play.golang.org/p/ybXLf5o_lA

        [13]

        在線運(yùn)行程序: 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)注公眾號(hào) 「polarisxu」,回復(fù) ebook 獲?。贿€可以回復(fù)「進(jìn)群」,和數(shù)萬(wàn) Gopher 交流學(xué)習(xí)。

        瀏覽 26
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            色老板导航 | 天堂一区二区 | 久久99精品久久久久久园产越南 | 色情A级成人片免费网站主播视频 | 国产无码99 | 日韩高清无码18 | 免费看片18 | 韩国一区二区无码视频 | 成人网站欧美 | 午夜亚洲AⅤ无码高潮片苍井空 |