pascal程序员学Golang——Method

Golang中的方法是带有接收器的函数,可以附加到类型上,实现面向对象编程。方法分为值接收器和指针接收器,前者在方法内对接收器的修改不会影响调用者,后者则可以。结构体方法允许在结构上定义行为,类似于面向对象语言中的类。方法链接允许连续调用方法,且非结构自定义类型也可定义方法。
摘要由CSDN通过智能技术生成

在pascal中function和procedure被称为Method,实现某一功能,无非就是有返回值和无返回值。在Golang中,method有特定的意义。

概述

golang 中的方法只不过是一个带有接收器的函数。接收器是某些特定类型(如结构)的实例,但它可以是任何其他自定义类型的实例。所以基本上当你把一个函数附加到一个类型时,该函数就会成为该类型的方法。该方法将有权访问接收器的属性,并可以调用接收器的其他方法。

为什么选择方法

由于方法允许您在类型上定义函数,因此它允许您在 Golang 中编写面向对象的代码。还有一些其他的好处,例如两个不同的方法可以在同一包中具有相同的名称,这是函数无法实现的

方法的格式

下面是方法的格式

func (receiver receiver_type) some_func_name(arguments) return_values

 

方法接收器和接收器类型显示在 func 关键字和函数名称之间。return_values在最后到来。

另外,让我们了解函数和方法之间的更多区别。它们之间存在一些重要差异。下面是一个函数的签名

函数:

func some_func_name(arguments) return_values

 从上面的签名中可以清楚地看出该方法具有接收器参数。这是函数和方法之间的唯一区别,但因此它们在提供的功能方面有所不同

  • 函数可以用作一阶对象,并且可以在方法不能传递时传递。
  • 方法可用于接收器上的链接,而函数不能用于相同的链接。
  • 可以存在具有相同名称的不同方法和不同的接收器,但在同一包中不能存在两个具有相同名称的不同函数。

结构体上的方法

Golang 不是一种面向对象的语言。它不支持类型继承,但它确实允许我们在任何自定义类型(包括结构)上定义方法。由于结构是字段的命名集合,因此也可以在其上定义方法。因此,golang中的结构可以与面向对象语言中的类进行比较。

让我们看一个结构体方法的例子

package main

import "fmt"

type employee struct {
    name   string
    age    int
    salary int
}

func (e employee) details() {
    fmt.Printf("Name: %s\n", e.name)
    fmt.Printf("Age: %d\n", e.age)
}

func (e employee) getSalary() int {
    return e.salary
}

func main() {
    emp := employee{name: "Sam", age: 31, salary: 2000}
    emp.details()
    fmt.Printf("Salary %d\n", emp.getSalary())
}

 输出

Name: Sam
Age: 31
Salary 2000

 请注意,接收器在方法中可用,接收器的字段可以在方法内部访问。

接收器的字段也可以在方法内部更改吗?

让我们看看

package main

import "fmt"

type employee struct {
    name   string
    age    int
    salary int
}

func (e employee) setNewName(newName string) {
    e.name = newName
}

func main() {
    emp := employee{name: "Sam", age: 31, salary: 2000}
    emp.setNewName("John")
    fmt.Printf("Name: %s\n", emp.name)
}

输出

Name: Sam

在上面的代码中,在员工结构上定义了一个方法集NewName。在这种方法中,我们像这样更新员工的姓名

e.name = newName

当我们在 main 函数中再次打印员工姓名时设置新名称后,我们看到打印的是旧名称“Sam”而不是“John”。发生这种情况是因为方法是在值接收器上定义的

func (e employee) setNewName(newName string)

由于该方法是在值接收器上定义的,因此当该方法被称为方法时,将创建接收器的副本,并且接收器的副本在方法中可用。由于它是副本,因此对值接收器所做的任何更改对调用方都不可见。这就是为什么它打印旧名称“山姆”而不是“约翰”。现在想到的问题是否有任何方法可以解决这个问题。答案是肯定的,这就是指针接收器出现的地方。

指针接收器上的方法

在上面的例子中,我们看到了一个值接收器上的方法。对值接收器所做的任何更改对调用方都不可见。也可以在指针接收器上定义方法。对指针接收器所做的任何更改都将对调用方可见。让我们看一个例子

package main

import "fmt"

type employee struct {
    name   string
    age    int
    salary int
}

func (e *employee) setNewName(newName string) {
    e.name = newName
}

func main() {
    emp := &employee{name: "Sam", age: 31, salary: 2000}
    emp.setNewName("John")
    fmt.Printf("Name: %s\n", emp.name)
}

输出

Name: John

在上面的程序中,我们在指针接收器上定义了方法setNewName

func (e *employee) setNewName(newName string)

然后,我们创建了一个员工指针,并在其上调用了 setNewName 方法。我们看到,对 setNewName 内的员工指针所做的更改对调用方可见,并且它会打印新名称。

是否需要创建员工指针才能使用指针接收器调用方法?不,不是。可以在员工实例上调用该方法,语言将负责它以将其作为指向该方法的指针正确传递。这种灵活性是由语言提供的。

让我们看一个例子

package main

import "fmt"

type employee struct {
    name   string
    age    int
    salary int
}

func (e *employee) setNewName(newName string) {
    e.name = newName
}

func main() {
    emp := employee{name: "Sam", age: 31, salary: 2000}
    emp.setNewName("John")

    fmt.Printf("Name: %s\n", emp.name)

    (&emp).setNewName("Mike")
    fmt.Printf("Name: %s\n", emp.name)
}

输出

Name: John
Name: Mike

我们在上面的程序中看到,即使在指针接收器上定义了方法,但我们使用非指针雇员实例调用该方法

emp.setNewName("John")

但是语言将接收方作为指针传递,因此更改对调用方可见。

即使这种调用方式也是有效的

(&emp).setNewName("Mike")

现在,反过来呢。如果在值接收器上定义了方法,是否可以使用接收器的指针调用该方法?

是的,即使这是有效的,并且无论该方法是在指针上还是在普通结构上调用,该语言都会负责将参数作为值接收器正确传递。

让我们看一个例子

package main

import "fmt"

type employee struct {
    name   string
    age    int
    salary int
}

func (e employee) setNewName(newName string) {
    e.name = newName
}

func main() {
    emp := employee{name: "Sam", age: 31, salary: 2000}
    emp.setNewName("John")

    fmt.Printf("Name: %s\n", emp.name)
    (&emp).setNewName("Mike")

    fmt.Printf("Name: %s\n", emp.name)
    emp2 := &employee{name: "Sam", age: 31, salary: 2000}
    emp2.setNewName("John")
    fmt.Printf("Name: %s\n", emp2.name)
}

输出

Name: Sam
Name: Sam
Name: Sam

请注意,因为在所有三种情况下,setNewName 方法都有一个值接收器,因此当值作为副本传递时,调用方看不到更改。它在所有三种情况下都打印旧名称

总结一下我们上面学到的东西

  • 如果方法具有值接收器,则支持同时使用值接收器和指针接收器调用该方法
  • 如果一个方法有一个指针接收器,那么它也支持同时使用值和指针接收器调用该方法

这与函数不同,其中如果

  • 如果一个函数有一个指针参数,那么它只会接受一个指针作为参数
  • 如果一个函数有一个值参数,那么它只接受一个值作为参数

何时使用指针接收器

  • 当在方法内部对接收器所做的更改必须对调用方可见时。
  • 当结构很大时,最好使用指针接收器,否则每次调用方法时都会制作结构的副本,这将很昂贵

关于方法的更多注意事项

  • 接收器类型必须在与方法定义相同的包中定义。在接收器上定义存在于不同包中的方法时,将引发以下错误。
ERROR: cannot define new methods on non-local types

 

  • 到目前为止,我们已经看到了使用点运算符的方法调用。还有另一种方法可以调用方法,如以下示例所示
package main

import "fmt"

type employee struct {
	name   string
	age    int
	salary int
}

func (e employee) details() {
	fmt.Printf("Name: %s\n", e.name)
	fmt.Printf("Age: %d\n", e.age)
}

func (e *employee) setName(newName string) {
	e.name = newName
}

func main() {
	emp := employee{name: "Sam", age: 31, salary: 2000}
	employee.details(emp)

	(*employee).setName(&emp, "John")

	fmt.Printf("Name: %s\n", emp.name)
}

 

输出

Name: Sam
Age: 31
Name: John

在上面的例子中,我们看到了一种不同的方法来调用方法。有两种情况

  • 当方法具有值接收器时,可以按如下所示调用它,它是结构名称,后跟方法名称。第一个参数是值接收器本身。
employee.details(emp)

 

  • 当方法具有指针接收器时,可以按如下所示调用它,该指针指向结构名称,后跟方法名称。第一个参数是指针接收器。

 

(*employee).setName(&emp, "John")

另请注意,该方法的参数从上面的 setName 函数的第二个参数开始:

(*employee).setName(&emp, "John")

您很少会看到使用这种样式,我们之前讨论的点表示法样式是推荐的,也是最常见的方法。

匿名嵌套结构字段的方法

让我们看一个程序

package main

import "fmt"

type employee struct {
	name   string
	age    int
	salary int
	address
}

type address struct {
	city    string
	country string
}

func (a address) details() {
	fmt.Printf("City: %s\n", a.city)
	fmt.Printf("Country: %s\n", a.country)
}

func main() {
	address := address{city: "London", country: "UK"}

	emp := employee{name: "Sam", age: 31, salary: 2000, address: address}

	emp.details()

	emp.address.details()
}

输出

City: London
Country: UK
City: London
Country: UK

请注意,在上面的程序中,可以通过两种方式访问地址结构的详细方法

emp.details()
emp.address.details()

因此,在匿名嵌套结构的情况下,可以直接访问该结构的方法。

导出方法

Go 没有任何公共、私有或受保护的关键字。控制包外部可见性的唯一机制是使用大写和非大写格式

  • 将导出大写标识符。大写字母表示这是导出的标识符,可在包外部使用。
  • 不导出非大写标识符。小写表示标识符未导出,只能从同一包中访问。

因此,任何以大写字母开头的结构都会导出到其他包中。同样,任何以资本开头的结构字段都会被导出,否则不会导出。同样,任何以大写字母开头的结构方法都会被导出。让我们看一个示例,该示例显示了结构、结构字段和方法的导出和非导出。请参阅下面的 model.go 和 test.go。两者都属于包。

  • 结构
    • 导出结构体人员
    • 结构公司是非出口的
  • 结构领域
    • 导出人员结构字段名称
    • 不导出人员结构字段年龄,但导出名称
  • 结构的方法
    • 导出结构体的方法 GetAge()
    • 结构体的方法 getName() 未导出

model.go

package main

import "fmt"

//Person struct
type Person struct {
    Name string
    age  int
}

//GetAge of person
func (p *Person) GetAge() int {
    return p.age
}

func (p *Person) getName() string {
    return p.Name
}

type company struct {
}

 test.go

package main

import "fmt"

//Test function
func Test() {
    //STRUCTURE IDENTIFIER
    p := &Person{
        Name: "test",
        age:  21,
    }
    fmt.Println(p)
    c := &company{}
    fmt.Println(c)
    
    //STRUCTURE'S FIELDS
    fmt.Println(p.Name)
    fmt.Println(p.age)

    
    //STRUCTURE'S METHOD
    fmt.Println(p.GetAge())
    fmt.Println(p.getName())

}

 输出

&{test 21}
&{}
test
21
21
test

如果我们移动文件 model.go 到另一个名为 model 的包。 现在运行“go build”的输出将给出编译错误。所有编译错误都是因为包中的test.go无法引用模型包中model.go的未导出字段。

编译错误将是

p.getName undefined (cannot refer to unexported field or method model.(*Person).getName)

方法链接

为了使方法链接成为可能,链中的方法应返回接收器。返回链中最后一个方法的接收器是可选的。

让我们看一个方法链接的示例。

 

package main

import "fmt"

type employee struct {
	name   string
	age    int
	salary int
}

func (e employee) printName() employee {
	fmt.Printf("Name: %s\n", e.name)
	return e
}

func (e employee) printAge() employee {
	fmt.Printf("Age: %d\n", e.age)
	return e
}

func (e employee) printSalary() {
	fmt.Printf("Salary: %d\n", e.salary)
}

func main() {
	emp := employee{name: "Sam", age: 31, salary: 2000}
	emp.printName().printAge().printSalary()
}

 输出

Name: Sam
Age: 31
Salary: 2000

非结构类型的方法

还可以在非结构自定义类型上定义方法。非结构自定义类型可以通过类型定义创建。以下是创建新自定义类型的格式

type {type_name} {built_in_type}

 例如,我们可以命名为float64类型的自定义类型myFloat

type myFloat float64

 可以在命名的自定义类型上定义方法。请参阅以下示例:

package main

import (
    "fmt"
    "math"
)

type myFloat float64

func (m myFloat) ceil() float64 {
    return math.Ceil(float64(m))
}

func main() {
    num := myFloat(1.4)
    fmt.Println(num.ceil())
}

输出

2

结论

go中的method,近似于delphi里面record所定义的function和procedure,strcuct和record都不可继承,go里面没有overload的概念,所以method不可重名,二者的method即插即用,不需构造函数。

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值