函数的定义
函数就是一段代码的集合。在Go语言中至少有一个main函数,在独立定义的情况下,需要有一个函数名来“见名知意”,函数可以有返回值也可以没有返回值,可以有参数也可以没有参数。
package main
import "fmt"
func main() {
// 调用函数,函数名要做到见名知意
printHelloWrold()
}
// func 定义函数
/**
func 函数名(参数1, 参数2, ...) {
// 函数的逻辑,本质是一段代码,未来开发中,很多业务代码都会通过函数隔离
// 封装的思想,方便复用,让代码更清晰
}
*/
func printHelloWrold() {
fmt.Println("Hello, World")
}
函数的具体定义:
无参无返回值
有一个参数
有两个或多个参数
有一个返回值
有一个或多个返回值
package main
import "fmt"
// 多个参数一个返回值的函数定义
func main() {
m := max(25, 10)
fmt.Println(m)
}
/**
func 函数名(参数1, 参数2) 返回值类型 {
代码逻辑
return 返回值
}
*/
// 比较两个数的大小,返回最大值
func max(num1 int, num2 int) int {
var result int
if num1 > num2 {
result = num1
} else {
result = num2
}
// return返回结果
return result
}
函数的多个返回值
package main
import "fmt"
func main() {
a, b := swap("zgy", "yyk")
fmt.Println(a, " ", b)
}
// 返回多个返回值的函数定义
// 案例:交换两个string
// 有多个返回值的情况下,返回值用括号括起来
// 两个参数相同类型可以简写
func swap(x , y string) (string, string) {
return y, x
}
package main
import "fmt"
func main() {
// 使用匿名变量过滤不需要的返回值
_, _, c, s := getResult(2, 4)
fmt.Printf("周长:%.1f,面积:%.1f", c, s)
}
// 多个参数、多个返回值的情况
func getResult(a, b float64) (float64, float64, float64, float64) {
var round float64
var area float64
round = (a + b) * 2
area = a * b
// return 如果指定了返回值,那么返回结果按照指定的顺序
return a, b, round, area
}
package main
import "fmt"
func main() {
c, s := getResult(2, 4)
fmt.Printf("周长:%.1f,面积:%.1f", c, s)
}
// 多个参数、多个返回值的情况
// return的结果值命名和定义函数返回值的命名无关
func getResult(a, b float64) (round float64, area float64) {
round = (a + b) * 2
area = a * b
// return 如果没有指定返回值,那么则按照函数返回值定义的顺序进行返回
return
}
函数的可变参数
可变参数:参数类型确定,但参数的个数不确定
注意:如果函数携带多个参数的情况下,可变参数必须放在最后一个
package main
import "fmt"
func main() {
fmt.Println(getSum(1, 2, 3, 4, 5))
}
func getSum(nums ...int) int {
sum := 0
// 多个参数使用下标来接收,len()函数获取可变参数的长度
for i := 0; i < len(nums); i++ {
sum += nums[i]
}
return sum
}
函数作用域
局部变量:函数内部定义的变量,只能在函数内部使用
全局变量:函数外部定义的变量,全局使用
函数变量在使用过程中遵循“就近原则”
package main
import "fmt"
// 全局变量,默认定义在上面,方便查看并且统一管理
var num int = 1
func main() {
temp := 100
// 小作用域可以用到大作用域中的变量,反之则不行。
// 对于很多一次性的变量,都可以这么写
if a := 1; a <= 10 {
fmt.Println(temp)
fmt.Println(num)
fmt.Println(a)
// 就近原则
if a := 2; a <= 10 {
fmt.Println(a)
}
}
fmt.Println(num)
}
递归函数
定义:函数自己调用自己
注意:递归函数必须有一个出口,并且逐渐向出口靠近,否则就会陷入死循环
使用递归函数实现斐波那契数列
package main
import "fmt"
/*
1 1 2 3 5 8 13 ......
递归的方式求出斐波那契数
规律 : 后面一个数等于前面两个数的和。
1 1 fib(1) = 1
2 1 fib(2) = 1
3 2 fib(3) = 2 = fib(3-1) + fib(3-2)
4 3 fib(4) = 3 = fib(4-1) + fib(4-2)
5 5 fib(5) = 5 = fib(4) + fib(3)
6 8 fib(6) = 8
*/
func main() {
fmt.Println(fib(111))
}
func fib(n int) int {
fmt.Println(n)
// 结束递归的条件
if n <= 2 {
return 1
} else {
return fib(n-1) + fib(n-2)
}
}
defer延迟函数
defer语句的作用:处理一些善后的问题,比如错误处理、文件或者网络流关闭等操作。
![](https://i-blog.csdnimg.cn/blog_migrate/48e3dc1a1c91da2c5eda517767566678.jpeg)
defer 函数或方法:函数或方法的执行被延迟了。
可以在函数中添加多个defer语句,当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回。在进行一些打开资源操作IO流时,遇到错误需要提前返回,但在返回前需要关闭相应的资源,不然很容易造成资源泄露的问题。
多个defer语句的执行是按照栈(后进先出)的模式。
![](https://i-blog.csdnimg.cn/blog_migrate/3facd054ee1379b3f53bcd246054b38e.jpeg)
defer 传递参数的时机:
package main
import "fmt"
// defer 传参的调用时机
func main() {
n := 10
fmt.Println("main n=", n) // main n= 10
// 由此可见,此时函数已经调用并且参数已经传递,但是还没有执行,延迟到最后执行的
defer f(n) // f n= 10
n++
fmt.Println("end main n=", n) // end main n= 11
}
func f(n int) {
fmt.Println("f n=", n)
}
函数的数据类型
函数在Go语言中也是一个数据类型,加()是函数调用,如果不加,则是一个特殊的变量,可以与普通变量一样传递并赋值。
package main
import "fmt"
// 函数的数据类型
func main() {
// %T 查看数据类型
// 查看变量的类型
a := 10
fmt.Printf("%T\n", a) // int
b := [4]int{1, 2, 3, 4}
fmt.Printf("%T\n", b) // [4]int
c := true
fmt.Printf("%T\n", c) // bool
// 查看函数的类型
fmt.Printf("%T\n", f1) // func()
fmt.Printf("%T\n", f2) // func(int) int
fmt.Printf("%T\n", f3) // func(int, int) (int, int)
fmt.Printf("%T\n", f4) // func(int, int, ...string) (int, int)
// 函数的类型等于创建的类型,在相同类型的函数之间可以相互赋值
var f func(int, int, ...string) (int, int)
f = f4
r1, r2 := f(1, 2, "111")
fmt.Println(r1, r2) // 1 2
}
// 无参无返回值的函数
func f1() {
}
// 有参有返回值的函数
func f2(a int) int {
return a
}
func f3(a, b int) (int, int) {
return a, b
}
func f4(a, b int, c ...string) (int, int) {
return a, b
}
匿名函数
由于Go语言中函数是一个特殊变量,所以也支持匿名操作。Go语言支持函数式编程。
将匿名函数作为另一个函数的参数,回调函数
将匿名函数作为另一个函数的返回值,可以形成闭包结构
package main
import "fmt"
// 匿名函数
func main() {
f1()
f2 := f1 // 函数的赋值,本质是两个函数指向了同一块内存空间,所以运行的代码一样
f2()
// 匿名函数,在最后加(),表示调用了匿名函数,只能执行一次
func() {
fmt.Println("我是匿名函数")
}()
// 匿名函数赋值后,可以多次调用
f3 := func() {
fmt.Println("我是匿名函数")
}
f3()
// 匿名函数可以添加参数和返回值
a, b := func(a, b int) (int, int) {
return a, b
}(1, 2)
fmt.Println(a, b)
}
func f1() {
fmt.Println("f1函数")
}
回调函数
高阶函数:可以将一个函数作为另一个函数的参数。
例如:fun2(fun1) ,fun1函数作为fun2函数的参数,fun2函数叫做高阶函数,接收一个函数作为参数;fun1函数叫做回调函数,作为另一个函数的参数。
package main
import "fmt"
// 回调函数和高阶函数
func main() {
r1 := add(1, 2)
fmt.Println(r1)
// 高阶函数调用
r2 := operator(1, 2, add)
fmt.Println(r2)
r3 := operator(4, 3, sub)
fmt.Println(r3)
// 匿名函数
f1 := func(a, b int) int {
return a * b
}
r4 := f1(1, 2) // 匿名函数调用
fmt.Println(r4)
// 直接传递匿名函数
r5 := operator(4, 2, func(a, b int) int {
if b == 0 {
fmt.Println("除数不能为0")
return 0
}
return a / b
})
fmt.Println(r5)
}
func add(a, b int) int {
return a + b
}
func sub(a, b int) int {
return a - b
}
// 高阶函数,接收函数作为参数
func operator(a, b int, fun func(int, int) int) int {
r := fun(a, b)
return r
}
闭包结构
概念:一个外层函数中,有内层函数,该内存函数中,会操作外层函数的局部变量,并且该外层函数的返回值就是这个内层函数,这个内存函数和外层函数的局部变量,统称为闭包结构。
因此,局部变量的生命周期就会发生改变。正常的局部变量会随着函数的调用而创建,随着函数的结束而销毁。但是闭包结构中外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还在使用。
闭包结构:是一种特殊的结构,违反了程序的正常生命周期。Go语言程序中允许的一种特殊结构,使用合法,变量作用域升级。
package main
import "fmt"
// 闭包结构
func main() {
r1 := increment()
// 内层函数的调用执行
v1 := r1()
fmt.Println(v1) // 1
fmt.Println(r1()) // 2
fmt.Println(r1()) // 3
fmt.Println(r1()) // 4
fmt.Println(r1()) // 5
// 再次调用外层函数,此时局部变量i变为初始值0
r2 := increment()
v2 := r2()
fmt.Println(v2) // 1
// 外层函数的局部变量i并没有随着外层函数第二次创建销毁归0,而是继续在内层函数使用
fmt.Println(r1()) // 6
fmt.Println(r2()) // 2
// 打印函数地址
// 两次创建函数的地址不同,第二次创建重新开辟了一块内存空间
fmt.Printf("%p\n", r1) // 0x7100a0
fmt.Printf("%p\n", r2) // 0x710080
}
// 函数返回值 func() int 类型
func increment() func() int { // 外层函数
// 局部变量
i := 0
// 内部定义一个匿名函数
fun := func() int {
i++
return i
}
return fun
}
思考:什么时候使用闭包?
在js中,有很多框架都是使用的闭包结构,为了防止变量的冲突,避免造成全局变量污染。
关于闭包:只有函数内部的子函数才能读取局部变量,闭包就是能够读取其它函数内部变量的函数。
闭包形成的原理:父对象的所有变量,对子对象都是可见的,反之不成立。
闭包解决的问题:能够让函数作用域中的变量在函数执行结束之后不被销毁,让这些变量的值始终保持在内存中;延伸变量作用域的范围,使函数外部可以访问函数内部的局部变量。
闭包可能带来的问题:由于垃圾回收期间不会将闭包中的变量进行销毁处理,所以可能会造成内存泄露。
闭包结构的返回值是一个函数。通过这个函数可以调用闭包结构中的变量。