8.方法#

方法是为特定类型的定义的,方法和函数类似,只能由该类型调用的函数。

定义:定义一个方法必须要添加一个接收者函数,接收者必须是自定义的类型- 调用:调用方法通过自定义类型的对象.方法名进行调用,在调用过程中对象传递给方法的接收者(值类型,拷贝) 指针接收者:当使用结构体指针调用接收者方法时,go编译器会自动将指针对象解引用。当使用结构体对象调用方法时,go编译器会自动将值对象取引用为指针调用方法。该使用值接收者还是指针接收者,取决于是否现需要修改原始结构体,若不需要修改则使用值,若需要修改则使用指针。若存在指针接收者,则所有方法是用指针接收者。对于接收者为指针类型的方法,需要注意在运行时若接收者为nil会发生错误。那么需要初始化。

方法值/方法表达式: 方法也可以赋值给变量,存储在数组,切片,映射中,也可以作为参数传递给函数,也可以作为返回值进行返回。方法有两种,一种使用对象/对象指针调用的方法值。另外一种是有类型/类型调用的方法表达式。

在方法表达式赋值时若方法接收者为值类型,则在赋值时会将值类型拷贝(若调用为指针则自动解引用拷贝)

8.1方法定义和获取#

定义一个类型

type Dog struct{
    Name string
}

定义一个方法来调用,在call函数名前加括号定义类型和变量值,如下

func (dog Dog)Call(){
    fmt.Printf("%s:call",dog.Name)
}

这样意思是只有dog类型的Dog才能调用

而后在调用的时候,直接调用

func main(){
    dog := Dog{"大白"}
    dog.Call()
}

在Call方法func (dog Dog)Call(){fmt.Printf("%s:call",dog.Name)}中,在调用的时候,外面(dog Dog)中的dog会传递给Dog.调用call方法

如下:

package main
import "fmt"
type Dog struct{
    Name string
}
func (dog Dog)Call(){
    fmt.Printf("%s:call",dog.Name)
}
func main(){
    dog := Dog{"大白"}
    dog.Call()
}

运行

[root@linuxea.com /opt/Golang/work5]# go run ffa.go
大白:call

8.2方法修改#

我们修改上面定义的方法

    dog.Name = "雪子"
    dog.Call()

如下

package main
import "fmt"
type Dog struct{
    Name string
}
func (dog Dog)Call(){
    fmt.Printf("%s:call\n",dog.Name)
}
func main(){
    dog := Dog{"大白"}
    dog.Call()

    dog.Name = "雪子"
    dog.Call()
}

运行

[root@linuxea.com /opt/Golang/work5]# go run ffa.go
大白:call
雪子:call

如果要在包外调用,就需要写一个函数能够在包外调用。我们先看普通的调用,如下:

func (dog Dog) Setname(name string){
    dog.Name = name
}
func main(){
    dog.Name = "雪子"
    dog.Call()

    dog.Setname("ss")
}

运行

[root@linuxea.com /opt/Golang/work5]# go run ffa.go
大白:call
雪子:call
雪子:call

你会发现这样的修改是没有作用, 这是因为结构体是值类型,调用方法的时候,只是复制了一份,前面和后面的dog是没有关系的。

如果要进行修改,就要修改成指针的接收方法。

func (dog *Dog) PsetName(name string) {
    dog.Name = name
}

这样一来调用的时候就需要获取他的指针调用,取地址,如下

    (&dog).PsetName("Psetname")
    dog.Call()

这里也可以直接使用dog.PsetName("Psetname"),因为PsetName函数中的方法*Dog是指针,会自动取引用。这是语法糖会自动进行转换。

  • 自动取引用和解应用只会发生在接收者,接收者参数才会,而函数参数是不会的,函数需要手动解引用

如下:

package main
import "fmt"
type Dog struct{
    Name string
}
func (dog Dog)Call(){
    fmt.Printf("%s:call\n",dog.Name)
}
func (dog Dog) Setname(name string){
    dog.Name = name
}
func (dog *Dog) PsetName(name string) {
    dog.Name = name
}
func main(){
    dog := Dog{"大白"}
    dog.Call()

    dog.Name = "雪子"
    dog.Call()

    dog.Setname("ss")
    dog.Call()

    (*&dog).PsetName("Psetname")
    dog.Call()
}

运行

[root@linuxea.com /opt/Golang/work5]# go run ffa.go
大白:call
雪子:call
雪子:call
Psetname:call

如果指针要调用值就可以使用*pog,如下

    pdog := &Dog{"pdog"}
    (*pdog).Call()

这里也可以直接使用pdog.Call(),调用者是一个指针类型,方法接收者是值类型,会进行自动解引用。 而(*pdog).Call()是解引用

  • 自动取引用和解应用只会发生在接收者,接收者参数才会,而函数参数是不会的,函数需要手动解引用

运行

[root@linuxea.com /opt/Golang/work5]# go run ffa.go
大白:call
雪子:call
雪子:call
Psetname:call
pdog:call

在或者这样

    pdog.PsetName("pdog.PsetName")
    (*pdog).Call()

代码块如下:

package main
import "fmt"
type Dog struct{
    Name string
}
func (dog Dog)Call(){
    fmt.Printf("%s:call\n",dog.Name)
}
func (dog Dog) Setname(name string){
    dog.Name = name
}
func (dog *Dog) PsetName(name string) {
    dog.Name = name
}
func main(){
    dog := Dog{"大白"}
    dog.Call()

    dog.Name = "雪子"
    dog.Call()

    dog.Setname("ss")
    dog.Call()

    (*&dog).PsetName("Psetname")
    dog.Call()

    pdog := &Dog{"pdog"}
    (*pdog).Call()
}

运行

[root@linuxea.com /opt/Golang/work5]# go run ffa.go
大白:call
雪子:call
雪子:call
Psetname:call
pdog:call
pdog.PsetName:call

8.3命名方法嵌入#

以user和address为例

package main
import "fmt"
type Address struct {
    Region string
    Street string
    No     string
    Name string
}
type User struct {
    ID int
    Name string
    Addr Address
}

如下:

func (addr Address)Addr()string{
    return fmt.Sprintf("%s-%s-%s-%s",addr.Region,addr.Street,addr.No,addr.Name)
}

user方法中,addr调用的是addr的方法,也就是上面写的这个方法

func (user User)User()string{
    return fmt.Sprintf("%d-%s-%s",user.ID,user.Name,user.Addr.Addr())
}

而后我们调用

func main(){
    var u User = User{
        ID:1,
        Name:"mark",
        Addr: Address{
            Region:"北京市",
            Street:"烤鸭",
            No:"123",
            Name:"mark2",
        },
    }
    fmt.Println(u)
}

代码块如下:

package main
import "fmt"
type Address struct {
    Region string
    Street string
    No     string
    Name string
}
type User struct {
    ID int
    Name string
    Addr Address
}
func (addr Address)Addr()string{
    return fmt.Sprintf("%s-%s-%s-%s",addr.Region,addr.Street,addr.No,addr.Name)
}
func (user User)User()string{
    return fmt.Sprintf("%d-%s-%s",user.ID,user.Name,user.Addr.Addr())
}
func main(){
    var u User = User{
        ID:1,
        Name:"mark",
        Addr: Address{
            Region:"北京市",
            Street:"烤鸭",
            No:"123",
            Name:"mark2",
        },
    }
    fmt.Println(u)
}

运行

[root@linuxea.com /opt/Golang/work5]# go run mmqr.go
{1 mark {北京市 烤鸭 123 mark2}}
fmt.Println(u.User())
fmt.Println(u.Addr.Addr())

如下:

[root@linuxea.com /opt/Golang/work5]# go run mmqr.go
{1 mark {北京市 烤鸭 123 mark2}}
1-mark-北京市-烤鸭-123-mark2
北京市-烤鸭-123-mark2

8.3.1.String函数#

我们还可以将两种方法命名为String,在打印方法的时候,go会自动调用string方法返回字符串进行打印,如下:

这个string方法是fmt.Println中调用的,print发现有这方法就进行调用,如果没有会将结构体属性并结成大括号的方式打印

func (addr Address)String()string{
    return fmt.Sprintf("%s-%s-%s-%s",addr.Region,addr.Street,addr.No,addr.Name)
}
func (user User)String()string{
    return fmt.Sprintf("%d-%s-%s",user.ID,user.Name,user.Addr)
}

代码块如下:

package main
import "fmt"
type Address struct {
    Region string
    Street string
    No     string
    Name string
}
type User struct {
    ID int
    Name string
    Addr Address
}
func (addr Address)String()string{
    return fmt.Sprintf("%s-%s-%s-%s",addr.Region,addr.Street,addr.No,addr.Name)
}
func (user User)String()string{
    return fmt.Sprintf("%d-%s-%s",user.ID,user.Name,user.Addr)
}
func main(){
    var u User = User{
        ID:1,
        Name:"mark",
        Addr: Address{
            Region:"北京市",
            Street:"烤鸭",
            No:"123",
            Name:"mark2",
        },
    }
    fmt.Println(u)
    fmt.Println(u.Addr)
}

如下:

[root@linuxea.com /opt/Golang/work5]# go run mmqr1.go
1-mark-北京市-烤鸭-123-mark2
北京市-烤鸭-123-mark2

8.4匿名方法嵌入#

若结构体匿名嵌入带有方法的结构体时,则在外部结构体可以调用嵌入结构体的方法,并且在调用时只有嵌入的字段会传递给嵌入结构体方法的接收者。

当被嵌入结构体与嵌入结构体据有相同名称的方法时,则使用对象.方法名调用被嵌入结构体方法。若想要调用嵌入结构体方法,则使用对象.嵌入结构体名.方法

定义结构体中有id和name,分别定义get和set,set使用指针类型

type User struct{
    id int
    name string
}

getid

func (user User)Getid() int {
    return user.id
}

getname

func (user User)Getname() string{
    return user.name
}

setid,使用指针接收者

func (user *User) SetID(id int){
    user.id = id
}

setname,使用指针接收者

func (user *User) Setname(name string){
    user.name = name
}
type Employee struct{
    User
    Salary float64
}

而后在main调用

func main(){
    var mm Employee =Employee{
        User :{
            id:1,
            name:"mark",
        },
        Salary:10000.1,
    }
}

8.4.1通过方法获取#

如果此时获取user对象,就可以使用getname

    fmt.Println(mm.User.Getname())

也可以写成这样

    fmt.Println(mm.Getname())

代码块如下:

package main
import "fmt"
type User struct{
    id int
    name string
}
func (user User)Getid() int {
    return user.id
}
func (user User)Getname() string{
    return user.name
}
func (user *User) SetID(id int){
    user.id = id
}
func (user *User) Setname(name string){
    user.name = name
}
type Employee struct{
    User
    Salary float64
}
func main(){
    var mm Employee =Employee{
        User:User{
            id:1,
            name:"mark",
        },
        Salary:10000.1,
    }
    fmt.Println(mm.User.Getname())
    fmt.Println(mm.Getname())
}

如下:

[root@linuxea.com /opt/Golang/work5]# go run nnqr3.go
mark

8.4.2通过方法修改#

这里的方法在上面已经定义并且使用指针

    mm.Setname("marksugar")
    fmt.Println(mm.User.Getname())

代码块

package main
import "fmt"
type User struct{
    id int
    name string
}
func (user User)Getid() int {
    return user.id
}
func (user User)Getname() string{
    return user.name
}
func (user *User) SetID(id int){
    user.id = id
}
func (user *User) Setname(name string){
    user.name = name
}
type Employee struct{
    User
    Salary float64
}
func main(){
    var mm Employee =Employee{
        User:User{
            id:1,
            name:"mark",
        },
        Salary:10000.1,
    }
    fmt.Println(mm.User.Getname())

    mm.Setname("marksugar")
    fmt.Println(mm.Getname())

}

mm.Getname()与mm.User.Getname()是一样的,getname自动解引用,这里是函数前的参数-接收者

  • 值或者指针调用,在go中中山自动转换,这种方式就是解引用和取引用,并且解引用和取引用只会发生在接收者这块

运行

[root@linuxea.com /opt/Golang/work5]# go run nnqr3.go
mark
marksugar

假设Employee结构体中加上name,如下

type Employee struct{
    User
    Salary float64
    name string
}

使用Getname调用是否为空?

func main(){
    var mm Employee =Employee{
        User:User{
            id:1,
            name:"mark",
        },
        Salary:10000.1,
    }
    fmt.Println(mm.User.Getname())
    mm.Setname("marksugar")
    fmt.Println(mm.Getname())
    fmt.Println(mm.name)
}

运行

[root@linuxea.com /opt/Golang/work5]# go run nnqr3.go
mark
marksugar

: 只有在使用mm.name调用的时候才会是空的,使用Getname方法调用的时候,实际上调用的是User本身的name。但是当使用Getname的时候,Employee是空的,就会找到User中的name

我们在定义一个Employee的方法,名称也是Getname,如下:

func (employee Employee)Getname()string{
    return employee.name
}

并且初始化name:"justin"

    var mm Employee =Employee{
        User:User{
            id:1,
            name:"mark",
        },
        Salary:10000.1,
        name:"justin",
    }

而后在进行访问

    fmt.Println(mm.User.Getname())
    fmt.Println(mm.Getname())

    mm.Setname("marksugar")
    fmt.Println(mm.Getname())
    fmt.Println(mm.name)

代码块如下:

package main
import "fmt"
type User struct{
    id int
    name string
}
func (user User)Getid() int {
    return user.id
}
func (user User)Getname() string{
    return user.name
}
func (user *User) SetID(id int){
    user.id = id
}
func (user *User) Setname(name string){
    user.name = name
}
type Employee struct{
    User
    Salary float64
    name string
}
func (employee Employee)Getname()string{
    return employee.name
}
func main(){
    var mm Employee =Employee{
        User:User{
            id:1,
            name:"mark",
        },
        Salary:10000.1,
        name:"justin",
    }
    fmt.Println(mm.User.Getname())
    fmt.Println(mm.Getname())

    mm.Setname("marksugar")
    fmt.Println(mm.Getname())
    fmt.Println(mm.name)

}

运行

[root@linuxea.com /opt/Golang/work5]# go run nnqr3.go
mark
justin
justin
justin

如上结果中,mm.User.Getname()是调用的是User的Getname,而mm.Getname()调用的是Employee的Getname,而Setname也是User的Setname,打印mm.Getname()就只会是justin

那我们在写一个方法是Employee的Setname

func (employee *Employee) Setname(){
    employee.name = name
}

其他不变,代码块如下:

package main
import "fmt"
type User struct{
    id int
    name string
}
func (user User)Getid() int {
    return user.id
}
func (user User)Getname() string{
    return user.name
}
func (user *User) SetID(id int){
    user.id = id
}
func (user *User) Setname(name string){
    user.name = name
}
type Employee struct{
    User
    Salary float64
    name string
}
func (employee Employee)Getname()string{
    return employee.name
}
func (employee *Employee) Setname(name string){
    employee.name = name
}
func main(){
    var mm Employee =Employee{
        User:User{
            id:1,
            name:"mark",
        },
        Salary:10000.1,
        name:"justin",
    }
    fmt.Println(mm.User.Getname())
    fmt.Println(mm.Getname())

    mm.Setname("marksugar")
    fmt.Println(mm.Getname())
    fmt.Println(mm.name)
}

运行

[root@linuxea.com /opt/Golang/work5]# go run nnqr3.go
mark
justin
marksugar
marksugar

那么现在,mm.Getname()获取的就是Employee的Getname,mm.Setname("marksugar")修改的也是Employee的Setname的name

这便是匿名结构体方法的调用。

8.5方法值和方法表达式#

方法值/方法表达式: 方法也可以赋值给变量,存储在数组,切片,映射中,也可以作为参数传递给函数,也可以作为返回值进行返回。方法有两种,一种使用对象/对象指针调用的方法值。另外一种是有类型/类型调用的方法表达式。

8.5.1方法值#

我们定义一个dog的结构体

type Dog struct{
    name string
}

在定义一个Call方法

func (dog Dog) Call(){
    fmt.Printf("%s:wangwang\n",dog.name)
}

在定义一个setname修改

func (dog *Dog) Setname(name string){
    dog.name = name
}

我们可将dog方法赋值给一个变量

func main(){
    dog := Dog{"二狗"}
    gg := dog.Call
    fmt.Printf("%T:\n",gg)
    gg()
}

运行

[root@linuxea.com /opt/Golang/work5]# go run dog.go
func():
二狗:wangwang

我们使用setname修改

dog.Setname("三狗")

如下:

func main(){
    dog := Dog{"二狗"}
    gg := dog.Call
    dog.Setname("三狗")
    fmt.Printf("%T:\n",gg)
    gg()

    dog.Setname("三狗")
    gg1 := dog.Call
    gg1()
}

这里必须重新赋值dog.Call到gg1

dog.Setname("三狗") gg1 := dog.Call gg1()

因为在gg := dog.Call中赋值后dog.Call已经复制到gg中,gg是值类型,setname后如果直接使用gg()是使用之前的,并且不会发生改变的。这里需要使用dog.Call重新赋值给gg1而后调用

  • 如果多次调用修改gg或者gg1,是不会影响到方法值的。值接收者方法会将对象进行拷贝

运行

[root@linuxea.com /opt/Golang/work5]# go run dog.go
func():
二狗:wangwang
三狗:wangwang

8.5.2方法值指针#

如果不是用指针,是这样

package main
import "fmt"
type Dog struct{
    name string
}
func (dog Dog) Call(){
    fmt.Printf("%s:wangwang\n",dog.name)
}
func (dog *Dog) Setname(name string){
    dog.name = name
}
func main(){
    dog := Dog{"二狗"}
    gg := dog.Call
    fmt.Printf("%T:\n",gg)
    gg()

    dog.Setname("三狗")
    dog.Call()
    gg()
}

运行

[root@linuxea.com /opt/Golang/work5]# go run dog.go
func():
二狗:wangwang
三狗:wangwang
二狗:wangwang

但是,如果是指针方法,就会不一样了,如下:

func (dog *Dog) Call(){
    fmt.Printf("%s:wangwang\n",dog.name)
}
func (dog *Dog) Setname(name string){
    dog.name = name
}

调用的方式我们可以这样

func main(){
    dog := Dog{"二狗"}
    gg := dog.Call
    fmt.Printf("%T:\n",gg)
    gg()

    dog.Setname("三狗")
    dog.Call()
    gg()
}

代码块

package main
import "fmt"
type Dog struct{
    name string
}
func (dog *Dog) Call(){
    fmt.Printf("%s:wangwang\n",dog.name)
}
func (dog *Dog) Setname(name string){
    dog.name = name
}
func main(){
    dog := Dog{"二狗"}
    gg := dog.Call
    fmt.Printf("%T:\n",gg)
    gg()

    dog.Setname("三狗")
    dog.Call()
    gg()
}

运行

[root@linuxea.com /opt/Golang/work5]# go run dog.go
func():
二狗:wangwang
三狗:wangwang
三狗:wangwang

由此可见,我们使用了指针后,gg的值也变成了三狗:wangwang.见上述代码

8.5.3方法表达式#

还是以上述的示例,如下

package main
import "fmt"
type Dog struct {
    name string
}
func (dog Dog) Call(){
    fmt.Printf("%s:旺旺\n",dog.name)
}
func (dog *Dog) Setname(name string){
    dog.name = name
}

方法表达式就是用结构体的名字去访问对象,如下

func main(){
    m1 := Dog.Call
    m2 := (*Dog).Setname
    fmt.Printf("%T\n",m1)
    fmt.Printf("%T\n",m2)
}

运行

[root@linuxea.com /opt/Golang/work5]# go run ffbds.go
func(main.Dog)
func(*main.Dog, string)

对于方法表达式来说,调用的时候,必须传递一个对象进去

    dog := Dog("小灰")
    m1(dog)

运行

[root@linuxea.com /opt/Golang/work5]# go run ffbds.go
func(main.Dog)
func(*main.Dog, string)
小灰:旺旺

使用setname修改名称。我们赋值m2,如下:

m2 = (*Dog).Setname
m2(&dog,"小黑")
m1(dog)

如下

package main
import "fmt"
type Dog struct {
    name string
}
func (dog Dog) Call(){
    fmt.Printf("%s:旺旺\n",dog.name)
}
func (dog *Dog) Setname(name string){
    dog.name = name
}
func main(){
    m1 := Dog.Call
    m2 := (*Dog).Setname
    fmt.Printf("%T\n",m1)
    fmt.Printf("%T\n",m2)

    dog := Dog{"小灰"}
    m1(dog)


    m2 = (*Dog).Setname
    m2(&dog,"小黑")
    m1(dog)
}

运行

[root@linuxea.com /opt/Golang/work5]# go run ffbds.go
func(main.Dog)
func(*main.Dog, string)
小灰:旺旺
小黑:旺旺

值对象可以将指针的方法赋值给一个变量,但定义的是指针接收者就不能用值对象调用。这是因为go会自动生成一个根据值接收者方法会生成一个指针接收者方法。如下

package main
import "fmt"
type Dog struct {
    name string
}
func (dog Dog) Call(){
    fmt.Printf("%s:在叫\n",dog.name)
}
func main(){
    m1 := (*Dog).Call
    dog := Dog{"小黄"}
    m1(&dog)
}

也可以写成这样

(*Dog).Call(&Dog{"小黄"})

运行

[root@linuxea.com /opt/Golang/work5]# go run ff1.go
小黄:在叫

9.sort#

对所有切片排序

9.1切片排序#

创建一个包含数组的切片

list := [][2]int{{1,3},{5,9},{4,7},{6,2},{0,8}}

排序的规则是使用每个元素的第二个元素(索引为1)比较大小

使用sort.Slice进行排序

Slice传入list,而后是一个匿名函数,索引为i和j,返回一个bool ,索引i和j是需要自己进行比较。如下

list[i][1] > list[j][1]list中索引i第二个元素1进行比较,那分别就是{1,3},{5,9},{4,7},{6,2},{0,8}中的,3,9,7,2,8进行排序

sort.Slice(list,func(i,j int)bool{
    return list[i][1] > list[j][1]
})

代码块

package main
import (
    "fmt"
    "sort"
)
func main(){
    list := [][2]int{{1,3},{5,9},{4,7},{6,2},{0,8}}
    sort.Slice(list,func(i,j int)bool{
        return list[i][1] > list[j][1]
    })
    fmt.Println(list)
}

运行

[root@linuxea.com /opt/Golang/work5]# go run sort.go
[[5 9] [0 8] [4 7] [1 3] [6 2]]

9.2结构体排序#

我们定义一个结构体,如下

type  User struct {
    Id int
    Name string
    Score float64
}

而后赋值给users,如下:

users :=  []User{{3,"m",6},{2,"a",5},{1,"r",7},{4,"k",8}}

使用sort.Slice进行排序,在返回值上,使用.score排序score,score的值现在是6,5,7,8,排序后的结果是5,6,7,8

    sort.Slice(users,func(i,j int)bool{
        return users[i].Score < users[j].Score
    })

如下:

package main
import (
    "fmt"
    "sort"
)
type  User struct {
    Id int
    Name string
    Score float64
}
func main(){
    users :=  []User{{3,"m",6},{2,"a",5},{1,"r",7},{4,"k",8}}

    sort.Slice(users,func(i,j int)bool{
        return users[i].Score < users[j].Score
    })
    fmt.Println(users)
}

运行

[root@linuxea.com /opt/Golang/work5]# go run sort2.go
[{2 a 5} {3 m 6} {1 r 7} {4 k 8}]

我们还可以对id排序,修改成users[i].Id < users[j].Id即可

package main
import (
    "fmt"
    "sort"
)
type  User struct {
    Id int
    Name string
    Score float64
}
func main(){
    users :=  []User{{3,"m",6},{2,"a",5},{1,"r",7},{4,"k",8}}

    sort.Slice(users,func(i,j int)bool{
        return users[i].Id < users[j].Id
    })
    fmt.Println(users)
}

运行

[root@linuxea.com /opt/Golang/work5]# go run sort2.go
[{1 r 7} {2 a 5} {3 m 6} {4 k 8}]