说明
在很多的语言当中,函数就是方法,例如Java。但是在go语言当中,函数和方法不太一样,有明确的概念区分。go中,函数是指不属于任何结构体、类型的方法,也就是说,函数是没有接收者的;而方法有接收者。我们在go中说的方法要么属于一个结构体,要么属于一个新定义的类型。
函数
函数和方法,虽然在go中的概念不相同,但是二者的定义非常类似。函数的定义声明没有接收者,所以我们可以直接在go文件中,go的包里面声明定义函数。
func main() {
sum := add(2,3)
fmt.Println(sum)
}
func add(x,y int) int {
return x + y
}
在上面的代码中,我们定义了一个加法函数,它的函数签名是func add(a,b int) int
,没有接收者。直接是在go的包中被定义,定义完成后可以直接调用。
在上面的代码中add函数的名称采用了小写的形式,所以根据go语言的规定它的作用域只限于函数声明的包内使用,在其他的包内并不能使用。如果希望这个函数在其他的go的包当中被使用就需要将函数名改为以大写字母开头,这样这个函数就可以被其他的包调用,这也是go当中标识符首字母大写的作用。
func Add(a,b int) int {
return a + b
}
上述的函数Add因为首字母大写,所以这个函数可以被其他的包调用。
方法
方法的声明和函数非常的类似,区别是:方法在定义的时候,会在func 和 方法名之间增加一个参数,这个参数就是接收者,这样我们定义的这个方法就会和接收者绑定在一起,可以称之为是接收者的方法。
type student struct {
name string
}
func (s student) SayHello() {
fmt.Println("hello,world")
}
在上面的代码中,我们给结构体student定义了一个方法,此时结构体student就是接收者,而SayHello就是属于student这个结构体的方法。
那么这个方法该如何使用呢?
zhangsan := student{"张三"}
zhangsan.sayHello()
上面的代码中,我们先需要设置一个变量为student类型,在初始化之后就可以调用SayHello方法。
在go语言当中,有两种类型的接收者:值接收者和指针接收者。在上面的代码中我们使用的是值接收者,相当于在调用时,使用的是值接收者的一个副本,所以在方法中对该值的任何操作,都不会影响原来的类型变量。
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 = "李四"
}
在上面的代码中,打印出来的结果依旧是张三,对其进行的修改是没有效果的。如果我们使用一个指针作为接收者,那么就会有作用了。因为指针接收者传递的是一个指向原值指针的副本,指针的副本,指向的还是原来类型的值,所以在修改的时候,同时也会影响原来类型变量的值。
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 = "李四"
}
在上面的代码中,只是将接收者改为了指针接收者,就完成了修改。
在上面的例子中,有没有发现,我们在调用指针接收者方法的时候,使用的也是一个值的变量,并不是一个指针,如果我们使用下面的也是可以的。
p:=person{name:"张三"}
(&p).modify() //指针接收者,修改有效
这样也是可以的。如果我们没有这么强制使用指针进行调用,Go的编译器自动会帮我们取指针,以满足接收者的要求。
同样的,如果是一个值接收者的方法,使用指针也是可以调用的,Go编译器自动会解引用,以满足接收者的要求,比如例子中定义的String()方法,也可以这么调用:
p:=person{name:"张三"}
fmt.Println((&p).String())
总之,方法的调用,既可以使用值,也可以使用指针,我们不必要严格的遵守这些,Go语言编译器会帮我们进行自动转义的,这大大方便了我们开发者。
不管是使用值接收者,还是指针接收者,一定要搞清楚类型的本质:对类型进行操作的时候,是要改变当前值,还是要创建一个新值进行返回?这些就可以决定我们是采用值传递,还是指针传递。
可变参数
函数方法的参数,可以是任意多个,这种我们称之为可以变参数,比如我们常用的fmt.Println()这类函数,可以接收一个可变的参数。
func main() {
fmt.Println("1","2","3")
}
可以变参数,可以是任意多个。我们自己也可以定义可以变参数,可变参数的定义,在类型前加上省略号…即可。
func main() {
print("1","2","3")
}
func print (a ...interface{}){
for _,v:=range a{
fmt.Print(v)
}
fmt.Println()
}
例子中我们自己定义了一个接受可变参数的函数,效果和fmt.Println()一样。
可变参数本质上是一个数组,所以我们向使用数组一样使用它,比如例子中的 for range 循环。