Go语言的函数
涛叔本文是Go语言入门教程的一部分。我认为函数是Go语言的基础单位。传统教材都是先讲 Hello World,这个时候有意回避函数的概念。紧接着就转到介绍类型系统。我认为可以直接从函数入手,因为Go程序本身就是一个函数(main),这样从逻辑上会更清晰一些。但因为没有类型系统的知识,所以只能强行跟读者假定int
或string
等基础类型。这是先讲函数的不足。现在开始正文内容。
Go语言是一种过程式语言,一个过程也叫一个函数。函数可能包含名字、入参和反回值三个部分。之所以说可能是因为每一个部分都不是必须的。举一个简单的例子:
func swap(a int, b int) (int, int) {
return b, a
}
函数以关键字func
开头,这个不能省略。swap
表示函数名。后面括号里的部分a int, b int
表示函数的入参列表。a
和b
表示参数名,int
表示参数的类型。我们会在下一节详细讨论 Go 的类型系统。现在大家只需要记住int
表示整数类型,a
和b
可以传入数字就可以了。函数可以有多个参数,也可以没有参数,但每个参数都应该有名字,也要指定类型。括号后面还有一对括号,表示函数的返回值列表。Go 语言的函数支持返回多个参数,这算是一个特色。返回值列表的结构跟入参列表一样,但可以不指定参数名。最后的{}
之间的部分就是函数过程。函数过程中可能包含多个return
表示函数执行结束。return
之后需要指定返回值,类型和数量都要跟返回值列表保持统一。
如果我们执行swap(1,2)
,函数会返回2
和1
两个值。
我们前面说过,函数的每一个部分都不是必须的。所以最简单的函数是func(){}
。它没有名字,没有入参,没有返回值,也没有函数过程。但它是一个合法的函数😂
函数之间可以相互调用,比如:
func add(a int, b int) int { return a + b }
func sub(a int, b int) int { return a - b }
func mul(a int, b int) int { return a * b }
// 计算 (a+b)*(a-b)
func foo(a int, b int) int {
return mul(add(a, b), sub(a, b))
}
其实以上代码都不能运行,因为 Go 语言要求每一个函数名都需要有一个包名(package)。而包名需要在源文件的开头指定:
package foo // 指定包名为 foo
func add(a int, b int) int { return a + b }
Go 实际调用函数的时候都是用「全名」,也就是包名+函数名的形式。比如上面的add
全名是foo.add
。一个 Go 程序其实就是一连串相互调用的函数。那程序在运行的时候需要从哪个函数开始呢?答案是main.main
函数。也就是说,操作系统会直接运行main.main
函数,我们可以在main.main
函数中调用其他各种函数并等待返回结果。所以典型的函数调用流程如下:
os -> main.main() +-> a.foo() --> b.bar()
|-> c.baz()
+-> d.zoo() +-> x.cos()
|-> y.sin()
main.main()
比较特殊,由操作系统直接调用,没有入参,也没有返回值。所以最小且可以执行的 Go 程序是:
package main
func main() {}
但这个程序什么功能也没有,操作系统执行main.main()
函数后马上就退出了。
再复杂一点的程序就是著名的 Hello World 程序:
package main
import "fmt"
func main() {
.Println("Hello, World!")
fmt}
我们前面讲每一个函数都需要定义包名。包名相同的函数可以直接使用函数名相互调用,比如前面的foo
和add
。但包名不相同的则必须使用全名调用。此外,如果要调用其他包名,还需要通过import
来导入对应的包。比如这里的import "fmt"
,就是导入名为fmt
的包。这是 Go 提供的标准包,用于格式化输出。
fmt.Println()
表示向标准输出设备(stdout)输出一行字符串,也就是Hello, World!
。我们把上面的代码保存到hello.go
文件,然后执行:
$ go run hello.go
Hello, World!
除了使用标准包外,我们还可以定义自己的包。但这部分内容涉及到包结构体系和工程化设计,并非现阶段的主要矛盾。等介绍完 Go 语言的核心知识后,我会再详细介绍包体系相关内容。接下来我们开始学习Go语言的类型系统。