zoukankan      html  css  js  c++  java
  • 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:
        }
    }
    
  • 相关阅读:
    存储过程分页
    SQL內置Function游标函数
    SQL 2000中的触发器使用
    使用.NET自带的功能制作简单的注册码
    在ASP.NET里轻松实现缩略图
    推荐几个用得上且免费的 .NET控件
    SQL內置Function日期和时间函数
    常用的asp代碼和javascript代碼
    SQL內置Function元数据函数
    數據庫中代@@的參數說明
  • 原文地址:https://www.cnblogs.com/heych/p/12579558.html
Copyright © 2011-2022 走看看