函数是完成一个特定任务的代码块。一个函数接受输入,对输入进行一些运算并产生输出。
函数声明
在 Go 中声明一个函数的语法为:
func functionname(parametername type) returntype { //function body }
函数声明以关键字 func 开头,后面是函数名字,接着是在 ( 和 ) 之间指定的参数列表,然后是函数的返回类型。指定参数的语法为参数名称后面跟着参数类型。可以指定任意数量的参数,形式为: (parameter1 type, parameter2 type)
。最后是由 { 和 } 以及它们之间的代码块组成的函数体。
在一个函数中,参数和返回值是可选的。因此下面的语法也是合法的函数声明:
func functionname() {
}
函数的例子
让我们写一个函数,它以单个产品的价格和产品数量作为输入参数,并以总价格(单个产品的价格与产品数量的乘积)作为返回值。
func calculateBill(price int, no int) int { var totalPrice = price * no return totalPrice }
上面的函数接受两个 int
类型的输入参数:price
和 no
,并返回 totalPrice
(price
与 no
的乘积) 。返回值的类型也是 int
。
如果连续的参数具有相同的类型,我们可以避免每次都写出它们的类型,只需要在结束的时候写一次就可以了,比如:price int, no int
可以写成:price, no int
。 于是上面的函数可以写成:
func calculateBill(price, no int) int { var totalPrice = price * no return totalPrice }
我们已经定义好了我们函数,现在让我们在代码中调用它。调用函数的语法为:functionname(parameters)
。上面定义的函数 calculateBill
可以通过下面的代码调用:
calculateBill(10, 5)
下面是完整的程序,它调用 calculateBill
并计算总价格。
package main import ( "fmt" ) func calculateBill(price, no int) int { var totalPrice = price * no return totalPrice } func main() { price, no := 90, 6 totalPrice := calculateBill(price, no) fmt.Println("Total price is ", totalPrice) }
多个返回值
一个函数可以返回多个值。让我们写一个函数 rectProps
,它接受一个矩形的长和宽,并返回该矩形的面积和周长。矩形的面积为长与宽的积。周长为长与宽的和的 2 倍。
package main import ( "fmt" ) func rectProps(length, width float64)(float64, float64) { var area = length * width var perimeter = (length + width) * 2 return area, perimeter } func main() { area, perimeter := rectProps(10.8, 5.6) fmt.Printf("Area %f Perimeter %f", area, perimeter) }
如果一个函数有多个返回值,那么这些返回值应该用小括号()
括起来,比如:func rectProps(length, width float64)(float64, float64)
接受两个类型为 float64
的参数(length
和 width
),并且同样返回两个类型为 float64
的返回值。上面程序的输出为:Area 60.480000 Perimeter 32.800000
具名返回值
可以给一个函数的返回值指定名字。如果指定了一个返回值的名字,则可以视为在该函数的第一行中定义了该名字的变量。
上面的 rectProps
函数可以用具名返回值的形式重写如下:
func rectProps(length, width float64)(area, perimeter float64) { area = length * width perimeter = (length + width) * 2 return //no explicit return value }
在上面的函数中,area 和 perimeter 是具名返回值。注意 return
语句没有指定任何返回值。因为在函数声明时已经指定area
和 perimeter
是返回值,在遇到 return
语句时它们会自动从函数中返回。
空指示符
在 Go 中,下划线(_
)表示空指示符(blank identifier)。它可以用于代替任何类型的任何值。让我们看看如何使用空指示符。
我们知道上面定义的函数 rectProps
返回矩形的面积(area
)和周长(perimeter
)。如果我们只需要获取 area
而想要忽略 perimeter
该怎么办呢?这时候就可以使用空指示符。
下面的程序仅接收 rectProps
返回的 area
:
package main import ( "fmt" ) func rectProps(length, width float64) (float64, float64) { var area = length * width var perimeter = (length + width) * 2 return area, perimeter } func main() { area, _ := rectProps(10.8, 5.6) // perimeter is discarded fmt.Printf("Area %f ", area) }
在 area, _ := rectProps(10.8, 5.6)
这一行,我们仅仅获取了 area
,而使用空指示符 _
来忽略第二个返回值(perimeter
)。
什么是变参函数?
变参函数是指可以接受可变数量的参数的函数。
语法
如果一个函数的最后一个参数由 ...T
表示,则表示该函数可以接受任意数量的类型为 T
的参数。
请注意只有函数的最后一个参数才能指定为可变参数。
例子
你有没有想过为什么 append 函数可以将任意数量的值追加到切片末尾?这是因为它是一个变参函数。append
的原型为 func append(slice []Type, elems ...Type) []Type
,其中 elems
是一个可变参数。
让我们来创建一个自己的变参函数。我们将编写一个程序来判断某个特定整数是否包含在某个整数列表中。
package main import ( "fmt" ) func find(num int, nums ...int) { fmt.Printf("type of nums is %T\n", nums) found := false for i, v := range nums { if v == num { fmt.Println(num, "found at index", i, "in", nums) found = true } } if !found { fmt.Println(num, "not found in ", nums) } fmt.Printf("\n") } func main() { find(89, 89, 90, 95) find(45, 56, 67, 45, 90, 109) find(78, 38, 56, 98) find(87) }
上面的程序中,func find(num int, nums ...int)
可以接受任意数量的参数。...int
在内部表示为切片。在这里 nums
的类型为 []int
。
第 10 行利用 range for 遍历 nums
切片,如果找到 num
则打印 num
所在位置,否则打印没有找到。
上面的程序输出如下:
type of nums is []int 89 found at index 0 in [89 90 95] type of nums is []int 45 found at index 2 in [56 67 45 90 109] type of nums is []int 78 not found in [38 56 98] type of nums is []int 87 not found in []
在第 25 行,find
只有一个参数。我们没有传递任何参数给 nums ...int
。这是合法的,(译者注:如果没有给可变参数传递任何值,则可变参数为 nil 切片),在这里 nums
是一个 nil
切片,长度和容量都是0。
传递切片给可变参数
我们已经提到 ...T
在内部表示为类型是 []T
切片。如果真是这样,可以传递一个切片给可变参数吗?让我们从下面的例子中寻找答案:
package main import ( "fmt" ) func find(num int, nums ...int) { fmt.Printf("type of nums is %T\n", nums) found := false for i, v := range nums { if v == num { fmt.Println(num, "found at index", i, "in", nums) found = true } } if !found { fmt.Println(num, "not found in ", nums) } fmt.Printf("\n") } func main() { nums := []int{89, 90, 95} find(89, nums) }
在第 23 行,我们没有将若干数量的参数传递给 find
的最后一个参数, 而是传递了一个切片。这是非法的,我们不能传递一个切片给可变参数。上面的程序将报错:main.go:23: cannot use nums (type []int) as type int in argument to find
。
这里有一个语法糖可以用来将切片传递给变参函数。可以在切片后面加 ...
,这样会将切片展开为其中的各个元素并将它们传递给变参函数。这样该程序将正常工作。
上面的程序如果将第23行的 find(89, nums)
改为 find(89, nums...)
,程序将通过编译,并输出如下:
type of nums is []int 89 found at index 0 in [89 90 95]