函数是基于功能或者逻辑进行聚合的可复用的代码块。将一些复杂的、冗长的代码抽离封装成多个代码片段,即函数,有助于提高代码逻辑的可读性和可维护性。不同于 Python,由于 Go lang 是编译型语言,编译之后再运行,所以函数的定义顺序无关痛痒。
函数声明
在 Go lang 里,函数声明语法如下:
func function_name(parameter_list) (result_list) {
//函数逻辑
}
复制代码
这里使用 function 的简写形式 func 关键词,后面依次接 function_name(函数名) , parameter_list(参数列表) , result_list(返回值列表)以及函数体 。
parameter_list(参数列表)成员:函数的参数名以及参数类型,这些参数作为局部变量,其值由参数调用者提供,函数中的参数列表和返回值并非是必须的。
result_list(返回值列表):函数返回值的变量名以及类型,如果函数返回一个无名变量或者没有返回值,返回值列表的括号是可以省略的。
如果有连续若干个参数的类型一致,那么只需在最后一个参数后添加该类型:
package main
import "fmt"
// 函数返回一个无名变量,返回值列表的括号省略
func sum(x int, y int) int {
return x + y
}
// 无参数列表和返回值
func printBookName() {
fmt.Println("go lang1.18")
}
// 参数的类型一致,只在最后一个参数后添加该类型
func sub(x, y int) int {
return x - y
}
func main() {
fmt.Println("1 + 1 = ", sum(1, 1))
printBookName()
fmt.Println("2 - 1 =", sub(2, 1))
}
复制代码
程序返回:
command-line-arguments
1 + 1 = 2
go lang1.18
2 - 1 = 1
复制代码
不定长参数
和 Python 一样,Go lang 也支持不定长参数,即参数有多少个并不确定的情况。
在参数类型前面加 ... 表示一个切片,用来接收调用者传入的参数。注意,如果该函数下有其他类型的参数,这些其他参数必须放在参数列表的前面,切片必须放在最后:
package main
import "fmt"
func show(args ...string) int {
sum := 0
for _, item := range args {
fmt.Println(item)
sum += 1
}
return sum
}
func main() {
fmt.Println(show("1", "2", "3"))
}
复制代码
和 Python 的*args 用法差不多,但需要注意必须要声明函数的数据类型,程序返回:
如果传多个参数的数据类型都不一样,可以指定类型为 ...interface{} ,然后再进行遍历:
package main
import "fmt"
func PrintType(args ...interface{}) {
for _, arg := range args {
switch arg.(type) {
case int:
fmt.Println(arg, "type is int.")
case string:
fmt.Println(arg, "type is string.")
case float64:
fmt.Println(arg, "type is float64.")
default:
fmt.Println(arg, "is an unknown type.")
}
}
}
func main() {
PrintType(1, 3.1415, "go lang 1.18")
}
复制代码
此外,还可以使用 ... 可以用来解序列,能将函数的可变参数(即切片)一个一个取出来,传递给另一个可变参数的函数,而不是传递可变参数变量本身:
package main
import "fmt"
func main() {
var s []string
s = append(s, []string{"1", "2", "3"}...)
fmt.Println(s)
}
复制代码
这里将字符串切片取出来后,传递给内置的 append 方法,程序返回:
函数的返回值
一个函数可以没有返回值,也可以有一个返回值,也可以有返回多个值:
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func SumAndProduct(A, B int) (add int, Multiplied int) {
add = A + B
Multiplied = A * B
return
}
func main() {
a, b := swap("Mahesh", "Kumar")
fmt.Println(a, b)
fmt.Println(SumAndProduct(1, 2))
}
复制代码
程序返回:
_ 是 Go lang 里的空白标识符。它可以代替任何类型的任何值。我们可以利用它来忽略某些别人会用到但我们不会用到的函数返回值:
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)
}
复制代码
程序返回:
匿名函数
有点类似 Python 中的 lambda 表达式,但实际上并不是作为语法糖而存在:
package main
import (
"fmt"
)
func main() {
f := func() {
fmt.Println("hello world")
}
f() //hello world
fmt.Printf("%T\n", f) //打印 func()
}
复制代码
程序返回:
一望而知,只是匿名而已,但通过变量可调用,另外也可以拥有参数:
package main
import (
"fmt"
)
func main() {
f:=func(args string){
fmt.Println(args)
}
f("hello world")//hello world
//或
(func(args string){
fmt.Println(args)
})("hello world")//hello world
//或
func(args string) {
fmt.Println(args)
}("hello world") //hello world
}
复制代码
程序返回:
hello world
hello world
hello world
复制代码
基本上,匿名函数和命名函数用法上并无二致。
闭包(closure)
很多语言都有闭包的概念,简单理解就是函数的嵌套:
package main
import "fmt"
func main() {
a := Fun()
b:=a("hello ")
c:=a("hello ")
fmt.Println(b)//worldhello
fmt.Println(c)//worldhello hello
}
func Fun() func(string) string {
a := "world"
return func(args string) string {
a += args
return a
}
}
复制代码
程序返回:
worldhello
worldhello hello
复制代码
这里我们将方法作为参数传递到方法内部执行,这样内层的函数可以使用外层函数的所有变量,即使外层函数已经执行完毕。
延迟函数
延迟其实是延迟(defer)语句,延迟语句被用于执行一个函数调用,在这个函数之前,延迟语句返回:
package main
import "fmt"
func main() {
a := 1
b := 2
defer fmt.Println(b)
fmt.Println(a)
}
复制代码
程序返回:
说白了就是一种倒装的形式,非延迟语句先执行,最后再执行延迟语句。
延迟也并不仅仅局限于函数内部语句,延迟一个方法调用也是可以的:
package main
import (
"fmt"
)
type person struct {
firstName string
lastName string
}
func (p person) fullName() {
fmt.Printf("%s %s", p.firstName, p.lastName)
}
func main() {
p := person{
firstName: "go lang",
lastName: "python",
}
defer p.fullName()
fmt.Printf("Welcome ")
}
复制代码
程序返回:
初始化函数
顾名思义,和 Python 中的魔法方法 init 一样,可以提前做一些初始化操作:
package main
import "fmt"
var a int = initVar()
func init() {
fmt.Println("init2")
}
func init() {
fmt.Println("init")
}
func initVar() int {
fmt.Println("init var...")
return 100
}
func main() {
fmt.Println("main...")
}
复制代码
程序返回:
这里的初始化顺序是:变量初始化->init()->main()
和 Python 不同的是,每个包可以有多个初始化函数。
结语
归根结底,函数可以被认为是 Go lang 中的一种数据类型,可以作为另一个函数的参数,也可以作为另一个函数的返回值,使用起来相当灵活,但我们也不能矫枉过正,毫无节制地用函数封装逻辑,造成过度封装的现象。
评论