欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

golang教程之方法

程序员文章站 2022-06-26 12:54:34
...

方法

原文:https://golangbot.com/methods/

什么是方法?

方法只是一个具有特殊接收器类型的函数,该函数在func关键字和方法名称之间编写。 接收器可以是struct类型或非struct类型。 接收器可用于方法内部的访问。

以下是创建方法的语法。

func (t Type) methodName(parameter list) {  
}

上面的代码片段创建了一个名为methodName的方法,该方法具有接收器类型Type

示例方法

让我们编写一个简单的程序,它在struct类型上创建一个方法并调用它。

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
}

在上面的程序第16行中,我们在Employee 结构体类型上创建了一个方法displaySalarydisplaySalary()方法可以访问其中的接收者e Employee。在第17行,我们使用接收器e并打印员工的姓名,货币和工资。

在第26行,我们使用语法emp1.displaySalary()调用了该方法。

这个程序打印Salary of Sam Adolf is $5000

为什么我们有函数了还用方法?

上面的程序仅使用函数而没有方法重写。

package main

import (  
    "fmt"
)

type Employee struct {  
    name     string
    salary   int
    currency string
}

/*
 displaySalary() method converted to function with Employee as parameter
*/
func displaySalary(e Employee) {  
    fmt.Printf("Salary of %s is %s%d", e.name, e.currency, e.salary)
}

func main() {  
    emp1 := Employee{
        name:     "Sam Adolf",
        salary:   5000,
        currency: "$",
    }
    displaySalary(emp1)
}

在上面的程序中,displaySalary方法转换为函数,Employee结构作为参数传递给它。 这个程序也产生完全相同的输出Salary of Sam Adolf is $5000

那么为什么我们可以使用函数编写相同的程序时还要使用方法。 这有几个原因。 让我们逐一看看它们。

  • Go不是纯粹的面向对象编程语言,它不支持类。 因此,类型上的方法是一种实现类似行为的方法。

  • 可以在不同类型上定义具有相同名称的方法,而不允许具有相同名称的函数。 让我们假设我们有一个SquareCircle结构。 可以在SquareCircle上定义名为Area的方法。 这在以下程序中完成。

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

这个程序打印

Area of rectangle 50  
Area of circle 452.389342  

方法的上述属性用于接口。 当我们处理接口时,我们将在下一个教程中讨论这个问题。

指针接收器与值接收器

到目前为止,我们已经看到只有值接收器的方法。 可以使用指针接收器创建方法。 值和指针接收器之间的区别在于,使用指针接收器的方法内部进行的更改对于调用者是可见的,而在值接收器中则不是这种情况。 让我们在程序的帮助下理解这一点。

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("\nEmployee name after change: %s", e.name)

    fmt.Printf("\n\nEmployee age before change: %d", e.age)
    (&e).changeAge(51)
    fmt.Printf("\nEmployee age after change: %d", e.age)
}

在上面的程序中,changeName方法有一个值接收器(e Employee),而changeAge方法有一个指针接收器(e * Employee)。 对changeName中的Employee结构体名称字段所做的更改将对调用者不可见,因此程序在调用方法e.changeName(“Michael Andrew”)之前和之后打印相同的名称。 由于changeAge方法适用于指针接收器(e * Employee),因此调用方可以看到方法调用(&e).changeAge(51)之后对age字段所做的更改。 这个程序打印,

Employee name before change: Mark Andrew  
Employee name after change: Mark Andrew

Employee age before change: 50  
Employee age after change: 51 

在上面的程序第36行中,我们使用(&e).changeAge(51)来调用changeAge方法。 由于changeAge有一个指针接收器,我们使用(&e)来调用该方法。 这不是必需的,语言为我们提供了使用e.changeAge(51)的选项。 e.changeAge(51)将被语言解释为(&e).changeAge(51)

下面的程序被重写为使用e.changeAge(51)而不是(&e).changeAge(51)并打印相同的输出。

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("\nEmployee name after change: %s", e.name)

    fmt.Printf("\n\nEmployee age before change: %d", e.age)
    e.changeAge(51)
    fmt.Printf("\nEmployee age after change: %d", e.age)
}

何时使用指针接收器以及何时使用值接收器

通常,当对方法内的接收器所做的更改应对调用者可见时,可以使用指针接收器。

指针接收器也可用于复制数据结构昂贵的地方。 考虑一个包含许多字段的结构。 在方法中使用此结构作为值接收器将需要复制整个结构,这将是昂贵的。 在这种情况下,如果使用指针接收器,则不会复制struct,并且只在该方法中使用指向它的指针。

在所有其他情况下,可以使用值接收器。

匿名字段的方法

可以调用属于结构的匿名字段的方法,就好像它们属于定义匿名字段的结构一样。

package main

import (  
    "fmt"
)

type address struct {  
    city  string
    state string
}

func (a address) fullAddress() {  
    fmt.Printf("Full address: %s, %s", a.city, a.state)
}

type person struct {  
    firstName string
    lastName  string
    address
}

func main() {  
    p := person{
        firstName: "Elon",
        lastName:  "Musk",
        address: address {
            city:  "Los Angeles",
            state: "California",
        },
    }

    p.fullAddress() //accessing fullAddress method of address struct

}

在上面的程序第32行中,我们使用p.fullAddress()调用地址结构的fullAddress()方法。 不需要显式指定p.address.fullAddress()。 这个程序打印

Full address: Los Angeles, California  

函数中的方法与值参数的值接收器

这个话题大多数都是新手。 我会尽量让它尽可能清楚。

当函数有一个value参数时,它只接受一个value参数。

当方法具有值接收器时,它将接受指针和值接收器。

让我们通过一个例子来理解这一点。

package main

import (  
    "fmt"
)

type rectangle struct {  
    length int
    width  int
}

func area(r rectangle) {  
    fmt.Printf("Area Function result: %d\n", (r.length * r.width))
}

func (r rectangle) area() {  
    fmt.Printf("Area Method result: %d\n", (r.length * r.width))
}

func main() {  
    r := rectangle{
        length: 10,
        width:  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
}

第12行中的函数func area(r rectangle)接受值参数,方法func (r rectangle) area()接受值接收器。

第25行,我们使用值参数area(r)调用区域函数,它将起作用。类似地,我们使用值接收器调用area方法r.area(),这也可以。

我们在第28行中创建一个指针p指向变量r。 如果我们尝试将此指针传递给只接受值的函数area,编译器将会报错。我注释了33行,如果取消注释该行,则编译器将抛出错误编译错误, compilation error, cannot use p (type *rectangle) as type rectangle in argument to area

现在是棘手的部分,第35行代码中调用方法p.area(),该area仅使用指针接收器p接受值接收器。这完全有效。原因是为方便起见,p.area()将由Go解释为(* p).area(),因为area具有值接收器。

该程序将输出,

Area Function result: 50  
Area Method result: 50  
Area Method result: 50  

方法中的指针接收器与函数中的指针参数。

与值参数类似,具有指针参数的函数将仅接受指针,而具有指针接收器的方法将接受值和指针接收器

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,
        width:  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

}

上述程序第12行中定义了一个函数perimeter,它接受指针参数。 在第17行中定义了一种具有指针接收器的方法。

我们使用指针参数调用perimeter函数,在第28行中,我们在指针接收器上调用perimeter方法。 一切都很好。

在注释行33中,我们尝试使用值参数r调用perimeter函数。 这是不允许的,因为带有指针参数的函数不接受值参数。 如果该行未被注释并且程序运行,则编译将失败,错误为main.go:33: cannot use r (type rectangle) as type *rectangle in argument to perimeter

在第35行中,我们使用值接收器r调用指针接收器方法perimeter。 这是允许的,代码行r.perimeter()将被语言解释为(&r).perimeter()以方便使用。 该程序将输出,

perimeter function output: 30  
perimeter method output: 30  
perimeter method output: 30  

非结构类型的方法

到目前为止,我们只在结构类型上定义了方法。 也可以在非结构类型上定义方法,但是有一个问题。 要在类型上定义方法,方法的接收器类型的定义和方法的定义应该在同一个包中。 到目前为止,我们定义的结构上的所有结构和方法都位于同一主包中,因此它们起作用。

package main

func (a int) add(b int) {  
}

func main() {

}

在上面的程序第3行中,我们试图在内置类型int中添加一个名为add的方法。 这是不允许的,因为方法add的定义和int类型的定义不在同一个包中。 这个程序会抛出编译错误,无法在非本地类型int上定义新方法

实现此方法的方法是为内置类型int创建类型别名,然后使用此类型别名创建一个方法作为接收器。

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

在上面程序的第5行中,我们为int创建了一个类型别名myInt。 在第7行中,我们定义了一个add作为myInt接收器的方法。

这个程序将打印Sum为15。

我创建了一个程序,其中包含我们目前讨论过的所有概念,并且可以在github中找到它。

相关标签: go methods