函数进阶
一 函数类型
go语言中函数是可以复制给一个变量的,变量又必须要有确定的类型,该类型就是函数类型
使用type
定义函数类型,函数类型就是值该函数的传参与返回值的格式
type 类型名 func(参数类型列表) 返回值类型列表
-
func
: 关键字 -
type
: 关键字,用于重新命名类型(自定义类型)
函数名是保存的函数的入口地址,所以函数类型实际上是一个指针类型
package main
import "fmt"
func calc(x, y int) (sum, sub int) {
sum = x + y
sub = x - y
return
}
type funcType func(int, int)(int, int) // 定义一个函数类型
func main() {
var ca funcType // 定义一个函数类型变量
ca = calc // 给变量复制
x, y := 30, -10
x, y = ca(x, y) // 调用函数
fmt.Println(x, y)
}
- 函数类型是确定函数的传参和返回值的格式
- 如果格式一致是指传参和返回值的类型都是一样的。
二 作用域
go语言中,变量存在于不同的作用域中,变量的查找先冲局部变量查找到全局变量
2.1 局部作用域
go语言中区别作用域的是{}
,因此if
、for
、switch
和函数都会新建
局部作用域,局部作用域中变量只能在该作用域中使用,出了该区域变量立即被销毁。
- 对于局部作用域,变量的适用范围是在其定义之后。
2.2 全局作用域
函数外部的空间被整个工程全局捕获。所以只要是在函数外的区域都是全局作用域。
在全局作用域中的变量,在任何位置都是可以访问和修改。
- 在全局作用域中的变量,无论在那个函数前后定义,整个工程项目的所有文件都能访问
- 全局变量是可以获取地址的
package main
import "fmt"
func test() {
var c = 20 // c是局部变量,只能在函数test中使用
fmt.Println(c)
}
func main() {
fmt.Println(a)
a = 20 // 函数内部可以修改全局变量
fmt.Println(a)
//fmt.Println(c) // 无法访问不变量c
//fmt.Println(b) // 无法访问变量b
b := 30
fmt.Println(b) // 变量定义之后才能访问
}
var a int = 10 // 全局变量
三 函数作为参数和返回值
由于go
语言中的函数作为一等公民,具有被变量接收的性质,因此函数可以作为
函数的参数,和函数的返回值
3.1 函数作为参数
函数当作参数传递到函数内部,实现函数回调。回调函数是指函数函数的调用取决于另一个
函数的逻辑。
示例,计算器程序
package main
import "fmt"
func plus(a...int) int {
sum := 0
for _, v := range a {
sum += v
}
return sum
}
func minus(a ... int) int {
return a[0] - a[1]
}
func calculator(option func(...int) int, a...int) int {
return option(a...)
}
func main() {
result := calculator(plus, 1,2,3,4)
fmt.Println(result)
result = calculator(minus, 5, 4)
fmt.Println(result)
}
3.2 函数作为返回值
go语言中函数是可以作为返回值进行返回的
package main
import (
"fmt"
)
func plus(a ...int) int {
sum := 0
for _, v := range a {
sum += v
}
return sum
}
func minus(a ...int) int {
return a[0] - a[1]
}
func calculator(option func(...int) int, a ...int) (int, func(...int) int) {
return option(a...), option
}
func main() {
result, option := calculator(plus, 1, 2, 3, 4)
fmt.Println(result, option)
result, option = calculator(minus, 5, 4)
fmt.Println(result, option)
}
四 匿名函数
函数可以作为返回值,但是在Go
语言中函数内部不能再像之前那样定义函数了,
只能定义匿名函数。匿名函数就是没有函数名的函数,匿名函数的定义格式如下
func(参数)(返回值){
函数体
}
-
func
: 关键字
匿名函数因为没有函数名,所以没办法像普通函数那样调用,
所以匿名函数需要 保存到某个变量 或者 作为立即执行函数
package main
func function1(a int, b int) int {
f := func(x int, y int) int {
return x + y
} // 匿名函数
return f(a, b)
}
func function2(a int, b int) int {
f := func(x int, y int) int {
return x + y
}(a, b) // 定义匿名函数,立即执行
return f
}
匿名函数多用于实现回调函数和闭包
对于无参数无返回值且直接调用的匿名函数可以直接使用一对{}
包裹即可
package main
import "fmt"
func main() {
{
fmt.Println("hello 匿名函数1")
}
func () {
fmt.Println("hello 匿名函数2")
}()
}
五 闭包函数
闭包指的是一个函数和与其相关的引用环境组合而成的实体。
简单来说,闭包=函数+引用环境
package main
// 该函数就是一个闭包函数
func adder() func(int) int {
var x int
return func(y int) int {
x += y
return x
}
}
闭包其实并不复杂,只要牢记闭包=函数+引用环境
闭包应用示例: 装饰器
package main
import "fmt"
func f1(f func()){
fmt.Println("this is function f1")
f()
}
func f2(x, y int) {
fmt.Println("this is function f2")
fmt.Println(x + y)
}
// 将f2函数在f1函数中进行调用,需要对f2函数进行如下定制
func f3(f func(int, int), x, y int) func() {
/**
闭包函数。装饰器
*/
return func() {
f(x, y)
}
}
// 也可以对函数f2进行如下包装
func f4(f func(int, int)) func(int, int) func(){
ret := func(x, y int) func() {
return func() {
f(x, y)
}
}
return ret
}
func main() {
function := f3(f2, 10, 20)
f1(function)
f := f4(f2)
ff := f(10, 20)
f1(ff)
}
六 递归函数
递归函数是指一个函数的函数体直接或间接调用了该函数自身.
递归函数调用执行过程分为两个阶段:
- 递推阶段: 从原问题出发, 按递归公式递推. 从未知到已知, 最终达到递归终止条件.
- 回归阶段: 按递归终止条件求出结果, 逆向逐步带入递归公式, 回归到原问题求解.
求一个数的阶乘
package main
func factorial(n int64) (result int64) {
if n < 1 {
result = 1
} else {
result = n * factorial(n - 1)
}
return
}
- 递归函数在执行是需要有一个出口,没有出口的递归函数为死递归。
求斐波拉且数列,并使用数组保存过程值
package main
var count [1000] int64
func fibolach(x int) int64 {
if x == 1 || x == 2 {
return 1
} else if count[x - 1] != 0 && count[x - 2] != 0 {
return count[x - 1] + count[x - 2]
} else {
count[x - 1] = fibolach(x - 1)
count[x - 2] = fibolach(x - 2)
return count[x - 1] + count[x - 2]
}
}
七 defer延迟调用
Go语言中的defer
语句会将其后面跟随的语句进行延迟处理。
在defer
归属的函数即将返回时,将延迟处理的语句按defer
定义的逆序进行执行,
也就是说,先被defer
的语句最后被执行,最后被defer
的语句,最先被执行
package main
import "fmt"
func main() {
fmt.Println("start")
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
fmt.Println("end")
}
面试题
package main
import "fmt"
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
func main() {
x := 1
y := 2
defer calc("AA", x, calc("A", x, y))
x = 10
defer calc("BB", x, calc("B", x, y))
y = 20
}
- 由于calc(“A”,x,y)和calc(“B”,x,y)没有被defer修饰。所以按顺序最先调用。
- defer延迟调用,当要执行defer时,被defer修饰的函数的所有参数值都确定。
- 最后被defer修饰的函数最先调用。
- 最后的调用输出结果为
A 1 2 3
B 10 2 12
BB 10 12 22
AA 1 3 4
工程管理
为了更好的管理工程中的文件,go语言建立了如下几个文件夹
-
src
: 源码文件夹,用于保存go语言的源码 -
pkg
: 存放代码包 -
bin
: 存放可执行文件
分文件编程(多个源文件)必须放在src目录下
一 同级目录文件
同级别目录下的文件,package
指定的包必须是相同的
同级目录文件,调用别的文件的函数,直接调用即可,无需包名引用
二 不同目录文件
一个工程可能不只一个包,不同包存放在不同的文件夹中。
当要在其他包中使用时,一定要进行导包操作。
对于需要在其他包总调用的函数,需要大写首字母
不同的目录下,不能有起相同的报名
随机数
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())// 设置随机数种子, 如果随机数种子不变,程序每次产生的随机数将会是一致的
// 产生随机数
fmt.Println(rand.Int()) // 产生整型随机数
fmt.Println(rand.Intn(10)) // 产生10以内的随机数
}