概念
在Go中,所谓的函数(function)、过程(procedure)、subroutine都统称为function。关键字只有func。
前面已经多次自定义过func了,包括有返回值或没有返回值。没有返回值的函数,在诸如VB的语言中,就成为precedure/subroutine。
示例再现
这个例子包括了两种形式的函数:有返回值、无返回值。
package main
import "fmt"
/*
D:\examples>go run helloworld.go
1 2 3 4 5
average: 3
D:\examples>
*/
func main() {
x := [5]int {1,2,3,4,5}
debug_array(x)
fmt.Println("average:", get_average(x))
}
/*
cannot use x (type [5]int) as type []int in argument to debug_array
func debug_array(a[]int) {
...
}*/
func debug_array(a[5]int) {
for _, item := range a {
fmt.Print(item, "\t")
}
fmt.Println()
}
func get_average(a[5]int) int {
total := 0
for _, item := range a {
total += item
}
return total / len(a)
}
named return value
函数的返回值,除了指定数据类型,还可以指定一个名字,可视为变量名,且在函数体中可以使用。——这可以看做Go的一个特色。
注意如下例子中的get_min_max()的两个版本:函数声明中的返回值部分,以及函数体的return语句。
package main
import "fmt"
/*
D:\examples>go run helloworld.go
1 2 3 4 5
average: 3
min: 1 , max: 5
min: 1 , max: 5
D:\examples>
*/
func main() {
x := [5]int {1,2,3,4,5}
debug_array(x)
fmt.Println("average:", get_average(x))
the_min, the_max := get_min_max(x)
fmt.Println("min: ", the_min, ", max: ", the_max)
the_min, the_max = get_min_max_v2(x)
fmt.Println("min: ", the_min, ", max: ", the_max)
}
func debug_array(a[5]int) {
for _, item := range a {
fmt.Print(item, "\t")
}
fmt.Println()
}
func get_average(a[5]int) (average int) {
total := 0
for _, item := range a {
total += item
}
average = total / len(a)
return
}
func get_min_max(a[5]int) (min int, max int) {
min = a[0]
max = a[0]
for _, item := range a {
if (item < min) { min = item }
if (item > max) { max = item }
}
// the 'return' is required, or 'missing return at end of function'
return
}
func get_min_max_v2(a[5]int) (int, int) {
min := a[0]
max := a[0]
for _, item := range a {
if (item < min) { min = item }
if (item > max) { max = item }
}
// the 'return' is required, or 'missing return at end of function'
return min, max
}
通过这个例子可以看到,命名返回值在某些场合下还是很有用。其他编程语言是在函数体中定义临时变量,然后返回这个临时变量。而Go直接在函数声明中指定返回值的变量名,这样子在函数体中使用的时候,就具有更加明显的含义,特指其是要返回的数据。
顺便提及,Go的函数可以返回多个数值的用法和Python脚本语言一样,而和C/C++/Java不一样。后者需要打包到一个(比如struct)对象中,即只能返回至多一个对象。
另外,在前面描述Go-Maps的时候也用到了返回多个数值的场景。——value,ok:=the_map[the_key].
变参/可变长度参数列表/Variadic Functions
注意观察示例中的两种传参方式。
package main
import "fmt"
/*
D:\examples>go run helloworld.go
6
15
D:\examples>
*/
func main() {
//x := [5]int {1,2,3,4,5}
y := [ ]int {1,2,3,4,5}
fmt.Println(add(1,2,3))
//cannot use x (type [5]int) as type []int in argument to add
//fmt.Println(add(x...))
fmt.Println(add(y...))
}
func add(args ... int) int {
total := 0
for _, item := range args {
total += item
}
return total
}
Closure / 内部函数
所谓的函数式编程,中文通常译closure为闭包。closure涉及到内部函数(inner function),但closure又不仅仅是内部函数,它还包括了使用的上下文(如变量)。
比如下面的示例代码中,add_square和square_total构成了closure。而add则既为inner function,又为closure,此时它没有用到任何上下文。
package main
import "fmt"
/*
D:\examples>go run helloworld.go
total: 15
square total: 55
D:\examples>
*/
func main() {
x := [5]int {1,2,3,4,5}
test_total(x)
test_square_total(x)
}
func test_total(x [5]int) {
//Ooh, attention pls the puzzling 'x'.
add := func(x int, y int) int {
return x + y
}
total := 0
for _,item := range x {
total = add(total, item)
}
fmt.Println("total:", total)
}
func test_square_total(x [5]int) {
square_total := 0
add_squre := func(n int) {
square_total += n * n
}
for _,item := range x {
add_squre(item)
}
fmt.Println("square total:", square_total)
}
Closure与状态
下面是Introducing Go中的例子:
package main
import "fmt"
/*
D:\examples>go run helloworld.go
0
2
4
6
D:\examples>
*/
func main() {
nextEven := makeEvenGenerator()
fmt.Println(nextEven())
fmt.Println(nextEven())
fmt.Println(nextEven())
fmt.Println(nextEven())
}
func makeEvenGenerator() func() uint {
i := uint(0)
return func() (ret uint) {
ret = i
i += 2
return
}
}
这个例子较好地阐述了closure所谓状态的概念。也可以把closure看做functor(中文译为仿函数/函子/etc)或函数对象。即closure不仅仅具备函数的特征,而且具有状态。所谓状态,就是具有内部存储的数据。
在这个例子中,makeEvenGenerator()用来生成偶数数列。初始化的时候,其内部状态(类似数据成员)i初始化为0。因为makeEvenGenerator()的返回值是一个函数func() uint,所以每次调用nextEven()的时候,就是执行func() uint这个内部函数。第一次调用的时候,ret取i,故ret为0,即nextEven()返回0。在这个调用过程中,i得到了更新,变成接下来的一个偶数。。。如此,nextEven()的不断调用,就形成了一个偶数序列。
作为对比,下面是C++代码的例子:
#include <stdio.h>
class MakeEvenGenerator {
public:
MakeEvenGenerator() {
i = 0;
}
int operator()() {
int ret = i;
i += 2;
return ret;
}
private:
int i;
};
int main()
{
MakeEvenGenerator nextEven;
for (int i = 0; i < 5; i++) {
printf("%d\t", nextEven());
}
printf("\n");
}
运行结果:
0 2 4 6 8
请按任意键继续. . .
递归函数/Recursion
递归函数比较常见。同为函数式编程的内容,显然递归函数比闭包Closure更为大众所知。——因为即便thq版《C语言编程》都会讲递归,任何一本数据结构也会讲递归;但却没有多少学校的教科书会讲到Closure(集合论中的闭包就不在讨论范围了)。
当然,一旦了解了函数式编程,那么就会有使用的冲动。事实上,用函数式编程实现功能,往往更为简洁和清晰。
接下来给一个传统&经典的阶乘的例子结束本节。
package main
import "fmt"
func main() {
fmt.Println(factorial(5)) //120
}
func factorial(x uint) uint {
if (x == 0) {
return 1
}
return x * factorial(x-1)
}
函数参数列表
最后再补充描述Go函数参数列表。下面的示例代码会有编译错误,如注释部分。
package main
import "fmt"
func main() {
var x, y int
// cannot use "hello" (type string) as type int in assignment
x = "hello"
y = 3
foo(2, 3)
//cannot use "hello" (type string) as type int in argument to bar
bar("hello", 3)
}
func foo(x int, y int) int {
return x + y;
}
func bar(x, y int) {
// cannot use "hello" (type string) as type int in assignment
x = "hello"
fmt.Println(y)
}
在Introducing Go和The Go Programming Language中都没有明确描述这种规则。但后者给出了一个示例,拷贝如下(P120):
Here are four ways to declare a function wit h two parameters and one result, all of typ e int. The blank identifier can be used to emp hasize that a parameter is unu sed.
func add(x int, y int) int { return x + y }
func sub(x, y int) (z int) { z = x - y; return }
func first(x int, _ int) int { return x }
func zero(int, int) int { return 0 }
fmt.Printf("%T\n", add) // "func(int, int) int"
fmt.Printf("%T\n", sub) // "func(int, int) int"
fmt.Printf("%T\n", first) // "func(int, int) int"
fmt.Printf("%T\n", zero) // "func(int, int) int"
另Page30:
It is possible to declare and optionally initialize a set of variables in a single declaration, with a matching list of expressions. Omitting the type allows declaration of multiple variables of different types:
var i, j, k int // int, int, int
var b, f, s = true, 2.3, "four" // bool, float64, string
通过这里的说明,以及前面有编译错误的示例代码,可以得出结论:如果相邻的变量为同一种数据类型,只需要在最后一个变量后面写上类型,之前的都可以省略。——好的语言就是能够简化就简化,让coder把精力集中于业务领域。