开发者学堂课程【Go 语言核心编程 - 面向对象、文件、单元测试、反射、TCP 编程:方法和函数区别说明】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/626/detail/9682
方法和函数区别说明
内容介绍:
一、区别1
二、区别2
三、区别3
一、区别1
调用方式不一样
函数的调用方式:
函数名(实参列表)
方法的调用方式:
变量(与方法绑定类型的变量)方法名(实参列表)
二、区别2
对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然
对于普通函数,如果接收者是值类型,只可以进行值类型的传入,而不能进行指针类型的传入;反之如果接收者是指针类型,则只可以进行指针类型的传入,即只可以传入地址,而不可以进行类型的传入。
下面进行举例说明:
新建一个文件夹,命名为 methodvsfunc,在文件夹中新建一个文件 main.go。输入共用代码:
package main
import (
"fmt”"
)
假设现有一个结构体 type Person struct,输入字段 name{Name string}。写入一个函数,例如 test01,然后为其定义一个类型 func teste1(p Person){,并将其输出fmt.Println(p. Name)}。在主函数中定义一个 person,并将其赋值为 tom,代码为:
func main(){
p:=Person{"tom"}
然后再调用这个函数test01(p),因为该函数是结构体类型而不是指针类型,因此传入p就可以,输入cd chapter10 cd methodvsfunc go run main.go,运行后得到tom,与理论结果一致。此时要注意因为上述函数写入的是person,因此就不能传地址了test01(&p),如果传入地址,代码肯定会报错。所以我们得出一个结论是对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递。
核心代码:
package main
import (
“fmt”
)
type Person struct{
Name string
}
func test01(p Person){
fmt.Print1n(p.Name)
}
func main(){
P :=Person{"tom"}
test01(p)
反之亦然,进行举例说明,如果上述函数传入的是指针func test02(p *Person){
,进行调用时只能输入一个地址,代码为
func main(){
P :=Person{"tom"}
test02(&p1)
输入cd chapter10 cd methodvsfunc go run main.go
完成运行,结果仍为tom,如果调用时输入test02(p),它也会没有报错了,说明此处有一个严格的规定,对于函数来说,如果接收者是指针,就只能传输地址,而如果接收者是值类型,则只能传输值,但是方法与函数不一样。
核心代码:
package main
import (
“fmt”
)
type Person struct{
Name string
}
func test02(p *Person){
fmt.Print1n(p.Name)
}
func main(){
P :=Person{"tom"}
test02(&p)
三、区别3
对于方法(如struct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以
为person定义方法,有两种调用方法,一种是值类型调用,func (p Person) teste3(){fmt.Print1n("teste3()=”,p.Name)}
,进行调用时可以直接通过 p.teste03();一种是指针类型,调用时可以通过,运行发现输出为 tom,输入也可以进行调用,因为此时编译器底层进行了优化,也就是说虽然对 person 进行了绑定,但是输入地址也能够调用 test03,但是传入进去之后仍然进行值的拷贝,运行之后观察效果,可以看到打印出两个 tom,即虽然在调用时填入了地址 func (p Person) test03() {中的p不会指向外面的p,仍然会进行值的拷贝。此处极其容易混淆,意味着面试题中也会经常出现该类型题目,例如曾经的一道面试题,如果此时增添以下代码:p.Name = "jack",利用 p.test03进行调用。由于该方法进行了值拷贝,那么这个地方的改变肯定不会影响到外面的 tom,即 test03的输出结果为 Jack,而主函数的输出结果仍为 tom,输入 fmt.Print1n("main() p.name=", p.Name)进行验证,输出结果与理论效果一致。注意输入(&p).test03()进行调用时,只是形式上传入了地址,但是本质仍然是值拷贝。即编译器在做优化时会将 (&p).test03()改成 p.test03()的形式。
核心代码:
func (p Person) test03() {
p.Name = "jack"
fmt.Println("test03() ="",p.Name)
//此时输出为jack
}
p.test03()
fmt.Print1n("main() p.name=", p.Name)
//此时输出为tom
(&p).test03()
//从形式上是传入地址,但是本质仍然是值拷贝
同样反过来也是一样的,创建test04,把它改成指针类型后,它也可以通过值类型或者是地址类型进行调用,为了显示出区别,将 test04的name 改成 marry,此时是与指针类型绑定的方法,按照正规的方法应该输入 (&p).test04()进行调用,因为是指针类型,调用时 test 与 main 函数都会输出 marry;而通过 p.test04()进行调用也是可以的,因为底层的编译器会进行优化处理,将 p.test04()等价于 (&p).test04(),运行后发现输出效果是一样的。同样的道理,p.test04()等价于(&p).test04,从形式上是传入值类型,但是本质仍然是地址拷贝,其关键的原因在于 test04 中与其绑定的类型为指针类型,因此在调用时的本质仍然是地址拷贝
核心代码:
func (p*Person) test04(){
p.Name = "mary"
fmt.Print1n("test03()=",p.Name)
//此时输出为mary
}
(&p).test04()
fmt.Print1n("main() p.name=", p.Name)
//此时输出为mary
p.test04()// 等价于 (&p).test04 //从形式上是传入值类型,但是本质仍然是地址拷贝
总结
对于普通的函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然;但是如果是定义一个方法,接受者为值类型时,既可以用值类型进行调用,也可以用指针类型进行调用,但是最终决定权取决于func()(接收者)中的内容,如果接收者是值类型,则在调用时无论是以值类型进行调用,还是以指针类型进行调用,调用的本质都是进行值的拷贝;反之,如果接收者是指针类型,则在调用时无论是以值类型进行调用,还是以指针类型进行调用,调用的本质都是进行地址的拷贝。其实最初golang的设计者这样的构思主要是为了使用方便,但在后来的实际应用中发现,这样的设计理念反而更加容易混淆概念。