Golang中函数和方法的区别【作用于值类型、指针类型】
在其他语言中,比如Java一般来说,函数就是方法,方法就是函数;
但是在Go语言中,函数和方法不太一样,有明确的概念区分。函数是指不属于任何结构体类型的方法,即函数是没有接受者的;而方法是有接收者的。
1、函数
func add (a, b int ) int { return a+b }
2、方法(特殊的函数)
func (t *T) add (a, b int ) int { return a+b }
其中T是自定义类型或者结构体,不能是基础类型int等。
3、方法的接收者
Go语言里方法有两种类型的接收者:值接收者和指针接收者。
1)、使用值类型接收者定义的方法,在调用的时候,使用的其实是值接收者的一个副本,所以对该值的任何操作,不会影响原来的类型变量。
func main() { p:=person{name:"张三"} p.modify() //值接收者,修改无效 fmt.Println(p.String()) } type person struct { name string } func (p person) String() string{ return "the person name is "+p.name } func (p person) modify(){ p.name = "李四" }
2)、如果我们使用一个指针作为接收者,那么就会起作用,因为指针接收者传递的是一个指向原值指针的副本,指针的副本指向的还是原来类型的值,所以修改时,同时也会影响原来类型变量的值。
func main() { p:=person{name:"张三"} p.modify() //指针接收者,修改有效 fmt.Println(p.String()) } type person struct { name string } func (p person) String() string{ return "the person name is "+p.name } func (p *person) modify(){ p.name = "李四" }
总结:
在调用方法的时候,传递的接收者本质上都是副本,一个是这个值副本,另一个是指向这个值指针的副本。指针具有指向原有值的特性,所以修改了指针指向的值,也就修改了原有的值。我们可以简单的理解为值接收者使用的是值的副本来调用方法,而指针接收者使用实际的值来调用方法。
4、如何选择 P 和 *P
一般的判断标准是看副本创建的成本和需求:
1)、不想变量被修改。 如果你不想变量被函数和方法所修改,那么选择类型P。相反,如果想修改原始的变量,则选择*P;
2)、如果变量是一个大的struct或者数组,则副本的创建相对会影响性能,这个时候考虑使用*P,只创建新的指针,这个区别是巨大的;
3)、(不针对函数参数,只针对本地变量/本地变量)对于函数作用域内的参数,如果定义成P,Go编译器尽量将对象分配到栈上,而*P很可能会分配到对象上,这对垃圾回收会有影响。
5、函数、方法作用于不同类型接收者的使用场景差异
1)、对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递;反之亦然。(接收者为指针类型时,不能将值类型的数据直接传递)
//1.普通函数 //接收值类型参数的函数 func valueIntTest(a int) int { return a + 10 } //接收指针类型参数的函数 func pointerIntTest(a *int) int { return *a + 10 } func structTest1() { a := 2 fmt.Println("valueIntTest:", valueIntTest(a)) //函数的参数为值类型,则不能直接将指针作为参数传递 //fmt.Println("valueIntTest:", valueIntTest(&a)) //compile error: cannot use &a (type *int) as type int in function argument b := 5 fmt.Println("pointerIntTest:", pointerIntTest(&b)) //同样,当函数的参数为指针类型时,也不能直接将值类型作为参数传递 //fmt.Println("pointerIntTest:", pointerIntTest(b)) //compile error:cannot use b (type int) as type *int in function argument }
2)、对于方法(如struct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以。(接收者为指针类型和值类型的方法,指针类型和值类型的变量均可相互调用)
//2.方法 type Person struct { id int name string } //接收者为值类型 func (p Person) valueShowName() { fmt.Println(p.name) } //接收者为指针类型 func (p *Person) pointShowName() { fmt.Println(p.name) } func structTest2() { //值类型调用方法 personValue := Person{101, "Will Smith"} personValue.valueShowName() personValue.pointShowName() //指针类型调用方法 personPointer := &Person{102, "Paul Tony"} personPointer.valueShowName() personPointer.pointShowName() //与普通函数不同,接收者为指针类型和值类型的方法,指针类型和值类型的变量均可相互调用 }