Go 中的方法
最佳答案 问答题库428位专家为你答疑解惑
方法介绍
方法只是一个func
关键字和方法名称之间具有特殊接收者类型的函数,接收者可以是结构类型或非结构类型。
下面提供了方法声明的语法。
func (t Type) methodName(parameter list) {
}
上面的代码片段创建了一个methodName
以接收者Type
类型命名的方法。t
被称为接收者并且可以在方法内访问它。
示例方法
让我们编写一个简单的程序,在结构类型上创建一个方法并调用它。
package mainimport ( "fmt"
)type Employee struct { name stringsalary intcurrency 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
}
Run in playground
上面的程序种,我们在Employee
结构类型上创建了一个displaySalary
方法。该displaySalary()
方法可以访问e
其内部的接收器。
在17行中,我们正在使用接收器e
并打印员工的姓名、货币和工资。
在26 行中。我们已经使用语法调用了该方法emp1.displaySalary()
。
该程序打印
Salary of Sam Adolf is $5000
方法与函数
上面的程序可以只使用函数而不使用方法来重写。
package mainimport ( "fmt"
)type Employee struct { name stringsalary intcurrency 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)
}
Run in playground
在上面的程序中,displaySalary
方法被转换为函数,并且Employee
结构体作为参数传递给它。该程序也产生完全相同的输出
Salary of Sam Adolf is $5000
那么,当我们可以使用函数编写相同的程序时,为什么还要有方法呢?造成这种情况的原因有几个。让我们一一看看。
- Go 不是一种纯粹的面向对象编程语言,它不支持类。因此,类型上的方法是实现类似于类的行为的一种方式。方法允许对与类似于类的类型相关的行为进行逻辑分组。在上面的示例程序中,所有与
Employee
类型相关的行为都可以通过使用Employee
接收者类型创建方法来分组。例如,我们可以添加calculatePension
、calculateLeaves
等方法。 - 可以在不同类型上定义具有相同名称的方法,但不允许在不同类型上定义具有相同名称的函数。假设我们有一个
Square
andCircle
结构。可以在这个2个结构中定义一个Area
命名的方法。这是在下面的程序中完成的。
package mainimport ( "fmt""math"
)type Rectangle struct { length intwidth 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())
}
Run in playground
该程序打印
Area of rectangle 50
Area of circle 452.389342
方法的上述属性用于实现接口。我们将在下一个教程中处理接口时详细讨论这一点。
指针接收器与值接收器
到目前为止,我们只看到了带有值接收者的方法。也可以创建带有指针接收器的方法。
值接收器和指针接收器之间的区别在于,使用指针接收器的方法内部所做的更改对于调用者来说是可见的,而在值接收器中则不是这样。
让我们借助一个程序来理解这一点。
package mainimport ( "fmt"
)type Employee struct { name stringage 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)
}
Run in playground
在上面的程序中,该changeName
方法有一个(e Employee)
值接收器,而该changeAge
方法有一个(e *Employee)
指针接收器。
对Employee
结构体name
内部字段所做的更改changeName
对调用者来说是不可见的,因此程序调用该方法之前和之后打印相同的名称。
由于changeAge
方法有一个(e *Employee)
指针接收者,因此age
方法调用后对字段所做的更改(&e).changeAge(51)
将对调用者可见。该程序打印,
Employee name before change: Mark Andrew
Employee name after change: Mark AndrewEmployee age before change: 50
Employee age after change: 51
上面的程序中,我们使用(&e).changeAge(51)
来调用该changeAge
方法。既然changeAge
有指针接收器,我们就用来(&e)
调用该方法。这是不需要的,我们还可以选择只使用e.changeAge(51)
, e.changeAge(51)
将被解释为由(&e).changeAge(51)
。
以下程序被重写以使用 e.changeAge(51)
代替(&e).changeAge(51)
并打印相同的输出。
package mainimport ( "fmt"
)type Employee struct { name stringage 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)
}
Run in playground
何时使用指针接收器以及何时使用值接收器
通常,当方法内部对接收器所做的更改应该对调用者可见时,可以使用指针接收器。
指针接收器也可以用在复制数据结构成本高昂的地方。
考虑一个具有许多字段的结构。在方法中使用此结构作为值接收器将需要复制整个结构,这将是昂贵的。在这种情况下,如果使用指针接收器,则不会复制该结构,并且在方法中仅使用指向它的指针。
在所有其他情况下,都可以使用值接收器。
匿名结构体字段的方法
属于结构体匿名字段的方法可以被调用,就好像它们属于定义匿名字段的结构体一样。
package mainimport ( "fmt"
)type address struct { city stringstate string
}func (a address) fullAddress() { fmt.Printf("Full address: %s, %s", a.city, a.state)
}type person struct { firstName stringlastName stringaddress
}func main() { p := person{firstName: "Elon",lastName: "Musk",address: address {city: "Los Angeles",state: "California",},}p.fullAddress() //accessing fullAddress method of address struct}
Run in playground
在上面程序的第32行中,我们使用调用结构体fullAddress()
的方法。不需要明确的指示。该程序打印
Full address: Los Angeles, California
方法中的值接收器与函数中的值参数
这个话题最适合 Go 新手。我会尽力让它尽可能清楚。
当函数有值参数时,它将只接受值参数。
当一个方法有一个值接收器时,它将同时接受指针和值接收器。
让我们通过一个例子来理解这一点。
package mainimport ( "fmt"
)type rectangle struct { length intwidth 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
}
Run in playground
func area(r rectangle)
函数接受一个值参数和func (r rectangle) area()
的方法接受一个值参数。
在25行中我们用一个值参数调用area函数area(r)
,它就会工作。类似地,我们使用值接收器调用区域方法r.area()
,这也可以工作。
我们创建一个p
指向r
指针。
如果我们试图将这个指针传递给只接受一个值的函数区域,编译器会报错。
如果不注释此行,则编译器将抛出错误*编译错误,cannot use p (variable of type rectangle) as rectangle value in argument to area。
调用使用指针接收器仅接受值接收器的p.area()
方法。这是完全有效的。原因是(*p).area()
将被 Go 解释为一个值接收器。
该程序将输出,
Area Function result: 50
Area Method result: 50
Area Method result: 50
方法中的指针接收器与函数中的指针参数
与值参数类似,具有指针参数的函数仅接受指针,而具有指针接收器的方法将同时接受指针和值接收器。
package mainimport ( "fmt"
)type rectangle struct { length intwidth 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 rperimeter(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}
Run in playground
上述程序的第 12 行定义了一个接受指针参数的函数。17行 定义了一个具有指针接收器的方法。
在第 27 行中,我们使用指针参数调用 perimeter 函数,在第 28 行中,我们在指针接收器上调用 perimeter 方法。一切都很好。
在第 33 行注释中,我们尝试使用值参数调用函数perimeter(r)
。这是不允许的,因为带有指针参数的函数不接受值参数。如果取消注释此行并且运行程序,则编译将失败,并显示错误main.go:33: Cannot use r (typefragment) as type *rectangle in argument to perimeter。
在第 35 行中,我们使用 r.perimeter()
值接收器调用指针接收器方法。这是允许的,并且为了方便起见,该语言将解释该代码行为(&r).perimeter()
。
该程序将输出,
perimeter function output: 30
perimeter method output: 30
perimeter method output: 30
具有非结构接收器的方法
到目前为止,我们仅在结构类型上定义了方法。也可以在非结构类型上定义方法.
但有一个问题。要在类型上定义方法,接收者类型的定义和方法的定义应存在于同一包中。
到目前为止,我们定义的所有结构体和结构体上的方法都位于同一个main
包中,因此它们可以工作。
package mainfunc (a int) add(b int) {
}func main() {}
Run in playground
在上面的程序中,我们正在尝试添加一个名为int
内置类型的add
方法。这是不允许的,因为方法的定义add
和类型的定义int
不在同一个包中。该程序将抛出编译错误cannot define new methods on non-local type int
实现此功能的方法是为内置类型创建类型别名int
,然后使用该类型别名创建一个方法作为接收者。
package mainimport "fmt"type myInt intfunc (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)
}
Run in playground
在上述程序的第 5 行中,我们创建了一个int
类型别名myInt
。在7行中,我们定义了一个方法add
作为myInt
接收者。
该程序将打印Sum is 15
.
这就是 Go 中的方法。祝你有美好的一天。
99%的人还看了
相似问题
- Kotlin学习——kt里的集合,Map的各种方法之String篇
- Office文件在线预览大全-Word文档在线预览的实现方法-OFD文档在线预览-WPS文件在线预览
- composer切换全局镜像源的方法
- Python通过selenium调用IE11浏览器报错解决方法
- 测试用例的设计方法(全):正交实验设计方法|功能图分析方法|场景设计方发
- Java8新特性 ----- Lambda表达式和方法引用/构造器引用详解
- C#中抽象类、抽象方法和接口暨内联临时变量的精彩表达
- ChatGLM2 大模型微调过程中遇到的一些坑及解决方法(更新中)
- 类方法,静态方法和实例方法的区别及应用场景
- 【链表的说明、方法---顺序表与链表的区别】
猜你感兴趣
版权申明
本文"Go 中的方法":http://eshow365.cn/6-22064-0.html 内容来自互联网,请自行判断内容的正确性。如有侵权请联系我们,立即删除!