什么是函数
函数是基本的代码块,用开执行一个任务。
Go语言最少有个 main() 函数。
你可以通过函数来划分不同功能,逻辑上每个函数执行的是指定的任务。
函数声明告诉了编译器函数的名称,返回类型,和参数。
函数的声明
Go语言函数定义格式如下:
func function_name( [parameter list])[return_types]) {
函数体
}
package main
import "fmt"
func main(){
// 调用函数 函数名()
fmt.Println(add(1, 2))
}
// func 函数名 (参数, 参数 ...) 函数调用后的返回值{
// 函数体 : 执行一段代码
// return 返回结果
//}
func add(a, b int)(int){
c := a + b
return c
}
函数多个返回值
无参无返回值函数
有一个参数的函数
有两个参数的函数
有一个返回值的函数
有多个返回值的函数
package main
import "fmt"
func main() {
noParamNoReturn() // 调用无参无返回值函数
oneParamFunc("调用有一个参数的函数") // 调用有一个参数的函数
twoParamFunc("调用有两个参数的函数", "第二个参数") // 调用有两个参数的函数
oneReturnFunc() // 调用有一个返回值的函数
x, _ := swap("坤", "鲲") // 调用有多个返回值的函数
fmt.Println("swap函数的返回值:", x) // 输出swap函数的返回值
x, y := swap("坤", "鲲") // 调用有多个返回值的函数
fmt.Println("swap函数的返回值:", x, y) // 输出swap函数的返回值
c := add(10, 20) // 调用有多个参数的函数
fmt.Println("add函数的返回值:", c) // 输出add函数的返回值
// 函数可以作为参数传递
}
func noParamNoReturn() {
// 无参无返回值函数
fmt.Println("无参无返回值函数")
}
func oneParamFunc(param string) {
// 有一个参数的函数
fmt.Println("有一个参数的函数:", param)
}
func twoParamFunc(param1, param2 string) {
// 有两个参数的函数
fmt.Println("有两个参数的函数:", param1, param2)
}
func oneReturnFunc() string {
// 有一个返回值的函数
fmt.Println("有一个返回值的函数")
return "这是返回值"
}
func swap(x, y string) (string, string) {
// 有多个返回值的函数
return y, x // 返回两个字符串的值
}
func add(a, b int) int {
// 函数可以有多个参数
c := a + b // 计算两个整数的和
return c // 返回两个整数的和
}
形式参数与实际参数
package main
func main() {
// 形参与实参要一一对应,顺序、个数和类型都要一致
max(1, 2)
}
// max 两个数比较大小
// 形式参数:定义函数时,用来接收外部传入的实际参数的数据,就是形式参数
// 实际参数:调用函数时,传给形参的实际数据,就是实际参数
func max(a, b int) int {
var result int // 定义一个变量result,用于存储比较结果
// a, b 是形参,int 是形参的类型
// 形参:函数定义时的参数
if a > b {
result = a // 如果a大于b,则result赋值为a
} else {
result = b // 如果a小于等于b,则result赋值为b
}
// 一个函数定义上有返回值,那么函数中必须使用return语句
// 返回值
// 调用处需要使用变量接收该结果
return result // 返回比较结果
}
可变参数
概念:一个函数的参数类型确定,但个数不确定,就可以使用可变参数。
package main
import "fmt"
func main() {
getSum(1, 2, 3, 4, 5) // 调用可变参数函数
}
// ... 可变参数函数示例
func getSum(nums ...int) {
// 可变参数函数
sum := 0
for i := 0; i < len(nums); i++ {
fmt.Println(nums[i]) // 输出每个参数的值
// nums[i] 是一个切片,nums[i] 的值是第 i 个参数的值
sum += nums[i] // 累加所有参数的值
}
fmt.Println("所有参数的和:", sum) // 输出所有参数的和
}
注意事项:
如果一个函数的参数是可变参数,同时还有其他的参数,可变参数要放在列表的最后。
一个函数的参数列表中最多只能有一个可变参数。
参数传递
按照数据的存储特点来分:
值类型的数据:操作的是数据本身、int、string、bool、float64、array...
引l用类型的数据:操作的是数据的地址 slice、map、chan...
值传递
package main
import "fmt"
func main() {
// 值传递
// 定义一个数组 [个数]类型
arr := [5]int{1, 2, 3, 4, 5}
fmt.Println("原数组:", arr)
updateArray(arr) // 调用函数,传递数组
fmt.Println("函数调用后数组:", arr) // 函数调用后,原数组未改变
}
func updateArray(arr2 [5]int) {
fmt.Println("函数内修改前的数组:", arr2)
// 修改数组的第一个元素
arr2[0] = 100
fmt.Println("函数内修改后的数组:", arr2)
}
引用传递
我们知道,变量在内存中是存放在一定的地址上的,修改变量实际是修改变量地址处的内存。
package main
import "fmt"
// 引用传递
func main() {
// 切片:可以扩容的数组
// 定义一个切片
s1 := []int{1, 2, 3, 4, 5}
fmt.Println("默认切片:", s1)
// 传入的引用类型的数据,地址是一样的
updateSlice(s1) // 调用函数,传递切片
fmt.Println("调用后的切片:", s1)
// 引用传递:传递的是数据的地址,导致多个变量指向同一块内存。
// 引用数据的类型,默认都是引用传递:slice、map、chan
}
func updateSlice(s2 []int) {
fmt.Println("原切片:", s2)
// 修改切片的第一个元素
s2[0] = 100
// 切片是引用类型,修改后会影响原切片
fmt.Println("修改后的切片:", s2)
}
函数中变量的作用域
作用域:变量可以使用的范围
局部变量:函数内部定义的变量,叫做局部变量
全部变量:函数外部定义的变量,叫做全局变量
package main
import "fmt"
func main(){
temp := 100
if a := 1; a <= 10{
fmt.Println(temp)
fmt.Println(a)
}
fmt.Println(temp)
// fmt.Println(a) 访问不了在if语句中定义的a
if b := 1; b <= 10{
temp := 50
fmt.Println(temp) // 就近原则,访问if语句中的temp,而不是全局的
fmt.Println(b)
}
}
递归函数
定义:一个函数自己调用自己,就叫做递归函数。
注意:递归函数需要有一个出口,逐渐向出口靠近,没有出口就会形成死循环。
package main
import "fmt"
// main 函数:程序入口
func main() {
// 调用 getSum 函数,计算从 1 到 n 的累加和
// 传入参数 n 的值为 10
result := getSum(10)
// 输出累加结果
fmt.Println("累加结果为:", result)
}
// 函数:计算从 1 到 n 的累加和
// getSum 函数使用递归方式实现
// 参数 n:表示累加的上限
// 返回值:返回从 1 到 n 的累加和
// 注意:递归函数需要有终止条件,否则会导致无限递归
// 递归函数:函数内部调用自身
// 递归函数的基本结构:
// 1. 递归终止条件:当满足某个条件时,停止递归
// 2. 递归调用:在函数内部调用自身,传入不同的参数
// 3. 返回值:递归函数通常会返回一个值,表示计算结果
func getSum(n int) int {
if n == 0 {
// 递归终止条件:当 n 为 0 时,返回 0
return 0
}
return getSum(n-1) + n
// 递归调用 getSum 函数,直到 n 为 0
}
// 每次递归调用都会将 n 的值减 1,直到达到终止条件
// 最终返回从 1 到 n 的累加和
递归程序运行分析:
//逐层返回计算结果:
getSum(1) = getSum(0) + 1 = 0 + 1 = 1
getSum(2) = getSum(1) + 2 = 1 + 2 = 3
getSum(3) = getSum(2) + 3 = 3 + 3 = 6
getSum(4) = getSum(3) + 4 = 6 + 4 = 10
getSum(5) = getSum(4) + 5 = 10 + 5 = 15
getSum(6) = getSum(5) + 6 = 15 + 6 = 21
getSum(7) = getSum(6) + 7 = 21 + 7 = 28
getSum(8) = getSum(7) + 8 = 28 + 8 = 36
getSum(9) = getSum(8) + 9 = 36 + 9 = 45
getSum(10) = getSum(9) + 10 = 45 + 10 = 55
//为什么累加和是 55?
数学本质:该递归计算的是 1 到 n 的自然数之和
求和公式:S = n(n+1)/2
代入 n=10:
S = 10 × 11 / 2 = 110 / 2 = 55
//递归调用栈示意图
getSum(10)
→ getSum(9)
→ getSum(8)
→ ...
→ getSum(0) = 0 // 触底反弹
← getSum(1) = 1
← getSum(2) = 3
← getSum(3) = 6
← ... 最终返回 55
然而,递归虽然简洁,但存在栈溢出的风险(尽管对于n=10来说不会发生),而且效率不如迭代。
package main
import "fmt"
func main() {
result := getSum(10)
fmt.Println("累加结果为:", result)
}
func getSum(n int) int {
sum := 0
for i := 1; i <= n; i++ {
sum += i
}
return sum
}
优化说明:
迭代替代递归:
原递归逻辑:
getSum(n) = n + getSum(n-1)
改为使用循环累加:初始化
sum=0
,从1遍历到n,逐次累加到sum
处理边界条件:
当
n <= 0
时,循环不会执行,直接返回0(与原代码n==0
时行为一致)支持负数输入(返回0,避免原递归的无限递归问题)
优势:
避免栈溢出:无递归调用深度限制
高效:时间复杂度 O(n),空间复杂度 O(1)
清晰:线性流程更易理解和维护
deferred函数
deferred语义:推迟、延迟
在go语言中,使用defer关键字来延迟一个函数载者方法的执行。
defer函数或者方法:一个函数或方法的执行被延迟了
你可以在函数中添加多个defer语句,当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回,特别是当你在进行一些打开资源的操作时,遇到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造成资源泄露等问题。
如果有很多调用defer,那么defer是采用后进先出(栈)模式。
package main
import "fmt"
func main() {
f("1")
fmt.Println("2")
defer f("3") //会延迟到最后执行
fmt.Println("4")
defer f("5") //会延迟到最后执行
fmt.Println("6")
}
func f(s string) {
fmt.Println(s)
}
// 输出结果:1, 2, 4, 6, 5, 3。
// 解释:
// 1. 首先执行f("1"),输出 "1"。
// 2. 然后输出"2"。
// 3. 接着遇到 defer f("3"),这个函数调用会被延迟到 main 函数结束时执行,所以"3"不会立即输出。
// 4. 输出"4"。
// 5. 再次遇到 defer f("5"),这个函数调用也会被延迟到 main 函数结束时执行,所以"5"也不会立即输出。
// 6. 输出"6"。
// 7. 最后,main 函数结束时,所有的 defer 调用按照后进先出(LIFO)的顺序执行,所以先输出 "5",再输出 "3"。
// 这样,最终输出的顺序是:1, 2, 4, 6, 5, 3。
// 注意:defer 语句的执行顺序是后进先出(LIFO),即最后一个 defer 语句最先执行。
// 在这个例子中,defer f("3") 和 defer f("5")
// 都被延迟到 main 函数结束时执行,所以它们的输出顺序是相反的。
// 这就是为什么输出结果是 "1, 2, 4, 6, 5, 3" 的原因。
defer函数传递参数的时候
package main
import "fmt"
func main() {
a := 10
fmt.Println("main a:", a)
defer f(a) //先调用,后执行
a++
fmt.Println("main end a:", a)
}
func f(s int) {
fmt.Println("函数中的a:", s)
}
defer的用法:
对象.close()临时文件的删除
文件.open() defer.close() 读或写操作
go语言中关于异常的处理,使用 panic() 和 recover()
panic 函数用于引发恐慌,导致程序中断执行
recover 函数用于恢复程序的执行,recover() 语法上要求必须在 defer 中执行。
高级:函数的数据类型
package main
import "fmt"
func main() {
// f1 如果不加括号,则 f1 是一个函数类型
// f1() 如果加上括号,则 f1 是一个函数调用
fmt.Printf("%T\n", f1) // 输出函数 f 的类型
fmt.Printf("%T\n", 10) // 输出整数 10 的类型
fmt.Printf("%T\n", "hello") // 输出函数 hello 的类型
}
func f1(a, b int) int {
return 0
}
// 输出结果:func() 说明 f 是一个函数类型
// 输出结果:int 说明 10 是一个整数类型
// 输出结果:string 说明 "hello" 是一个字符串类型
// 输出结果:func(int, int) 说明 f1 是一个函数类型,接受两个 int 参数
// 输出结果:func(int, int) int 说明 f1 是一个函数类型,接受两个 int 参数并返回一个 int
高级:函数的本质
package main
import "fmt"
// func() 本身就是一个数据类型
func main() {
// f1 如果不加括号,则 f1 是一个函数类型
// f1() 如果加上括号,则 f1 是一个函数调用
fmt.Printf("%T\n", f1) // 输出函数 f 的类型
// 定义函数类型的变量
var f2 func(int, int) // f2 是一个函数类型,接受两个 int参数
f2 = f1 // 将 f1 赋值给 f2
fmt.Println(f1) // 输出函数 f1 的内存地址
fmt.Println(f2) // 输出函数 f2 的内存地址
f2(10, 20) // 调用函数 f2,传入参数 10 和 20
}
func f1(a, b int) {
fmt.Println(a, b)
}
// 输出结果:
// func(int, int)
// 0xa0c3c0 // f1 的内存地址
// 0xa0c3c0 // f2 的内存地址
// 10 20
函数在Go语言中是复合类型,可以看做是一种特殊的变量。
函数名():调用返回结果
函数名:指向函数体的内存地址,一种特殊类型的指针变量
高级:匿名函数
Go语言是支持函数式编程:
将匿名函数作为另外一个函数的参数,回调函数。
将匿名函数作为另外一个函数的返回值,可以形成闭包结构。
匿名函数的定义如下:
func (参数)(返回值){
函数体
}
立即执行函数:
func main(){
func(x, y int){
fmt.Println(x + y)
}(10, 20)// 匿名函数定义完加()直接执行
}
实例:
package main
import "fmt"
func main() {
// 匿名函数就是没有名字的函数
f1()
f2 := f1
f2()
// 匿名函数,函数体后增加一个()执行,通常只能执行一次
func() {
fmt.Println("匿名函数")
}()
// 将匿名函数赋值,单独进行调用
f3 := func() {
fmt.Println("匿名函数...")
}
f3()
// 定义代参数的匿名函数
func(a, b int) {
fmt.Println(a, b)
}(1, 2)
// 定义代返回值的匿名函数
r1 := func(a, b int) int {
return a + b
}(1, 2) // 带了括号就是函数调用
fmt.Println(r1)
r2 := func(a, b int) int {
return a + b
} // 将匿名函数赋值给了 r2
fmt.Println(r2(1, 2))
}
func f1() {
fmt.Println("我是f1函数")
}
高级:回调函数
高阶函数:根据go语言的数据类型的特点,可以将一个函数作为另外一个函数的参数。
fun1(), fun2()
将 f1 函数作为 fun2 这个函数的参数
fun2 函数:就叫做高阶函数,接收了一个函数作为参数的函数
fun1 函数:就叫做回调函数,作为另外一个函数的参数
package main
import "fmt"
func main() {
r1 := add(1, 2)
fmt.Println(r1)
r2 := operate(3, 4, add)
fmt.Println(r2)
r3 := operate(5, 6, sub)
fmt.Println(r3)
// 使用匿名函数作为参数
r4 := operate(7, 8, func(a, b int) int {
return a * b
})
fmt.Println(r4)
// func1 := func(a, b int) int {
// return a * b
// }
// r4 := operate(7, 7, func1)
// fmt.Println(r4)
r5 := operate(8, 4, func(a, b int) int {
if b == 0 {
fmt.Println("除数不能为0")
return 0
}
return a / b
})
fmt.Println(r5)
// func2 := func(a, b int) int {
// if b == 0 {
// fmt.Println("除数不能为0")
// return 0
// }
// return a / b
// }
// r5 = operate(8, 4, func2)
// fmt.Println(r5)
}
//高阶函数。可以接收一个函数作为参数
func operate(a, b int, f func(int, int) int) int {
return f(a, b)
}
func add(a, b int) int {
return a + b
}
func sub(a, b int) int {
return a - b
}
高级:闭包
闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说。闭包=函数+引用环境
。
package main
import "fmt"
/*
一个外层函数中,有内层函数,该内层函数中,会操作外层函数的局部变量并且该外层函数的返回值就是这个内层函数。这个内层函数和外层函数的局部变量,统称为闭包结构
局部变量的生命周期就会发生改变,正常的局部变量会随着函数的调用而创建,随着函数的结束而销毁但是闭包结构中的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还在继续使用
*/
func main(){
r1 := increment()
fmt.Println(r1)
v1 := r1()
fmt.Println(v1)
v2 := r1()
fmt.Println(v2)
fmt.Println(r1())
fmt.Println(r1())
fmt.Println(r1())
fmt.Println("--------------------")
r2 := increment() // 和r1指向同一个内存地址
v3 := r2()
fmt.Println(v3)
fmt.Println(r1())
fmt.Println(r2())
}
// 自增
func increment() func() int {
// 局部变量i
i := 0
// 定义一个匿名函数,给变量自增并返回
func := func() int { // 内层函数,没有执行
// 局部变量的生命周期发生了变化
i ++
return i
}
return fun
}