zoukankan      html  css  js  c++  java
  • golang 函数和方法

    由于自己是搞python开发的,所以在学习go时,当看到函数和方法时,顿时还是挺蒙的,因为在python中并没有明显的区别,但是在go中却是两个完全不同的东西。在官方的解释中,方法是包含了接收者的函数。

    定义

    函数的格式是固定的
    Func + 函数名 + 参数 + 返回值(可选) + 函数体

    Func main( a, b int) (int) {
    }
    

    而方法会在方法在func关键字后是接收者而不是函数名,接收者可以是自己定义的一个类型,这个类型可以是struct,interface,甚至我们可以重定义基本数据类型。不过需要注意的是接收者是指针和非指针的区别,我们可以看到当接收者为指针式,我们可以通过方法改变该接收者的属性,但是非指针类型缺做不到。

    func (p myint) mysquare() int {  
        p = p * p  
        fmt.Println("mysquare p = ", p)  
        return 0  
    }  
    

     函数

    函数的值(闭包)
    在Go中,函数被看作第一类值(first-class values):函数像其他值一样,拥有类型,可以被赋值给其他变量,传递给函数,从函数返回。函数类型的零值是nil。调用值为nil的函数值会引起panic错误:
    var f func(int) intf(3) // 此处f的值为nil, 会引起panic错误
    函数值不仅仅是一串代码,还记录了状态。Go使用闭包(closures)技术实现函数值,Go程序员也把函数值叫做闭包。我们看个闭包的例子

    func f1(limit int) (func(v int) bool) {
        //编译器发现limit逃逸了,自动在堆上分配
        return func (limit int) bool { return v>limit} 
    }
    func main() {
        closure := f1(5)
        fmt.Printf("%v
    ", closure(1)) //false
        fmt.Printf("%v
    ", closure(5)) //false
        fmt.Printf("%v
    ", closure(10)) //true
    }

    在这个例子中,f1函数传入limit参数,返回一个闭包,闭包接受一个参数v,判断v是否大于之前设置进去的limit。

    2 可变参数列表

    在go中函数提供可变参数,对那些封装不确定参数个数是一个不错的选择。声明如下
    func 函数名(变量名...类型) 返回值

    package main
    import (
        "fmt"
    )
    
    func f1(name string, vals... int) (sum int) {
        for _, v := range vals {
            sum += v
        }
        sum += len(name)
        return
    }
    func main() {
        fmt.Printf("%d
    ", f1("abc", 1,2,3,4 )) //13
    }
    

    在函数中提供延迟执行即 defer
    包含defer语句的函数执行完毕后(例如return、panic),释放堆栈前会调用被声明defer的语句,常用于释放资源、记录函数执行耗时等,有一下几个特点:
    当defer被声明时,其参数就会被实时解析
    执行顺序和声明顺序相反
    defer可以读取有名返回值
    运用最典型的场景及关闭资源,如操作文件,数据库操作等。如下例子

    func do() error {
        f, err := os.Open("book.txt")
        if err != nil {
            return err
        }
        defer func(f io.Closer) {
            if err := f.Close(); err != nil {
                // log etc
            }
        }(f)
    
        // ..code...
        f, err = os.Open("another-book.txt")
        if err != nil {
            return err
        }
        defer func(f io.Closer) {
            if err := f.Close(); err != nil {
                // log etc
            }
        }(f)
    
        return nil
    }
    

     

    异常panic

    在开始闭包中提到过返回panic,那什么是panic。Go有别于那些将函数运行失败看作是异常的语言。虽然Go有各种异常机制,但这些机制仅仅用于严重的错误,而不是那些在健壮程序中应该被避免的程序错误。runtime在一些情况下会抛出异常,例如除0,我们也能使用panic关键字自己抛出异常。
    出现异常,默认程序退出并打印堆栈。如下函数

    package main
    func f6() {
        func () {
            func () int {
                x := 0
                y := 5/x
                return y
            }()
        }()
    }
    func main() {
    
        f6()
    }

    如果不想程序退出的话,也有办法,就是使用recover捕捉异常,然后返回error。在没发生panic的情况下,调用recover会返回nil,发生了panic,那么就是panic的值。看个例子:

    package main
    import (
        "fmt"
    )
    
    type shouldRecover struct{}
    type emptyStruct struct{}
    func f6() (err error) {
        defer func () {
            switch p := recover(); p {
                case nil: //donoting
            case shouldRecover{}:
                err = fmt.Errorf("occur panic but had recovered")
            default:
                panic(p)
            }
        } ()
    
        func () {
            func () int {
                panic(shouldRecover{})
                //panic(emptyStruct{})
                x := 0
                y := 5/x
                return y
            }()
        }()
    
        return
    }
    
    
    func main() {
        err := f6()
        if err != nil {
            fmt.Printf("fail %v
    ", err)
        } else {
            fmt.Printf("success
    ")
        }
    }
    

     方法

    package main
    import (
        "fmt"
    )
    
    type Employee struct {
        name     string
        salary   int
        currency string
    }
    /*
     displaySalary() method has Employee as the receiver type
    */
    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() //Calling displaySalary() method of Employee type
    }
    

     也许有人会问,方法和函数差不多,为什么还要多此一举使用方法呢?

    • Golang 不是一个纯粹的面向对象的编程语言,它不支持类。因此通过在一个类型上建立方法来实现与 class 相似的行为。
    • 同名方法可以定义在不同的类型上,但是 Golang 不允许同名函数。假设有两个结构体 Square 和 Circle。在 Square 和 Circle 上定义同名的方法是合法的。

    如下一个函数就很明了了

    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,
              5,
        }
        fmt.Printf("Area of rectangle %d
    ", r.Area())
        c := Circle{
            radius: 12,
        }
        fmt.Printf("Area of circle %f", c.Area())
    }

    值接收者和指针接收者

    两者区别在于,以指针作为接收者时,方法内部进行的修改对于调用者是可见的,但是以值作为接收者却不是。

    package main
    import (
        "fmt"
    )
    
    type Employee struct {
        name string
        age  int
    }
    /*
    Method with value receiver
    */
    func (e Employee) changeName(newName string) {
        e.name = newName
    }
    /*
    Method with pointer receiver
    */
    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("
    Employee name after change: %s", e.name)
    
        fmt.Printf("
    
    Employee age before change: %d", e.age)
        (&e).changeAge(51)
        fmt.Printf("
    Employee age after change: %d", e.age)
    }

    上面的程序中, changeName 方法有一个值接收者 (e Employee),而 changeAge 方法有一个指针接收者 (e *Employee)。在 changeName 中改变 Employee 的 name 的值对调用者而言是不可见的,因此程序在调用 e.changeName("Michael Andrew") 方法之前和之后,打印的 name 是一样的。而 changeAge 的接受者是一个指针 (e *Employee),因此通过调用方法 (&e).changeAge(51) 来修改 age 对于调用者是可见的。
    使用 (&e).changeAge(51) 来调用 changeAge 方法不是必须的,Golang 允许我们省略 & 符号,因此可以写为 e.changeAge(51)。Golang 将 e.changeAge(51) 解析为 (&e).changeAge(51)。

    非结构体类型的方法

    现在我们定义的都是结构体类型的方法,同样可以定义非结构体类型的方法,不过需要注意一点。为了定义某个类型的方法,接收者类型的定义与方法的定义必须在同一个包中。

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

    在函数和方法中都会接收值参数和指针参数,那么两者又有什么却别?

    方法的值接收者和函数的值参数

    当一个函数有一个值参数时,它只接受一个值参数。
    当一个方法有一个值接收者时,它可以接受值和指针接收者。
    如下一个例子

    package main
    import (
        "fmt"
    )
    
    type rectangle struct {
        length int
        width  int
    }
    func area(r rectangle) {
        fmt.Printf("Area Function result: %d
    ", (r.length * r.width))
    }
    
    func (r rectangle) area() {
        fmt.Printf("Area Method result: %d
    ", (r.length * r.width))
    }
    func main() {
        r := rectangle{
            length: 10,
              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() //calling value receiver with a pointer
    }

    我们创建了一个指向 r 的指针 p。如果我们试图将这个指针传递给只接受值的 area 函数那么编译器将报错。
    p.area() 使用指针接收者 p 调用一个值接收者方法 area 。这是完全合法的。原因是对于 p.area(),由于 area 方法必须接受一个值接收者,所以 Golang 将其解析为 (*p).area()。

    方法的指针接收者和函数的指针参数

    具有指针参数的函数将仅接受指针,而具有指针接收者的方法将接受值和指针接收者

    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,
              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() //calling pointer receiver with a value
    
    }
    

    试图以一个值参数 r 调用 perimeter 函数,这是非法的。因为一个接受指针为参数的函数不能接受一个值作为参数。如果去掉注释,则编译报错。

    通过一个值接收者 r 调用一个指针接收者 perimeter 方法,这是合法的。r.perimeter() 将被 Golang 解析为 (&r).perimeter()。

     

  • 相关阅读:
    【css基础修炼之路】— 谈谈元素的垂直水平居中
    git在linux安装步骤详解!!
    idea :不支持发行版本11问题
    centos7 升级gcc
    mysql--优化
    Docker安装
    使用idea从零编写SpringCloud项目-zuul
    使用idea从零编写SpringCloud项目-Hystrix
    使用idea从零编写SpringCloud项目-Feign
    使用idea从零编写SpringCloud项目-Ribbo
  • 原文地址:https://www.cnblogs.com/flash55/p/10546501.html
Copyright © 2011-2022 走看看