1. 函数介绍 函数是基本的代码块,为完成某一功能的程序指令(语句)的集合,用于执行一个任务
Go语言最少有个main()函数
可以通过函数来划分不同的功能,逻辑上每个函数执行的是指定的任务
函数的声明告诉了编译器函数的名称,返回类型,和参数
Go语言标准库提供了多种可用的内置函数。例如,len()函数可以接受不同类型参数并返回该类型的长度。如果传入的是字符串则返回字符串的长度,如果传入的是数组,则返回数组中包含的元素个数。
函数的定义:
1 2 3 4 func function_name ([parameter list]) [return_types ] { 函数体 return 返回值列表 }
语法解析:
func: 函数由func开始声明
**function_name:**函数的名称,参数列表和返回值类型构成了函数签名
**parameter list:**参数列表,参数就像一个占位符,当函数被调用的时,可以将值传递给参数,这个值被称为实际参数。参数列表指定的事参数类型、顺序、及参数个数。参数是可以选择的,也就是说函数也可以不包括参数。
**return_types:**返回类型,函数返回一列值。return_types是该值的数据类型。有些功能不需要返回值,这种情况下return_types不是必须的。
函数体: 函数定义的代码集合。
2. 实例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package mainimport "fmt" func cal (a1 float64 , a2 float64 , kay byte ) float64 { var a3 float64 switch kay { case '+' : a3 = a1 + a2 case '-' : a3 = a1 - a2 case '*' : a3 = a1 * a2 case '/' : if a2 != 0 { a3 = a1 / a2 } else { fmt.Println("除数不能为0" ) return 0 } default : fmt.Println("请正确输入" ) return 0 } return a3 }func main () { var a1 float64 var a2 float64 var kay byte fmt.Println("请输入第一个数a1" ) fmt.Scanln(&a1) fmt.Println("请输入第二个数a2" ) fmt.Scanln(&a2) fmt.Println("请输入+/-/*//" ) fmt.Scanf("%c" , &kay) result := cal(a1, a2, kay) fmt.Println("result=" , result) }
定义一个函数,返回两个值的最大值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport "fmt" func max (num1, num2 int ) int { var result int if num1 > num2 { result = num1 } else { result = num2 } return result }func main () { var a int = 100 var b int = 200 var ret int ret = max(a, b) fmt.Printf("最大值是:%d\n" , ret) }
3. 函数包 注意⚠️:
根目录文件夹名称xxx和go mod init xxx 必须要一致
主文件中import的是子包的目录路径,不能写子包的文件名或者包名。
调用子包的方法的时候, 前缀必须是子包的包名(package名),和路径或者文件名无关
在实际的开发中,我们往往需要在不同的文件中,去调用其它文件定义的函数,比如 main.go中,去使用 utils.go 文件中的函数,如何实现? 通过包实现
现在有两个程序员共同开发一个 Go 项目,程序员李白希望定义函数 Cal ,程序员杜甫
也想定义函数也叫 Cal。怎么办? 通过包实现
包的基本介绍:
包的本质实际上就是创建不同的文件夹,来存放程序文件。go 的每一个文件都是属于一个包的,也就是说 go 是以包的形式来管理文件和项目目录结构的。
包的使用:
package 包名
import “包的路径”
3.1 包入门案例 创建function-demo
根目录,目录结构如下:
生成mod
文件
1 go mod init function-demo
uiil.go代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package untilimport "fmt" var Num1 int = 300 func Cal (n1 float64 , n2 float64 , operator byte ) float64 { var res float64 switch operator { case '+' : res = n1 + n2 case '-' : res = n1 - n2 case '*' : res = n1 * n2 case '/' : if n2 != 0 { res = n1 / n2 } else { fmt.Println("除数不能为0" ) return 0 } default : fmt.Println("操作符号错误.." ) } return res }
main.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport ( "fmt" "function-demo/until" )func main () { fmt.Println("until.go Num=" , until.Num1) var n1 float64 = 1.6 var n2 float64 = 5.6 var operator byte = '+' result := until.Cal(n1, n2, operator) fmt.Printf("result=%2.f\n" , result) n1 = 7.8 n2 = 6.7 operator = '*' result = until.Cal(n1, n2, operator) fmt.Printf("result=%.2f" , result) }
运行的结果
1 2 3 until.go Num= 300 result=7.20 result=52.26
4. 函数return语句 1 2 3 4 func 函数名(形参列表)[返回值类型列表] { 执行语句 return 返回值列表 }
如果返回多个值,在接收的时候,希望忽略某个返回值。则使用_符号表示占位忽略
如果返回值只有一个,返回值类型列表可以不写()
4.1 实例
计算两个数,返回结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package mainimport "fmt" func test (n1 int ) { n1 = n1 + 1 fmt.Println("test() n1=" , n1) }func getSum (n1 int , n2 int ) int { sum := n1 + n2 fmt.Println("getSum sum=" , sum) return sum }func getSumAndSub (n1 int , n2 int ) (int , int ) { sum := n1 + n2 sub := n1 - n2 return sum, sub }func main () { n1 := 10 test(n1) fmt.Println("main() n1=" , n1) sum := getSum(10 , 20 ) fmt.Println("getSum=" , sum) res1, res2 := getSumAndSub(1 , 2 ) fmt.Printf("res1=%v,res2=%v\n" , res1, res2) _, res3 := getSumAndSub(9 , 6 ) fmt.Println("res3=" , res3) }
5. 递归函数 一个函数在函数体内又调用了本身,我们称之为递归调用,语法格式:
1 2 3 4 5 6 7 func recursion () { recursion() }func main () { recursion() }
Go语言支持递归,但是再使用递归时,开发者需要设置退出的条件,否则递归将陷入无限的循环之中。
递归函数对于解决数学上的问题是非常有用的,就像计算阶乘,生成数列斐波那契数列等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport "fmt" func test (n int ) { if n > 2 { n-- test(n) } fmt.Println("n=" , n) }func main () { test(4 ) }
6. init函数 每个源文件都可以包含一个init函数,该函数会在main函数执行前被go运行框架调用,也就是说init会在main函数前被调用
如果一个文件同时使用包含全局变量定义,init函数和main函数,则执行的流程
全局变量定义–>init函数–>main函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport "fmt" var age = test()func test () int { fmt.Println("test()" ) return 90 }func init () { fmt.Println("init()..." ) }func main () { fmt.Println("main()...age=" , age) }
如果main.go和utils.go中都有变量定义、init函数,执行流程如下:
utils.go中的变量定义>utils.go中的init函数>main.go中的变量定义>main.go中的init函数>main.go中的main函数
main.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport ( "fmt" "function-demo2/util" )var age = test()func test () int { fmt.Println("test()" ) return 90 }func init () { fmt.Println("init()" ) }func main () { fmt.Println("main()...age=" , age) fmt.Println("util的Age=" , util.Age) fmt.Println("util的Name=" , util.Name) }
util.go
1 2 3 4 5 6 7 8 9 10 11 12 package utilimport "fmt" var Name string var Age int func init () { fmt.Println("util包的init()。。。" ) Age = 100 Name = "tom~" }
最终输出结果
1 2 3 4 5 6 util包的init()。。。 test() init() main()...age= 90 util的Age= 100 util的Name= tom~
6.1 实例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport "fmt" func init () { fmt.Println("init()..." ) }func main () { fmt.Println("main()..." ) }
7. 匿名函数 go支持匿名函数,匿名函数就是没有名字的函数,如果我们某个函数只是希望使用一次,可以考虑使用匿名函数,匿名函数也可以多次调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package mainimport "fmt" var ( fun1 = func (n1 int , n2 int ) int { return n1 * n2 } )func main () { res1 := func (n1 int , n2 int ) int { return n1 + n2 }(10 , 10 ) fmt.Println("res1=" , res1) a := func (n1 int , n2 int ) int { return n1 - n2 } res2 := a(10 , 10 ) fmt.Println("res2=" , res2) res4 := fun1(10 , 10 ) fmt.Println("res4=" , res4) }
8. 闭包 闭包就是一个函数与其相关的引用环境组合的一个整体(实体)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package mainimport "fmt" func AddUpper () func (int ) int { var n int = 10 return func (x int ) int { n = n + x return n } }func main () { f := AddUpper() fmt.Println(f(1 )) fmt.Println(f(2 )) fmt.Println(f(3 )) }
AddUpper是一个函数,返回的数据类型是func(int)int
返回的是一个匿名函数,但是这个匿名函数引用到函数外的n,因此这个匿名函数就和n形成了一个整体,构成闭包
当反复的调用f函数时,因为n是初始化的一次,因此每调用一次就进行累计
要搞清楚闭包的关键,就是要分析出返回的函数和它使用(引用)到哪些变量,因为函数和他引用到的变量共同构成闭包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package mainimport ( "fmt" "strings" )func makeSuffix (suffix string ) func (string ) string { return func (name string ) string { if !strings.HasSuffix(name, suffix) { return name + suffix } return name } }func main () { f2 := makeSuffix(".jpg" ) fmt.Println("文件处理后=" , f2("winter" )) fmt.Println("文件处理后=" , f2("bird.jpg" )) }
makeSuffix(suffix string) func(string) string
:
这个函数接受一个 suffix
(后缀)作为参数,并返回另一个函数。返回的这个函数本身接受一个字符串作为参数并返回一个字符串。
在 makeSuffix
内部,返回的函数检查提供的字符串(name
)是否以 suffix
结尾。如果不是,它会添加后缀;否则,它返回字符串本身。
在 main
函数中:
f2
被赋值为 makeSuffix(".jpg")
返回的函数。这意味着 f2
现在是一个函数,如果字符串不以 “.jpg” 结尾,它将添加 “.jpg”。
fmt.Println("文件处理后=", f2("winter"))
打印 “文件处理后=winter.jpg”。由于 “winter” 不以 “.jpg” 结尾,所以添加了后缀。
fmt.Println("文件处理后=", f2("bird.jpg"))
打印 “文件处理后=bird.jpg”。由于 “bird.jpg” 已经以 “.jpg” 结尾,所以返回字符串未改变。
9. 函数defer 在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等),为了在函数执行完毕后,及时释放资源,Go的设计者提供defer(延迟机制)
当go执行到一个defer时,不会立即执行defer后的语句,而是将defer后的语句压入到一个栈中,然后继续执行函数的下一个语句
当函数执行完毕后,再从defer栈中,一次从栈顶取出语句执行(遵循栈先入后出的机制)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport "fmt" func sum (n1 int , n2 int ) int { defer fmt.Println("ok1 n1=" , n1) defer fmt.Println("ok2 n2=" , n2) n1++ n2++ res := n1 + n2 fmt.Println("ok3 res=" , res) return res }func main () { res := sum(10 , 20 ) fmt.Println(res) }
9.1 defer的最佳实践 defer最主要的价值是,当函数执行完毕后,可以及时的释放函数创建的资源。
说明:
在golang编程过程中通常的做法是,创建资源后,比如(打开了文件,获取了数据库的链接,或者锁资源),可以执行defer file.Close() defer connect.Close()
在defer后,可以继续使用创建资源
当函数完毕后,系统回一次从defer栈中,取出语句,关闭资源
这种机制,非常简洁,程序员不用再为什么时候关闭资源而烦心
10. 错误处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport "fmt" func test () { num1 := 10 num2 := 0 res := num1 / num2 fmt.Println(res) }func main () { test() fmt.Println("main()下面的代码是。。。" ) }
错误总结:
在默认情况下,当发生错误后(panic),程序就会退出(崩溃)
但是我们希望:当发生错误后,可以捕获到错误,并进行处理,保证程序可以继续执行。还可以在捕获到错误后,给管理员一个提示(邮件、短信),这就需要对错误进行特殊的处理
10.1 使用defer+recover来处理错误 进行错误处理后,程序不会轻易挂掉,如果加入预警代码,可以让程序更加的健壮
go语言追求简洁优雅,所以go语言不支持传统的try…catch…finally来处理
go中引用的处理方式为:defer、panic、recover
这几个异常的使用场景可以这么简单的描述:go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package mainimport ( "fmt" "time" )func test () { defer func () { err := recover () if err != nil { fmt.Println("err=" , err) fmt.Println("发送邮件给admin" ) } }() num1 := 10 num2 := 0 res := num1 / num2 fmt.Println(res) }func main () { test() for { fmt.Println("main()下面的函数" ) time.Sleep(time.Second * 10 ) } }
10.2 自定义错误 go程序中,也支持自定义错误,使用errors.New和panic内置函数错误
errors.New(“错误说明”),会返回一个error类型的值,表示一个错误
panic内置函数,接收一个interface{}类型的值(也就是任何值了)作为参数。可以接收error类型的变量,输出错误信息,并退出程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package mainimport ( "errors" "fmt" )func readConf (name string ) (err error) { if name == "config.ini" { return nil } else { return errors.New("读取文件错误。。。" ) } }func test02 () { err := readConf("config.ini" ) if err != nil { panic (err) } fmt.Println("test02继续执行" ) }func main () { test02() fmt.Println("main()下面的代码" ) }