Kodewick
开始学 →
第 5 课,共 24 课目录

函数

第 5 课,共 24 课 · 第 1 部分 · 起步 · 约 15 分钟

同一段代码要在好几个地方用,就把它包成一个函数,起个名字,用的时候叫它的名字。main 就是一个函数;下面再写一个:

prog.go
1package main2 3import "fmt"4 5func greet(name string) {6	fmt.Println("你好," + name)7}8 9func main() {10	greet("小林")11	greet("小陈")12}
输出
你好,小林
你好,小陈

func greet(name string) 定义了一个叫 greet 的函数。name 是参数,类型写在名字后面:name string。greet("小林") 是调用它,"小林" 是实参,进了函数就是 name。函数写在 main 前面后面都行。

返回值

函数可以算出一个值交回去:在括号后面写上返回值的类型,用 return 把值交回给调用它的地方:

prog.go
1package main2 3import "fmt"4 5func total(price int, count int) int {6	return price * count7}8 9func main() {10	fmt.Println(total(25, 4))11	sum := total(12, 3) + total(8, 5)12	fmt.Println(sum)13}
输出
100
76

total(25, 4) 这个调用本身就是一个值,可以显示,可以参加计算,也可以放进变量。几个参数类型一样时可以合着写:func total(price, count int) int。

少了 return

说了要返回 string,就每条路都要有 return。Go 编译器会检查:

prog.go
1package main2 3import "fmt"4 5func grade(score int) string {6	if score >= 60 {7		return "及格"8	}9}10 11func main() {12	fmt.Println(grade(72))13}
输出
# command-line-arguments
./prog.go:9:1: missing return

发生了什么: 函数说好要返回值,可是有一条路走到最后也没有 return。

./prog.go:9:1: missing return 指着第 9 行,grade 结尾的那个 }:分数不到 60 的时候,函数走到这里还什么都没返回。在 } 前面加一行 return "补考" 就好了。

多个返回值

Go 的函数可以一次返回好几个值。返回值的类型用括号括起来,return 后面用逗号隔开,接的时候也用逗号:

prog.go
1package main2 3import "fmt"4 5func divmod(a, b int) (int, int) {6	return a / b, a % b7}8 9func main() {10	q, r := divmod(7, 3)11	fmt.Println(q, r)12	hours, minutes := divmod(135, 60)13	fmt.Println(hours, "小时", minutes, "分钟")14}
输出
2 1
2 小时 15 分钟

divmod 同时给出商和余数:135 分钟是 2 小时 15 分钟。

用 _ 丢掉不要的

有时候只想要其中一个值。可是两个都接住、只用一个,就碰上了老问题:

prog.go
1package main2 3import "fmt"4 5func divmod(a, b int) (int, int) {6	return a / b, a % b7}8 9func main() {10	hours, minutes := divmod(135, 60)11	fmt.Println(minutes, "分钟")12}
输出
# command-line-arguments
./prog.go:10:2: declared and not used: hours

发生了什么: 变量 hours 声明了却没有用到。Go 不允许这样:用上它,或者删掉它(不想要的值可以给 _)。

hours 声明了没用,编译不过。不要的那个写成 _,它是个「扔掉」的位置,不算变量:

prog.go
1package main2 3import "fmt"4 5func split(total, people int) (each, left int) {6	each = total / people7	left = total % people8	return9}10 11func main() {12	each, left := split(100, 3)13	fmt.Println("每人", each, "剩", left)14	_, left = split(50, 4)15	fmt.Println("剩", left)16}
输出
每人 33 剩 1
剩 2

split 还用了命名返回值:(each, left int) 给两个返回值起了名字,它们一开始是零值,在函数里像普通变量一样赋值,最后一个光秃秃的 return 就把它们交回去。短小的函数里偶尔这样写,平时还是 return 值 更清楚。

函数拿到的是一份复制

先猜一猜

这段程序显示什么?

1package main2 3import "fmt"4 5func double(n int) {6	n = n * 27}8 9func main() {10	price := 3011	double(price)12	fmt.Println(price)13}

Go 调用函数时把实参的值复制一份给参数,这叫传值。点单步,看右边:进了 addBonus,变量面板上多出一层,有它自己的 score;两个 score 名字一样,却是两个格子。

prog.go
1package main2 3import "fmt"4 5func addBonus(score int) {6	score = score + 107	fmt.Println("函数里", score)8}9 10func main() {11	score := 8012	addBonus(score)13	fmt.Println("main 里", score)14}
输出
函数里 90
main 里 80

函数回来以后,它那一层就没有了。想让 main 拿到新的值,就让函数把它 return 回来,再赋值:score = addBonus(score)(这样 addBonus 就要写成返回 int:func addBonus(score int) int,最后 return score)。(还有一种办法是指针,第 11 课讲。)

可变参数

fmt.Println 可以收一个、两个、很多个值。自己的函数也可以:最后一个参数的类型前面写 ...,调用时给几个都行,一个不给也行:

prog.go
1package main2 3import "fmt"4 5func sum(nums ...int) int {6	total := 07	for _, n := range nums {8		total += n9	}10	return total11}12 13func main() {14	fmt.Println(sum(12, 30, 8))15	fmt.Println(sum(99))16	fmt.Println(sum())17}
输出
50
99
0

在函数里,nums 是装着这些值的一个切片(第 6 课细讲)。for _, n := range nums 把它们一个一个拿出来放进 n,前面的 _ 丢掉的是位置编号,第 6 课也会讲到。

练一练

写好代码,点检查。提示随便看,实在想不出来再看答案。

练习 1

打折以后多少钱

写好 discount:原价 price 打掉 percent%,返回打折以后的价(整数)。要显示 170 和 49。

要显示
170
49
exercise1.go
1package main2 3import "fmt"4 5func discount(price, percent int) int {6	// 在这里算7	return 0 // 先占个位,换成你算出来的价8}9 10func main() {11	fmt.Println(discount(200, 15))12	fmt.Println(discount(99, 50))13}
输出
点「运行」或按 Ctrl+Enter
练习 2

三个温度里最低和最高

写好 minMax,同时返回三个数里最小的和最大的。要显示 最低 -3 最高 8 和 最低 15 最高 27。

要显示
最低 -3 最高 8
最低 15 最高 27
exercise2.go
1package main2 3import "fmt"4 5func minMax(a, b, c int) (int, int) {6	return 0, 07}8 9func main() {10	low, high := minMax(5, -3, 8)11	fmt.Println("最低", low, "最高", high)12	low, high = minMax(27, 15, 22)13	fmt.Println("最低", low, "最高", high)14}
输出
点「运行」或按 Ctrl+Enter
练习 3

修好它:加了税,价格没变

addTax 想把价格加上 10% 的税,可是 main 里的 price 一直没变。改好它,显示 含税 220 和 含税 60。

要显示
含税 220
含税 60
exercise3.go
1package main2 3import "fmt"4 5func addTax(price int) {6	price = price * 110 / 1007}8 9func main() {10	price := 20011	addTax(price)12	fmt.Println("含税", price)13	price = 5514	addTax(price)15	fmt.Println("含税", price)16}
输出
点「运行」或按 Ctrl+Enter

这一课学会了

  • func 名字(参数 类型) 返回类型 { … }:类型写在名字后面;类型一样的参数可以合着写 a, b int。
  • return 把值交回去;说了有返回值,每条路都要 return,不然是 missing return。
  • 多个返回值:func divmod(a, b int) (int, int),接的时候 q, r := divmod(7, 3);不要的写 _。
  • 命名返回值 (each, left int) 先是零值,光写 return 就交回它们。
  • 传值:参数是实参的一份复制,函数里改它不影响调用的地方;要新值就 return 回来。
  • 可变参数 nums ...int 能收任意多个值,在函数里是一个切片。
这一课学完了吗?点一下标记。进度存在这个浏览器里,随时可以撤销。