Go语言之Go 语言方法

Posted heych

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go语言之Go 语言方法相关的知识,希望对你有一定的参考价值。

Go 语言方法

go 语言方法定义

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

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

方法的声明和普通函数的声明类似,只是在函数名称前面多了一个参数,这个参数把这个方法绑定到这个参数对应的类型上。

方法定义
首先声明一个自定义类型Test

type Test struct{}

方法参数 receiver 类型可以是 Test 或 *Test。类型 Test不能是接口或指针。

第一种,定义一个无参数、无返回值的方法

func (t Test) method() {
}
func (t *Test) method() {

}

第二种,定义一个单参数、无返回值的方法

func (t Test) method(i int) {
}
func (t *Test) method(i int) {

}

第三种,定义一个多参数、无返回值的方法

func (t Test) method(x, y int) {

}
func (t *Test) method(x, y int) {

}

第四种,定义一个无参数、单返回值的方法

func (t Test) method() (i int) {
    return
}
func (t *Test) method() (i int) {
    return
}

第五种,定义一个多参数、多返回值的方法

func (t Test) method(x, y int) (z int, err error) {
    return
}
func (t *Test) method(x, y int) (z int, err error) {
    return
}

方法和函数的关系

方法是特殊的函数,定义在某一特定的类型上,通过类型的实例来进行调用,这个实例被叫接收者。

接收者必须有一个显式的名字,这个名字必须在方法中被使用。 接收者类型必须在和方法同样的包中被声明。

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

package main

import (
    "fmt"
)

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

编译错误:

cannot define new methods on non-local type int

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

合法的方法定义如下:

package main

import (
    "fmt"
)

type myInt int

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

func main() {
    var x, y myInt = 3, 6
    x.Add(y)
}

函数与方法的区别

1、对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然。
2、对于方法(如struct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以。

Go 语言方法规则

根据调用者不同,方法分为两种表现形式:方法(method value)、方法表达式(method expression)。

两者都可像普通函数那样赋值和传参,区别在于 方法 (method value)绑定了实例,而方法表达式(method expression)必须显式传参。

直接调用

直接调用,类型 T 和 *T 上的方法集是互相继承的。

package main

import (
    "fmt"
)

type T struct {
    int
}

func (t T) testT() {
    fmt.Println("接受者为 T ")
}

func (t *T) testP() {
    fmt.Println("接受者为 *T ")
}

func main() {
    t1 := T{1}
    fmt.Printf("t1 is : %v
", t1)
    t1.testT()
    t1.testP()

    t2 := &t1
    fmt.Printf("t2 is : %v
", t2)
    t2.testT()
    t2.testP()
}

直接调用,类型 S 包含匿名字段 *T 或 T ,则 S 和 *S 方法集包含 T 和 *T 上的方法集是互相继承的。

package main

import (
    "fmt"
)

type ST struct {
    T
}

type SP struct {
    *T
}

type T struct {
    int
}

func (t T) testT() {
    fmt.Println("类型 S 包含匿名字段 *T 或 T ,则 S 和 *S 方法集包含 T 方法")
}
func (t *T) testP() {
    fmt.Println("类型 S 包含匿名字段 *T 或 T ,则 S 和 *S 方法集包含 *T 方法")
}

func main() {
    st1 := ST{T{1}}
    st2 := &st1
    fmt.Printf("st1 is : %v
", st1)
    st1.testT()
    st1.testP()
    fmt.Printf("st2 is : %v
", st2)
    st2.testT()
    st2.testP()

    sp1 := SP{&T{1}}
    sp2 := &sp1
    fmt.Printf("sp1 is : %v
", sp1)
    sp1.testT()
    sp1.testP()
    fmt.Printf("sp2 is : %v
", sp2)
    sp2.testT()
    sp2.testP()
}

隐式传递调用

接受者隐式传递,类型 T 和 *T 上的方法集是互相继承的。

package main

import (
    "fmt"
)

type T struct {
    string
}

func (t T) testT() {
    fmt.Println("接受者为 T ")
}

func (t *T) testP() {
    fmt.Println("接受者为 *T ")
}

func main() {
    t := T{"oldboy"}
    methodValue1 := t.testT
    methodValue1()
    methodValue2 := (&t).testT
    methodValue2()
    methodValue3 := t.testP
    methodValue3()
    methodValue4 := (&t).testP
    methodValue4()
}

接受者隐式传递,类型 S 包含匿名字段 *T 或 T ,则 S 和 *S 方法集包含 T 和 *T 上的方法集是互相继承的。

package main

import (
    "fmt"
)

type ST struct {
    T
}

type SP struct {
    *T
}

type T struct {
    string
}

func (t T) testT() {
    fmt.Println("类型 S 包含匿名字段 *T 或 T ,则 S 和 *S 方法集包含 T 方法")
}
func (t *T) testP() {
    fmt.Println("类型 S 包含匿名字段 *T 或 T ,则 S 和 *S 方法集包含 *T 方法")
}

func main() {
    st1 := ST{T{"oldboy"}}
    methodValue1 := st1.testT
    methodValue1()
    methodValue2 := (&st1).testT
    methodValue2()
    methodValue3 := st1.testP
    methodValue3()
    methodValue4 := (&st1).testP
    methodValue4()

    sp1 := SP{&T{"oldboy"}}
    methodValue5 := sp1.testT
    methodValue5()
    methodValue6 := (&sp1).testT
    methodValue6()
    methodValue7 := sp1.testP
    methodValue7()
    methodValue8 := (&sp1).testP
    methodValue8()
}

显式传递调用

接受者显示传值,类型 T 的可调用方法集包含接受者为 T 的所有方法,不包含接受者为 *T 的方法。类型 *T 的可调用方法集包含接受者为 *T 或 T 的所有方法集。

package main

import (
    "fmt"
)

type T struct {
    string
}

func (t T) testT() {
    fmt.Println("接受者为 T ")
}

func (t *T) testP() {
    fmt.Println("接受者为 *T ")
}

func main() {
    t := T{"oldboy"}
    expression1 := T.testT
    expression1(t)
    expression2 := (*T).testT
    expression2(&t)

    // expression3 := T.testP
    // expression3(t)
    expression4 := (*T).testP
    expression4(&t)

}

接受者显示传值,类型 S 包含匿名字段 *T ,则 S 和 *S 方法集包含 T 和 *T 上的方法集是互相继承的。
类型 S 包含匿名字段 T ,类型 S 的可调用方法集包含接受者为 T 的所有方法,不包含接受者为 *T 的方法。类型 *S 的可调用方法集包含接受者为 *T 或 T 的所有方法集。

package main

import (
    "fmt"
)

type ST struct {
    T
}

type SP struct {
    *T
}

type T struct {
    string
}

func (t T) testT() {
    fmt.Println("类型 S 包含匿名字段 *T 或 T ,则 S 和 *S 方法集包含 T 方法")
}
func (t *T) testP() {
    fmt.Println("类型 S 包含匿名字段 *T 或 T ,则 S 和 *S 方法集包含 *T 方法")
}

func main() {
    st1 := ST{T{"oldboy"}}
    expression1 := ST.testT
    expression1(st1)
    expression2 := (*ST).testT
    expression2(&st1)
    // expression3 := ST.testP
    // expression3(st1)
    expression4 := (*ST).testP
    expression4(&st1)

    sp1 := SP{&T{"oldboy"}}
    expression5 := SP.testT
    expression5(sp1)
    expression6 := (*SP).testT
    expression6(&sp1)
    expression7 := SP.testP
    expression7(sp1)
    expression8 := (*SP).testP
    expression8(&sp1)
}

Go 语言方法应用

匿名字段

Go语言支持只提供类型,而不写字段名的方式,也就是匿名字段,也称为嵌入字段。

当匿名字段是一个struct的时候,那么这个struct所拥有的全部字段都被隐式地引入了当前定义的这个struct。

Go语言匿名字段可以像字段成员那样访问匿名字段方法,编译器负责查找。

package main

import "fmt"

type Student struct {
    id   int
    name string
}

type Course struct {
    Student
}

func (self *Student) ToString() string {
    return fmt.Sprintf("Student: %p, %v", self, self)
}

func main() {
    c := Course{Student{1, "oldboy"}}
    fmt.Printf("Course: %p
", &c)
    fmt.Println(c.ToString())
}

运行结果:

Class: 0xc0420023e0
User: 0xc0420023e0, &{1 oldboy}

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

继承复用

Go语言中没有继承,但是可以依靠组合来模拟继承和多态。

通过匿名字段,可获得和继承类似的复用能力。依据编译器查找次序,只需在外层定义同名方法,就可以实现。

package main

import "fmt"

type Student struct {
    id   int
    name string
}

type Course struct {
    Student
    title string
}

func (self *Student) ToString() string {
    return fmt.Sprintf("Student: %p, %v", self, self)
}

func (self *Course) ToString() string {
    return fmt.Sprintf("Course: %p, %v", self, self)
}

func main() {
    c := Course{Student{1, "oldboy"}, "Golang"}

    fmt.Println(c.ToString())

    fmt.Println(c.Student.ToString())
}

运行结果:

Course: 0xc04207e060, &{{1 oldboy} Golang}
Student: 0xc04207e060, &{1 oldboy}

自定义ERROR
错误是可以用字符串描述自己的任何东西。 可以由预定义的内建接口类型 error,和其返回字符串的方法 Error 构成。

type error interface {
Error() string
}

当用 fmt 包的多种不同的打印函数输出一个 error 时,会自动的调用该方法。

package main

import (
    "fmt"
    "os"
    "time"
)

type PathError struct {
    path       string
    op         string
    createTime string
    message    string
}

func (p *PathError) Error() string {
    return fmt.Sprintf("path=%s 
op=%s 
createTime=%s 
message=%s", p.path,
        p.op, p.createTime, p.message)
}

func Open(filename string) error {

    file, err := os.Open(filename)
    if err != nil {
        return &PathError{
            path:       filename,
            op:         "read",
            message:    err.Error(),
            createTime: fmt.Sprintf("%v", time.Now()),
        }
    }

    defer file.Close()
    return nil
}

func main() {
    err := Open("/oldboy/golang.go")
    switch v := err.(type) {
    case *PathError:
        fmt.Println("get path error,", v)
    default:
    }
}

以上是关于Go语言之Go 语言方法的主要内容,如果未能解决你的问题,请参考以下文章

Go语言之函数方法

GO语言实战之函数与方法

Go语言的异常处理之errors,panic, recover

Go语言基础之包

Go语言基础之单元测试

GO语言实战之函数与方法