函数让你可以把代码组织成一个可复用的单元。
它由func关键字、函数的名称,以及括号中以逗号分隔的零个或多个形参及其类型组成。
所有形参都必须显式声明类型;形参没有类型推断。 形参也没有默认值,因此函数的所有形参都是必填的。
import "fmt"
// No parameters
func PrintHello() {
fmt.Println("Hello")
}
// One parameter
func PrintHelloName(name string) {
fmt.Println("Hello " + name)
}
同一类型的形参可以一起声明,后面只跟一个类型声明。
import "fmt"
func PrintGreetingName(greeting, name string) {
fmt.Println(greeting + " " + name)
}
我们快速看一下两个经常被混淆的术语:parameters 和 arguments。
函数形参是函数签名中定义的名称,比如上面PrintGreetingName函数里的greeting和name。
函数实参是我们在调用函数时传给形参的具体值。
例如,在下面的例子里,"Hello"和"Katrina"就是传给greeting和name这两个形参的实参:
PrintGreetingName("Hello", "Katrina")
函数形参之后是零个或多个返回值,它们同样必须显式声明类型。
单个返回值直接书写,多个返回值则用括号包起来。
函数使用 return 关键字把值返回给调用它的代码。
一个函数里可以有多个return语句。
函数一旦执行到其中某个return语句,就会立即结束。
如果函数要返回多个值,它们之间用逗号分隔。
关于多个返回值的惯用写法,可以在链接的概念页里找到更多信息。
func Hello(name string) string {
return "Hello " + name
}
func HelloAndGoodbye(name string) (string, string) {
return "Hello " + name, "Goodbye " + name
}
调用函数时,需要写出函数名,并在括号中为函数的每个形参传入实参。
import "fmt"
// No parameters, no return value
func PrintHello() {
fmt.Println("Hello")
}
// Called like this:
PrintHello()
// One parameter, one return value
func Hello(name string) string {
return "Hello " + name
}
// Called like this:
greeting := Hello("Dave")
// Multiple parameters, multiple return values
func SumAndMultiply(a, b int) (int, int) {
return a+b, a*b
}
// Called like this:
aplusb, atimesb := SumAndMultiply(a, b)
和形参一样,返回值也可以选择命名。
如果使用了命名返回值,那么不带实参的return语句就会返回这些值。
这被称为“裸返回”。
func SumAndMultiplyThenMinus(a, b, c int) (sum, mult int) {
sum, mult = a+b, a*b
sum -= c
mult -= c
return
}
同样重要的是弄清楚值传递和引用传递这两个概念。
首先,我们来说明值传递。
在下面的例子里,当我们把变量val传给函数MultiplyByTwo时,传的其实是val的一份副本。
因此,newVal的值是更新后的4,但原来的变量val仍然是2。
在幕后,Go 实际上复制了原始值,所以函数只修改了这份副本,也就是v。
val := 2
func MultiplyByTwo(v int) int {
v = v * 2
return v
}
newVal := MultiplyByTwo(val)
// newval is 4, val is still 2 because only a copy of its value was passed into the function
严格来说,在 Go 里所有实参都是按值传递的,也就是说,会把提供给函数的值或数据复制一份。
但如果你不想复制传给函数的数据,而是想在函数里修改这份数据,那就应该用pointers作为实参,也就是按引用传递。
我们使用pointer来实现按引用传递。
把pointer实参传进函数,我们就能修改传进函数的底层数据,而不只是操作数据的一份副本。
目前,你只需要知道,指针类型可以通过函数签名中类型前面的*来识别。
func HandlePointers(x, y *int) {
// Some logic to handle integer values referenced by pointers x and y
}
如果pointer这个概念让你感到困惑,别担心。
教学大纲后面有专门的一节,帮你掌握指针。
注意,slices和maps是上述规则的例外。
当我们把slice或map作为实参传进函数时,它们会被当作指针类型处理,即使类型里并没有显式的*。
这意味着,如果我们把一个切片或映射传进函数并修改它的底层数据,这些改动会反映到原来的切片或映射上。
在 Go 里,函数被视为一等公民,这让它们非常强大。 围绕函数还有很多其他概念,比如方法和匿名函数,你会在之后的学习旅程中遇到。