第5章
函数与方法 
函数是执行计算的命名语句序列。将一段代码封装为函数并在需要的位置进行调用, 
不仅可以实现代码的重复利用,更重要的是可以保证代码完全一致。
5.1 函数的定义与使用
5.1.1 函数的定义 
在Go语言中,定义函数的语法格式如下: 
func funcName( [arg argType] ) [returnType] { 
function body 
} 
其中,funcName是函数名,arg是形式参数,argType是形式参数的类型,returnType是返
回值的类型。Go语言使用关键字func定义函数(Function)。func后面有一个空格,然后
是函数名,接下来是一对小括号,小括号里是可选的形式参数及其对应的类型,小括号后面
是可选的返回值及其类型,最后是一对大括号括起来的函数体。函数名、形式参数类型和返
回值类型三者共同构成了函数签名。形参名以及返回值的变量名不属于函数签名。定义函
数时还需要注意如下几个问题: 
(1)一个函数即使不需要接收任何参数,也必须保留一对小括号; 
(2)如果一个函数没有返回值,则返回值可省略(包括小括号)。 
func f1() { } //没有形参,没有返回值
func f2(a int) { } //一个形参,没有返回值
//两个形参,一个返回值,返回一个整数
func f3(a int, b int) int { return 1 } 
func f4(x float64) (y int, z float64) { //一个形参,两个返回值 
return 1, 2 
}
举例:自定义函数bigger()。 
func main() { 
var a, b int = 10, 20 //定义局部变量a 和b 
var result int 
result = bigger(a, b)

44 Go语言程序设计教程 
fmt.Printf("较大值: %d\n", result) 
} /*函数的返回值是两个形式参数(Formal Parameter)x 和y 中的较大者*/ 
func bigger(x, y int) int { //函数签名bigger(int, int) int 
if x > y { 
return x 
} 
return y 
}
上述代码的输出结果: 
较大值: 20 
当一组形参或返回值的数据类型相同时,可不必逐个为它们标明数据类型。下面两个
函数声明是等价的,即它们的函数签名相同。 
func f(a, b int, m, n string) { … } 
func f(a int, b int, m string, n string) { … } 
举例: 
func main() { 
//输出数据类型使用格式控制符%T,Type 
fmt.Printf("%T\n", add) 
fmt.Printf("%T\n", sub) 
fmt.Printf("%T\n", first) 
fmt.Printf("%T\n", zero) 
}f
unc 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 } 
上述代码的输出结果: 
func(int, int) int 
func(int, int) int 
func(int, int) int 
func(int, int) int 
5.1.2 函数的调用
函数定义完毕并不能自动运行,只有被调用(Call)时才能运行。下面的代码用整数10 
和20调用bigger()函数,该函数的返回值被赋值给变量result。 
result = bigger(10, 20) 
上述调用bigger()函数时使用的整数10和20是实际参数(ActualParameter),简称实
参。形参没有具体的值,形参的值来自实参。调用函数时必须按照声明的顺序为所有的形
参赋值。Go语言的形参不能带有默认值,这点与Python语言等不同。

第5章 函数与方法 45 
实参通过值传递的方式为形参赋值,形参是实参的一个副本,对形参执行操作通常不会
影响实参的值。但是,如果实参是引用类型,如指针、切片slice、投影map,则可以通过形参
修改实参的值。
举例: 
func modify(z *int) { //形参z 是指针类型 
*z = 20 
}f
unc main() { 
var x int = 10 
fmt.Printf("调用函数前x 的值= %d\n", x) 
modify(&x) //实参&x 是引用类型 
fmt.Printf("调用函数后x 的值= %d\n", x) 
}
上述代码的输出结果: 
调用函数前x 的值= 10 
调用函数后x 的值= 20 
5.1.3 函数的返回值
通常,定义一个函数是希望它能够返回一个或多个计算结果,这在Go语言中是通过关
键字return实现的。无论return语句出现在函数的什么位置,一旦被执行,它都会立即结
束函数的执行过程。Go语言支持函数返回多个值。 
func twoRetValues() (int, int) { //返回值必须与return 语句相匹配 
return 1, 2 //不能写作(1, 2) 
}f
unc main() { 
a, b := twoRetValues() 
fmt.Println(a, b) 
}
上述代码的执行结果: 
1 2 
与形参名一样,Go语言支持对返回值进行命名,此时需要在函数体中显式地使用
return语句返回。 
func namedRetValues() (x, y int) { //两个返回值被命名为x 和y 
x = 2 
y = 1 
return //return 语句可为空,等价于return x, y 
}f
unc main() { 
a, b := namedRetValues() 
fmt.Println(a, b) 
}

46 Go语言程序设计教程
上述代码的输出结果: 
2 1 
main()函数是一种特殊类型的函数,它不接收任何参数,也没有返回值。main()函数
是一个可执行程序的入口。Go编译器会自动调用main()函数,因此不需要显式地调用它。
main包是一个特殊的包。一个可执行程序必须拥有一个main包,而且在该包中必须有且
只能有一个main()函数。
与main()函数类似,init()函数不接收任何参数,也没有返回值。每个包中可以包含一
个或多个init()函数。init()函数按照出现的先后顺序依次执行。init()函数也不需要显式
地调用,Go编译器会自动调用它。init()函数在main()函数之前执行,它的主要用途是初
始化全局变量。如果发生多个包的嵌套引用,则最后导入的包会最先执行其包含的init() 
函数。
5.2 lambda 函数
lambda函数又称为匿名函数。匿名函数没有函数名,只有函数体。定义匿名函数的语
法格式如下: 
func ( [arg argType] ) [returnType] { 
function body 
}
(1)在定义的同时调用匿名函数 
func main() { 
func(x int) { //函数嵌套 
fmt.Println(x) 
}(10) 
}
上述代码的输出结果: 
10 
(2)将匿名函数赋值给一个变量 
func main() { 
f := func(x int) { //将匿名函数赋值给变量f 
fmt.Println(x) 
} 
f(10) //使用f()调用匿名函数
}
上述代码的输出结果: 
10 
再举一个例子:

第5章 函数与方法 47 
func main() { 
add := func(x int) int { 
return x + 1 
} 
fmt.Println(add(10)) 
}
上述代码的输出结果: 
11 
5.3 闭包
可以将闭包(Closure)理解为定义在一个函数内部的匿名函数。本质上,闭包是连接匿
名函数内部与外部的桥梁。闭包对外部环境中变量的引用过程称为“捕获”。闭包可以用一
个简单的公式表示如下: 
闭包= 匿名函数+ 引用的外部环境
举例: 
func main() { 
i := 42 //声明一个整型变量i 
f := func() { //将匿名函数赋值给变量f 
j := i / 2 //访问外部变量i 
fmt.Println(j) 
} 
f() //输出21 
}
上述代码创建了包含整型变量i的匿名函数f的闭包。函数f可以直接访问变量i,这
是闭包的属性。 
func f() func() int { 
i := 0 
return func() int { //函数f()的返回值是一个匿名函数 
i += 1 
return i 
} 
}f
unc main() { 
a := f() //闭包a 
b := f() //闭包b 
fmt.Println(a()) //输出1 
fmt.Println(b()) //输出1 
b() 
fmt.Println(a()) //输出2 
fmt.Println(b()) //输出3 
}
当需要创建一个对状态进行封装的函数时,就需要使用闭包。闭包可以实现很多高级

48 Go语言程序设计教程
的功能,如创建一个生成器,限于篇幅,本书不再讲述。
5.4 defer 语句
defer将其后的语句进行延迟处理。在defer语句所属的函数返回之前,将延迟处理语
句按照后进先出(LastInFirstOut,LIFO)的顺序执行。
举例: 
func main() { 
defer fmt.Printf("%s\n", "first") 
defer fmt.Printf("%s ", "second") 
defer fmt.Printf("%s ", "third") //最后进入,最先出来 
fmt.Println("exit") 
}
上述代码的输出结果: 
exit 
third second first 
defer语句一般用于释放某些已分配的系统资源,如关闭文件。下面定义函数fileSize() 
用于打开并获取一个文件的大小。
举例: 
func fileSize(fileName string) int64 { 
f, err := os.Open(fileName) 
if err != nil { 
return 0 
} 
//延迟调用Close()函数,此刻不会调用它 
defer f.Close() 
info, err := f.Stat() //统计Statistics 
if err != nil { 
return 0 //函数返回前,会调用Close()函数关闭文件 
} 
size := info.Size() 
return size //函数返回前,会调用Close()函数关闭文件
}
在main()主函数中调用fileSize()函数。 
func main() { 
size := fileSize("test.txt") 
fmt.Printf("File size = %d", size) 
}
上述代码的输出结果: 
File size = 25 
在上述例子中,如果没有defer语句,则在fileSize()函数中后两个return语句的前面都

第5章 函数与方法 49 
必须添加语句f.Close()关闭文件,以释放系统资源。读者是否能通过上述例子,总结出
defer语句的优点? 
5.5 递归函数
什么是算法? 简单地说,算法是解决问题的方法与步骤。递归算法(RecursiveAlgorithm) 
的核心思想是分治策略。分治是“分而治之”(DivideandConquer)的意思。分治策略将一
个复杂的问题反复分解为两个或更多个相同的或相似的子问题,直至这些子问题可以直接
求解,最后将子问题的解合并起来,就能得到原问题的解,如图5-1所示。
图5-1 分治策略
一个函数在其函数体内部调用它自身,这种函数叫作递归函数。递归函数使用了分治
策略,其由终止条件和递归条件两部分组成。下面定义一个计算阶乘的函数factorial(n): 
func factorial(n int) int { //阶乘factorial 
if n <= 1 { //终止条件 
return 1 
} 
return n * factorial(n-1) //递归条件
}f
unc main() { 
var n int = 5 
fmt.Println(factorial(n)) //输出120 
}
调用上述定义的factorial(n)函数计算5的阶乘,其执行流程如下所示: 
factorial(5) = 5 * factorial(4) 
= 5 * 4 * factorial(3) 
= 5 * 4 * 3 * factorial(2) 
= 5 * 4 * 3 * 2 * factorial(1) 
= 5 * 4 * 3 * 2 * 1 
= 120

50 Go语言程序设计教程
下面定义一个计算斐波那契数列(Fibonacci)的递归函数fib(n): 
func fib(n int) int { 
if n <= 1 { //终止条件 
return n 
} 
return fib(n-1) + fib(n-2) //递归条件
}f
unc main() { 
var n int = 10 
for i := 0; i <= n; i++ { 
fmt.Printf("%d ", fib(i)) 
} 
fmt.Println() 
}
上述代码的输出结果: 
0 1 1 2 3 5 8 13 21 34 55 
5.6 可变长度参数
与Python语言类似,Go语言也支持可变长度参数。也就是说,一个函数可以接收任意
数量的实际参数。
举例: 
func multiply(args …int) int { //形参类型是int 型 
z := 1 
for _, arg := range args { 
z *= arg 
} 
return z 
}f
unc main() { 
fmt.Println(multiply(2, 3, 4)) //输出24 
fmt.Println(multiply(4, 5)) //输出20 
fmt.Println(multiply(10, 9)) //输出90 
}
…int本质上是一个切片,也就是[]int,因此可以将其用在for循环中。如果想传入任
意类型的数据,则需要将int修改为空接口interface{}。
举例: 
func what(args …interface{}) { 
for _, arg := range args { 
switch arg.(type) { 
case int: 
fmt.Println(arg, "int") 
case int64:

第5章 函数与方法 51 
fmt.Println(arg, "int64") 
case string: 
fmt.Println(arg, "string") 
default: 
fmt.Println(arg, "unknown") 
} 
} 
}f
unc main() { 
var val1 int = 1 
var val2 int64 = 2 
var val3 string = "good" 
var val4 float32 = 1.5 
what(val1, val2, val3, val4) 
}
上述代码的输出结果: 
1 int 
2 int64 
good string 
1.5 unknown 
5.7 方法
函数和方法分别是面向过程和面向对象编程范畴的概念。从某种角度说,Go是将两种
编程理念融为一体的语言。那么,在Go语言中怎样定义方法呢? 定义方法的语法格式
如下: 
func (receiver receiverType) methodName ( [arg argType] )( [returnType] ){ 
method body 
} 
其中,receiver是接受者的名称,receiverType是接受者的类型,methodName是方法名,arg 
是形式参数,argType是形式参数的类型,returnType是返回值的类型。显然,函数与方法
的区别之一是方法有接受者。在Go语言中,方法的接受者可以是结构体。
举例: 
type User struct { //定义结构体User 
name string 
email string 
}f
unc (u User) userInfo() string { //方法的接受者是User 类型 
return fmt.Sprintf("User name: %s and email: %s\n", u.name, u.email) 
}f
unc main() { 
user1 := User{name: "Hui", email: "whui2008@tust.edu.cn"} 
fmt.Print(user1.userInfo()) 
}

52 Go语言程序设计教程
上述代码的输出结果: 
User name: Hui and email: whui2008@tust.edu.cn 
在上述代码中,u是接受者的名称,User是接受者的类型(结构体),userInfo是方法名, 
形式参数及其类型为空,返回值的类型是string。
方法的接受者也可以是非结构体类型,如int。Go语言要求方法methodName()与接
受者类型receiverType必须定义在同一个包内。
举例: 
type myNumber int //定义整数类型myNumber 
func (num myNumber) square() myNumber { 
if num <= 1 { 
return 1 
} 
return num * num 
}f
unc main() { 
var n myNumber = 15 
result := n.square() 
fmt.Printf("The square of %d is %d.\n", n, result) 
}
上述代码的输出结果: 
The square of 15 is 225. 
如果读者删除代码行typemyNumberint,则编译器会给出错误信息“cannotdefine 
new methodsonnon-localtypeint”。
在Go语言中,函数也是一种数据类型,与其他数据类型一样可以将其赋值给变量。
举例: 
func demo() { 
fmt.Println("in demo()") 
}f
unc main() { 
//声明变量f 为func()函数类型,此时f = nil 
var f func() 
f = demo 
f() 
}
上述代码的输出结果: 
in demo() 
5.8 小结
函数是执行计算的命名语句序列,而方法则是有接受者的函数。定义函数,使用关键字
func。函数定义完毕后并不能自动运行,只有被调用时才能运行。参数分为形参和实参,形