Go语言入门8:函数
Table of Contents
Go 语言入门基础学习笔记之 Go 语言的函数
函数
函数定义
func function_name( [parameter list] ) [return_types] {
函数体
}
func
:函数由 func 开始声明function_name
:函数名称,参数列表和返回值类型构成了函数签名。parameter list
:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。return_types
:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。- 函数体:函数定义的代码集合。
/* 函数返回两个数的最大值 */
func max(num1, num2 int) int {
/* 声明局部变量 */
var result int
if (num1 > num2) {
result = num1
} else {
result = num2
}
return result
}
Go 语言还支持命名返回值,比如:
func Factorial(n uint64)(result uint64) {
if (n > 0) {
result = n * Factorial(n-1) // 直接对命名返回值 result 赋值
return // 这里直接返回 result
}
return 1 // 这里返回 1,result 会被自动赋值为 1
}
这样写的好处在于:
- 在函数体中不需要再次声明定义这个变量。
- 允许在函数中使用
result
直接作为返回值。 - 使得代码更简洁,容易阅读,并且可以避免在每个返回语句中重复指定返回值。
- 如果没有提供名称,返回值则需要在
return
语句中显式指定,例如return someValue
。
返回多个值
Go 函数可以返回多个值,并且也支持匿名和有名。
// 匿名的返回值
func swap(x, y string) (string, string) {
return y, x
}
// 有名的返回值
func swap1(x, y string) (r1 string, r2 string){
r1 = y
r2 = x
return
}
// 相同类型的类型名可以合并
func swap2(x, y string) (r1, r2 string){
r1 = y // 如果 r1 r2不赋值直接返回,则为默认值(空)
r2 = x
return
}
函数参数
调用函数,可以通过两种方式进行参数传递
- 值传递:指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
- 引用传递:指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。
Go 语言中所有的等号赋值和函数传参都是进行拷贝的值传递。
值传递:
package main
import "fmt"
func main() {
/* 定义局部变量 */
var a int = 100
var b int = 200
fmt.Printf("交换前 a 的值为 : %d\n", a )
fmt.Printf("交换前 b 的值为 : %d\n", b )
/* 通过调用函数来交换值 */
swap(a, b)
fmt.Printf("交换后 a 的值 : %d\n", a )
fmt.Printf("交换后 b 的值 : %d\n", b )
}
/* 定义相互交换值的函数 */
func swap(x, y int) int {
var temp int
temp = x /* 保存 x 的值 */
x = y /* 将 y 值赋给 x */
y = temp /* 将 temp 值赋给 y*/
return temp;
}
// 输出结果
交换前 a 的值为 : 100
交换前 b 的值为 : 200
交换后 a 的值 : 100
交换后 b 的值 : 200
引用传递:
package main
import "fmt"
func main() {
/* 定义局部变量 */
var a int = 100
var b int= 200
fmt.Printf("交换前,a 的值 : %d\n", a )
fmt.Printf("交换前,b 的值 : %d\n", b )
/* 调用 swap() 函数
* &a 指向 a 指针,a 变量的地址
* &b 指向 b 指针,b 变量的地址
*/
swap(&a, &b)
fmt.Printf("交换后,a 的值 : %d\n", a )
fmt.Printf("交换后,b 的值 : %d\n", b )
}
func swap(x *int, y *int) {
var temp int
temp = *x /* 保存 x 地址上的值 */
*x = *y /* 将 y 值赋给 x */
*y = temp /* 将 temp 值赋给 y */
}
// 输出结果
交换前,a 的值 : 100
交换前,b 的值 : 200
交换后,a 的值 : 200
交换后,b 的值 : 100
函数用法
- 函数作为另外一个函数的实参:函数定义后可作为另外一个函数的实参数传入
- 闭包:闭包是匿名函数,可在动态编程中使用
- 方法:方法就是一个包含了接受者的函数
Go 语言可以很灵活的创建函数,并作为另外一个函数的实参。以下实例中我们在定义的函数中初始化一个变量,该函数仅仅是为了使用内置函数 math.sqrt()
。
package main
import (
"fmt"
"math"
)
func main(){
/* 声明函数变量 */
getSquareRoot := func(x float64) float64 {
// 这是一个匿名函数
return math.Sqrt(x)
}
/* 使用函数 */
fmt.Println(getSquareRoot(9))
}
// 输出结果
3
Go 语言支持匿名函数,可作为闭包。匿名函数是一个内联语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。
匿名函数是一种没有函数名的函数,通常用于在函数内部定义函数,或者作为函数参数进行传递。
匿名函数可以在声明时直接使用,如下面这个例子。也可以当成值赋值给一个变量,这个变量就相当于是这个匿名函数的一个函数名。
package main
import "fmt"
func main() {
a := 1
b := 2
c := func(a int, b int) int {
return a + b
}(a, b)
fmt.Println(c)
}
以下实例中,我们创建了函数 getSequence()
,返回另外一个函数。该函数的目的是在闭包中递增 i
变量,代码如下:
package main
import "fmt"
func getSequence() func() int {
// func()表示getSequence()返回一个函数类型的值
// int表示func()返回一个int类型的值
i := 0
return func() int {
i+=1
return i
}
}
func main(){
/* nextNumber 为一个函数,函数 i 为 0 */
nextNumber := getSequence()
/* 调用 nextNumber 函数,i 变量自增 1 并返回 */
fmt.Println(nextNumber())
fmt.Println(nextNumber())
fmt.Println(nextNumber())
/* 创建新的函数 nextNumber1,并查看结果 */
nextNumber1 := getSequence()
fmt.Println(nextNumber1())
fmt.Println(nextNumber1())
}
// 输出结果
1
2
3
1
2
package main
import "fmt"
func main() {
// 定义一个匿名函数并将其赋值给函数变量add
add := func(a, b int) int {
return a + b
}
// 调用匿名函数
result := add(3, 5)
fmt.Println("3 + 5 =", result)
// 定义一个匿名函数并将其赋值给函数变量multiply
multiply := func(x, y int) int {
return x * y
}
product := multiply(4, 6)
fmt.Println("4 * 6 =", product)
// 将匿名函数作为参数传递给其他函数
calculate := func(operation func(int, int) int, x, y int) int {
return operation(x, y)
}
sum := calculate(add, 2, 8)
fmt.Println("2 + 8 =", sum)
// 也可以直接在函数调用中定义匿名函数
difference := calculate(func(a, b int) int {
return a - b
}, 10, 4)
fmt.Println("10 - 4 =", difference)
}
// 输出结果
3 + 5 = 8
4 * 6 = 24
2 + 8 = 10
10 - 4 = 6
Go 语言中还有一个特殊的函数用法,defer
。defer
后面跟的代码或者匿名函数,在当前层函数退出前(return
后)才会被执行。
package main
import "fmt"
func work() {
fmt.Println("Step 1")
defer func(){ // ()用来放置形参
fmt.Println("Step 3")
}() // ()用来传递实参
fmt.Println("Step 2")
}
在上面一个例子中,在匿名函数前使用的了 defer
,使得这个匿名函数在 work
函数最后才会被执行。
在同一个函数中可以有多个 defer
关键字,由于 defer
使用栈的结构,因此它们执行的时的顺序恰好与书写顺序相反。
init 函数与 import 导包
Go 语言给每个包提供了一个 init()
函数,作为当前包的一个主入口。
在上面张图中,展示了 Go 语言程序有关包、init()
和 main()
的调用流程。
main
程序作为项目的主入口,一开始会调用一系列的包,不同的包中也会不断的调用其他包,整体是以递归的方式进行解析。包的解析流程是从 const
到 var
到 inti()
,主程序的解析流程也是类似,从 const
到 var
到 init()
到 main()
,由于 init
的执行时机优先于 main
,因此我们可以将需要在 main
函数中执行的操作写在 init
函数中。
实例:
│ main.go
│
├─lib1
│ lib1.go
│
└─lib2
lib2.go
以上是实例的目录情况,main.go
是入口程序,lib1
和 lib2
是要导入的包。
package main
import (
"GolangStudy/5-init/lib1"
"GolangStudy/5-init/lib2"
)
func main() {
lib1.Lib1Test()
lib2.Lib2Test()
}
// 输出结果
lib1. init() ...
lib2. init() ...
lib1Test()...
lib2Test()...
这里需要指定 lib1
和 lib2
相对于 GOROOT
目录的路径位置,否则会报错找不到包。后续会使用 go mod
来更方便的管理包模块,不再需要添加路径。
package lib1
import "fmt"
//当前lib1包提供的API
func Lib1Test() {
fmt.Println("lib1Test()...")
}
func init() {
fmt.Println("lib1. init() ...")
}
package lib2
import "fmt"
//当前lib2包提供的API
func Lib2Test() {
fmt.Println("lib2Test()...")
}
func init() {
fmt.Println("lib2. init() ...")
}
如果要让其他程序获取当前包中的变量或者函数,变量和函数名的开头必须大写!表示一个公共开放的变量或者函数。否则只能在自己当前的包内使用。
当然在 main
入口程序中导入 lib1
和 lib2
包,也可以设置别名或匿名。
如果导入了一个包,但是未使用,Go 语言在编译时也会报错。但是存在一种情况:你只需要使用一个包中的 init
初始化函数,但是却不使用它,我们应该怎么做?针对这样的需求,Go 语言提供了 _
别名用于表示导入匿名包,如:
import (
_ "GolangStudy/5-init/lib1" // 导入包执行init,但无法使用包中的方法
}
除此之外,你也可以为导入的包设置一个正常的别名。
package main
import (
mylib2 "GolangStudy/5-init/lib2"
)
func main() {
mylib2.Lib2Test()
}
也可以通过 .
来设置一个无名的包。尽量少使用,避免有重名的方法,产生错误。
package main
import (
. "GolangStudy/5-init/lib2"
)
func main() {
Lib2Test()
}
参考课程: