Skip to content

Latest commit

 

History

History
305 lines (234 loc) · 7.66 KB

10.Golang方法(十).md

File metadata and controls

305 lines (234 loc) · 7.66 KB

Golang方法(十)

方法

Go语言中有一个概念和函数极其相似,叫做方法。Go语言的方法其实是作用在接收者(receiver)上的一个函数,接收者是某种非内置类型的变量。方法是与对象实例绑定的一种特殊的函数

接收者类型可以是(几乎)任何类型,不仅仅是结构体类型:任何类型都可以有方法,甚至可以是函数类型,可以是intboolstring或数组的别名类型。但是接收者不能是一个接口类型。

和函数关系

方法和函数的区别是:方法在定义的时候,会在func和方法名之间增加一个参数,这个参数就是接收者,这样我们定义的这个方法就和接收者绑定在了一起,称之为这个接收者的方法。

type Person struct {
    name string
}
func (p Person) getName() string{
    return "the person name is "+p.name
}

留意例子中,func和方法名之间增加的参数(p Person),这个就是接收者。现在我们说,类型person有了一个getName()方法,现在我们看下如何使用它:

func main() {
    p := Person{name: "张三"}
    fmt.Println(p.getName())
}

Go语言里有两种类型的接收者:

  • 值接收者
  • 指针接收者

我们上面的例子中,就是使用值类型接收者的示例。

使用值类型接收者定义的方法,在调用的时候,使用的其实是值接收者的一个副本,所以对该值的任何操作,不会影响原来的类型变量。

package main

import "fmt"

type Person struct {
    name string
}

func (p Person) getName() string {
    return "the person name is " + p.name
}

func (p Person) changeName() string {
    p.name = "zhaosi"
    return p.name
}

func main() {
    p := Person{name: "张三"}
    fmt.Println(p.getName())
    fmt.Println(p.changeName())
    fmt.Println(p.getName())
}

执行结果:

the person name is 张三
zhaosi
the person name is 张三

以上的例子,打印出来的值还是张三,对其进行的修改无效。如果我们使用一个指针作为接收者,那么就会其作用了,因为指针接收者传递的是一个指向原值指针的副本,指针的副本,指向的还是原来类型的值,所以修改时,同时也会影响原来类型变量的值:

package main

import "fmt"

type Person struct {
    name string
}

func (p Person) getName() string {
    return "the person name is " + p.name
}
// 只是改了这里,改成了Person指针的方法
func (p *Person) changeName() string {
    p.name = "zhaosi"
    return p.name
}

func main() {
    p := Person{name: "张三"}
    fmt.Println(p.getName())
    // 或者这样写也可以fmt.Println((&p).changeName()),方法的调用,既可以使用值,也可以使用指针,我们不必要严格的遵守这些,Go语言编译器会帮我们进行自动转义的,这大大方便了我们开发者。
    fmt.Println(p.changeName())  
    fmt.Println(p.getName())
}

执行结果:

the person name is 张三
zhaosi
the person name is zhaosi

从上面可以看到只需要改动一下,变成指针的接收者,就可以完成了修改。

在调用方法的时候,传递的接收者本质上都是副本,只不过一个是这个值副本,一是指向这个值指针的副本。指针具有指向原有值的特性,所以修改了指针指向的值,也就修改了原有的值。我们可以简单的理解为值接收者使用的是值的副本来调用方法,而指针接收者使用实际的值来调用方法。

Go中,(接收者)类型关联的方法不写在类型结构里面,就像类那样;耦合更加宽松;类型和方法之间的关联由接收者来建立。 方法没有和数据定义(结构体)混在一起:它们是正交的类型;表示(数据)和行为(方法)是独立的。

注意:Go语言不允许为简单的内置类型添加方法,所以下面定义的方法是非法的。

package main

import (
    "fmt"
)

func Add(a, b int) { //函数合法
    fmt.Println(a + b)
}

func (a int) Add(b int) { //方法非法!不能是内置数据类型
    fmt.Println(a + b)
}

这个时候我们需要用Go语言的type,来临时定义一个和int具有同样功能的类型。这个类型不能看成是int类型的别名,它们属于不同的类型,不能直接相互赋值。

修改后合法的方法定义如下:

package main

import (
    "fmt"
)

type myInt int

func Add(a, b int) { //函数
    fmt.Println(a + b)
}

func (a myInt) Add(b myInt) { //方法
    fmt.Println(a + b)
}

func main() {
    a, b := 3, 4
    var aa, bb myInt = 3, 4
    Add(a, b)
    aa.Add(bb)
}

上面的表达式aa.Add称作选择子(selector)它为接收者aa选择合适的Add方法。

“类的”方法

Go语言不像其它面相对象语言一样可以写个类,然后在类里面写一堆方法,但其实Go语言的方法很巧妙的实现了这种效果:我们只需要在普通函数前面加个接受者(receiver,写在函数名前面的括号里面),这样编译器就知道这个函数(方法)属于哪个struct了。例如:

package main

import (
    "fmt"
)

type A struct {
    Name string
}

func (a A) foo() { //接收者写在函数名前面的括号里面
    fmt.Println("foo")
}

func main() {
    a := A{}
    a.foo() //foo
}

最后插一点知识,上面再讲方法的时候用到了type myInt int,这是干什么用的呢?这里是类型等价定义,相当于类型重命名,只要这样定以后myInt类型就与int等价。 那type到底有哪些作用呢? 这里总结一下:

  • 定义结构体
type Person struct {
    name string
    age int
}
  • 类型等价定义,相当于类型重命名
type name string

func main() {
    var myname name = "taozs" //其实就是字符串类型
    l := []byte(myname)       //字符串转字节数组
    fmt.Println(len(l))       //字节长度
}
  • 定义接口
type Personer interface {
    Run()
    Name() string
}
  • 定义函数类型
type handler func(name string) int
针对这个函数类型可以再定义方法func (h handler) add(name string) int {
    return h(name) + 10
}

方法的继承

继承是通过匿名字段的方式实现。

type Person struct {
    name string
    age  int
}

func (person Person) Print() {
    fmt.Println("print :", person)
}

type Student struct {
    Person // 继承了Person里面的成员和方法
    byte
    class string
}

func main() {
    s := Student{Person{"hello", 1}, 'm', "幼儿园"}
    // Print()方法是Person类的,但是Student继承了Person类,也就有了Print方法
    s.Print()
}

执行结果:

print : {hello 1}

方法的重写

type Person struct {
    name string
    age  int
}

func (person Person) Print() {
    fmt.Println("print :", person)
}

type Student struct {
    Person // 只有类型,没有名字就叫做匿名字段,继承了Person里面的成员
    byte
    class string
}

// 方法名一样,就是重写
func (student Student) Print() {
    fmt.Println("print :", student)
}

func main() {
    s := Student{Person{"hello", 1}, 'm', "幼儿园"}
    s.Print()
}

执行结果:

print : {{hello 1} 109 幼儿园}