前言
Go中对函数的使用非常普遍,Go语言中没有默认参数这个概念。
函数格式
func 函数名(参数1,参数2,......)(返回值1,返回值2,....){ }
package main import ( "fmt" ) //函数 //函数的定义:Go是强类型语言必须给 参数、和返回值指定数据类型 //返回值使用和参数各使用()分开 func sum(x1 int, x2 int) (ret int) { return x1 + x2 } //没有返回值的函数 func f1(x1 int, x2 int) { println(x1 + x2) } //没有参数的函数,也没有返回值的函数 func f3() { fmt.Println( "hello world" ) } //没有参数,但是有返回值的函数 func f4() string { return "Hello World" } //函数的返回值可以命名也不可不命名 func f5(x int, y int) (ret int) { ret = x + y return //使用命名返回值,为ret,return后面可省略 } //返回多个返回值 func f6() (int, string) { return 1, "2" } //参数的类型简写:当函数有多个参数并且类型一致时,我们可以简写前面的参数类型 func f7(x, y, z int, n, o string, i, j bool) int { return x + y } //可变长的参数:可变长参数必须放在最后 func f8(x string,y...int){ fmt.Println(x) fmt.Println(y) //切片类型[1 2 3] } func main() { ret := sum(1, 1) fmt.Println(ret) f1(2, 3) str01 := f4() fmt.Println(str01) n, s := f6() fmt.Println(n, s) f8( "下雨了" ,1,2,3) } |
函数参数
在Go语言中函数的所有传参都是值传递(传值)都是1个副本。
副本的内容是值类型(int、string、bool、array、struct 属于值类型),这样在函数中就无法修改原内容数据;
副本的内容是引用类型(pointer、slice、map、chan 属于引用类型),这样在函数中就可以修改原内容数据;

package main import "fmt" func changeSliceVal(a []int) { fmt.Printf("2.changeSliceVal函数调用中:变量a的内存地址是:%p\n", &a) a[0] = 99 fmt.Printf("2.changeSliceVal函数调用中:变量a的内存地址是:%p\n", &a) } func main() { a := []int{1, 2, 3, 4} fmt.Printf("1.changeSliceVal函数调用前:变量a的内存地址是:%p\n", &a) changeSliceVal(a) fmt.Printf("3.changeSliceVal函数调用后:变量a的内存地址是:%p\n", &a) }
package main import "fmt" var s1 []string func modify(s1 []string) { s1[0] = "Zero" //修改了s1副本和s1本身对应的底层数组的同1个位置 } func expansion(s2 []string) { s2 = append(s2, "3" ) //扩展了、不算修改底层数组原处! fmt.Println( "我append数据了啊:" ,s2) } func main() { s1 = []string{ "1" , "2" } s1[0] = "一" fmt.Println(s1) modify(s1) fmt.Println(s1) expansion(s1) fmt.Println( "增加了没有" ,s1) } |
可变参数
可变长的参数
package main import "fmt" func f1(args ... interface {}) { //args空接口类型的slice类型 fmt.Printf( "类型:%T,可变参数值:%v\n" , args, args) } func main() { f1() f1(1) f1(2, 3, 4) //拆开传值 slice1 := [] interface {}{5, 6, 7} f1(slice1...) } |
参数总结
//1.可选参数:调用f1函数是option可以不传,传参之后函数接收到1个slice。 func f1(option ...string) { fmt.Println(option) } //2.必须传参:参数可以是任意类型,如果没有参数可以传nil func f2(option interface{}) { fmt.Println(option) } //3.兼容以上2种情况option可选参数,参数可以是任意类型 func f3(option ...interface{}) { fmt.Println(option) }
闭包函数
1个函数嵌套了另1个函数,内层的函数引用了外层函数的参数或者变量之后,这个外层函数就行包一样包住了内层的函数,外层函数就叫闭包函数。
值得注意得是在Go的函数里面,你只能定义1个匿名函数,不能像Python 一样在函数内部在声明1个有名函数。
Python versus Golang
闭包的原理
1.函数A可以作为函数B的1个参数传到函数B。
2.函数查找变量的顺序为 首先在自己内部找,找不到再去外层函数找。
3.函数A也可以作为函数B的返回值返回。
package main import "fmt" //1个简单的闭包函数 结束1个 int参数x,返回1个参数为int和返回值为int的函数 func adder(x int) func (int) int { //内部的匿名函数引用了外部adder函数的变量x就称为闭包 return func (y int) int { x += y return x } } func main() { ret := adder(10) fmt.Println(ret(100)) } |
package main import "fmt" //1.需求在不改变CEO和CTO写得代码的情况下,把CTO写得的函数传到CEO函数中执行 //CEO写得函数(代码不能动) func ceo(f func ()) { fmt.Println( "This is CEO 函数" ) f() } //CTO写的函数 func cto(y...int) { var sum int for _,v:= range (y){ sum+=v } fmt.Println( "This is CTO 函数" ,sum) } //我写得函数 func wraper(f func (...int), a ...int) func () { tmp := func () { f(a...) } fmt.Println( "This is function code by me" ) return tmp } func main() { // //匿名函数 // var f1 = func(x, y int) { // fmt.Println(x + y) // } // f1(10, 20) // //立即执行函数 // func(x, y int) { // fmt.Println(x + y) // }(10, 200) //闭包 w := wraper(cto, 2020, 4,4,10,41) ceo(w) } |
闭包面试题
package main import "fmt" func cal(base int) ( func (int) int, func (int) int) { add := func (i int) int { base += i return base } sub := func (i int) int { base -= i return base } return add, sub } func main() { f1, f2 := cal(10) //由于 内部函数一直引用外部函数的变量i,所以外部函数的变量i一直在变。 fmt.Println(f1(1), f2(2)) //11 9 fmt.Println(f1(3), f2(4)) //12 8 fmt.Println(f1(5), f2(6)) //13 7 } |
递归函数
递归。递 、 归 所说的就是 递和归2个不同动作过程。有递也有归才叫递归。递就是压栈的过程,归就是出栈的过程。
递归函数就是自己调用自己
应用场景:问题相同但是每次问题规模都减小
必须有1个退出条件:递 、归 。递 、归不能无限得传递(压栈)达到了1临界值(达到了目的) 就得归(出栈)。
package main import "fmt" func test(n int){ fmt.Println( "--->n=" ,n) if n<3{ n++ test(n) //从这里进去的,就从这里出来 } fmt.Println( "<----n=" ,n) } func main() { test(1) } /* main函数开始执行 test(1):栈(一) 1.fmt.Println("--->n=",n) 打印:--->n= 1 2.if n<3{n=1所以条件成立}n++之后 n=2 3.遇到函数自己调用自己,开辟新栈(二)也就是test(2) ############################################################### 13.出栈二来到在栈一步骤3位置,代码往下执行fmt.Println("<----n=",n)栈一中此时变量n=3啊,所以打印:<----n= 2 14.main函数结束 test(2) 栈(二) 4.fmt.Println("--->n=",n) 打印:--->n= 2 5.if n<3{n=2所以条件成立}n++之后 n=3 6.遇到函数自己调用自己,开辟新栈(三)也就是test(3) ############################################################### 11.出栈三 来到在栈二步骤6的位置,代码往下执行fmt.Println("<----n=",n)栈二中此时变量n=3啊,所以打印:<----n= 3 12.栈(一)步骤3给我压栈的栈,我出栈就到栈(一)步骤3 test(3)栈(三) 7.fmt.Println("--->n=",n) 打印:--->n= 3 8.if n<3{n=3所以条件不成立了} n++不执行 test()也不会调用自己进行压栈 9.不压栈了就出栈,看到fmt.Println("<----n=",n)就执行所以打印:<----n= 3 10.然后出栈,步骤6给我压栈的栈,我出栈了就从步骤6 出去 */ |
文件夹&递归思想
递归思想对我们影响深远,每天打开电脑进入文件夹、查找子文件、进入子文件 也算得上是递归操作
递:进入1个目录 ------>进入这个目录的子目---------> 进入子目录的子目录......
临界值:找到/看到自己想要的
归:再逐步退出来
rm -rf /* 中的 -r 选项是什么意思?
-r, -R, --recursive remove directories and their contents recursively
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import os file_info = [] def recursion_files(inital_path = "D:\goproject" ): files = os.listdir(inital_path) if not files: return for item in files: fileAbs = os.path.join(inital_path,item) if os.path.isfile(fileAbs): file_info.append(fileAbs) else : #如果有文件夹就把该文件夹下的子目录保存进行压栈操作 recursion_files(inital_path = fileAbs) recursion_files() print (file_info) |
阶乘
1 2 3 4 5 6 7 8 9 10 11 12 | package main import "fmt" func f(n int) int { if n <= 1 { return 1 } return n*f(n-1) } func main() { ret:=f(5) fmt.Println(ret) } |
有N个台阶,1次可以走1步,也可以走2步,请问有多少种走法?
package main import "fmt" func f(n int) int { if n == 1 { return 1 } if n == 2 { return 2 } return f(n-1) + f(n-2) } func main() { ret := f(3) fmt.Println(ret) } |
Golang异常处理
Golang程序发生异常只能在函数的defer中捕捉并recover这个Panic。
子Go程发生Panic会导致整个Go进程终止,Panic不可以跨Go程进行捕捉和Recover。
当前函数执行--->panic---->defer---->recover--->当前函数结束
现象
- Panic发生时,后台打印输出的第1行,打印发生Panic的代码位置
- 使用defer recover处理Panic之后后台不会打印输出Panic信息
package main import "fmt" func main() { defer func() { if err := recover(); any(err) != nil { // 在这里可以执行一些清理异常的操作 fmt.Println("发生了 panic:", any(err)) } }() defer func() { fmt.Println("---------") }() // 模拟一个引发 panic 的情况 panic(any("这是一个自定义的panic")) //这里的代码将不会被执行,因为上面的 panic 已经终止了程序的正常流程 // 当函数遇到了panic后就不会再继续执行当前函数中的代码,而是开始按顺序执行函数中的defer函数 fmt.Println("这段代码不会被执行") }
error和Panic区别
1.err!=nil
主要用于处理预期的错误。当函数执行过程中发生预期的错误时,返回一个非空的错误对象,需要检查错误是否为空并采取相应措施来处理这个错误。
2.Panic/recover
主要用于处理意外的错误。当程序遇到一个致命错误时,抛出1个 Panic 异常,程序会停止运行。可以使用 recover 函数来捕获这个 panic 异常,使程序不会崩溃。
3.注意
Panic/recover 不应该被用于处理可预测的错误,它们只适用于必须立即停止程序的情况。
defer关键字
代码大多情况下是按照由上至下的顺序执行的,而Python中的 yeild、send(上下切换执行=协程)还有Golang中的defer关键字可以改变程序的执行顺序。
在Go的函数里可以声明defer关键字+函数,有延迟调用函数的效果,多用于释放sync.Mutex、file句柄、socket资源、数据库事务操作。
这是1个经典的例子我使用了defer+recover检测事务执行过程中可能出现的err和panic再进行RollBACK。

//sqlx事务操作:transactionsn: Noun(一笔)交易,业务,买卖;办理;处理 func sqxTransaction() (err error) { var transaction *sqlx.Tx transaction, err = db.Beginx() //开启事务 if err != nil { fmt.Println("开始事务失败", err) return err } //defer延迟执行函数,最后检查程序执行过程中是否出现panic?是否返回错误? defer func() { //程序执行出现panic之后回滚 if p := recover(); p != nil { transaction.Rollback() panic(p) //程序执行返回错误之后回滚。 } else if err != nil { fmt.Println("事务回滚:", err) transaction.Rollback() //程序执行过程没出现panic、也没有返回err信息,提交事务。 } else { err = transaction.Commit() fmt.Println("事务执行成功") } }() //执行SQL1 sqlStr1 := "update user set age=20 where id=?" ret, err := transaction.Exec(sqlStr1, 1) if err != nil { fmt.Println(err) return err } affectedRow, err := ret.RowsAffected() if err != nil { return err } if affectedRow != 1 { return errors.New("执行SQL1时未对数据库进行修改!\n") } //执行SQL1成功之后开始执行SQL2 sqlStr2 := "update user set age=19 where id=?" ret, err = transaction.Exec(sqlStr2, 2) if err != nil { return err } affectedRow, err = ret.RowsAffected() if err != nil { return err } if affectedRow != 1 { return errors.New("执行SQL2时未对数据库进行修改!\n") } return err }
声明1个defer 就是开辟1层独立的栈针进行逐层压栈操作,在函数体内语句执行完毕之后,按照先进后出(后进先出)的顺序出栈。
defer关键字执行时机
Go语言的函数中return
语句在底层并不是原子操作。
它分为2部执行:
1.返回值赋值
2.执行RET指令两步。
defer
语句执行的时机就在返回值赋值操作后,RET指令执行前。
具体如下图所示:
defer的执行步骤如下:
1.先执行函数中语句内容
2.遇到defer关键字 开辟独立的defer栈空间(不同于函数)逐一压栈(不执行)
3.给函数中return值=赋值
4.按先入后出的顺序 执行defer栈中的语句内容
package main import "fmt" func f1() int { x := 5 defer func () { x++ // }() return x } func f2() (x int) { defer func () { x++ }() return 5 //1.先给返回值赋值 x=5 2.defer压栈x=5 3.执行x=5+1 4.return x=6 } func f3() (y int) { x := 5 defer func () { //压栈时开辟了独立的空间x=5 x+1 x++ }() return x //这里是x=y=5 } func f4() (x int) { defer func (x int) { x++ //参数改写的是副本 }(x) return 5 //1.先给返回值赋值x=5 2.defer开辟独立的栈 压栈x=5 3.执行x++ 4.returm } func main() { fmt.Println(f1()) //5 fmt.Println(f2()) //6 fmt.Println(f3()) //5 fmt.Println(f4()) //5 } |
当defer的函数语句中遇到函数调用先执行函数然后再压栈
defer func1("1", a, fun2("10", a, b)
defer压栈时遇到函数调用,先把函数执行完毕之后,再执行压栈。
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() { a := 1 b := 2 //1.先执行calc("10", a, b)打印"10" 1 3 4 //2.压栈1 defer calc("1", a, 4) defer calc( "1" , a, calc( "10" , a, b)) a=0 //3.再执行calc("20", a, b)打印"20" 0 2 2 //4.压栈2:defer calc("2", 0, 2) defer calc( "2" , a, calc( "20" , a, b)) //栈2出栈:2 0 2 2 //栈1出栈:1 1 3 4 b=1 } |
如何在嵌套函数内修改函数外部的变量?指针啊!
package main import "fmt" func outerFunc()(n int) { defer func(n *int ){ *n+=10 fmt.Println(*n) }(&n) n=900 return n } func main() { n := outerFunc() fmt.Println(n) }
defer里面再嵌套defer会是什么效果?递归吗?

package main import "fmt" func f1() { fmt.Println("f1开始") defer func() { fmt.Println("f2开始") defer func() { fmt.Println("f3开始") defer func() { fmt.Println("f4开始") defer func() { fmt.Println("f4结束") }() }() fmt.Println("f3结束") }() fmt.Println("f2结束") }() fmt.Println("f1结束") } func outerFunc() { fmt.Println("outerFunc开始") defer f1() fmt.Println("outerFunc结束") } func main() { outerFunc() }
分金币
package main import "fmt" var ( coins = 50 users = []string{ "Matthew" , "Sarah" , "Augustus" , "Heidi" , "Emilie" , "Peter" , "Giana" , "Adriano" , "Aaron" , "Elizabeth" , } distribution = make( map [string]int, len(users)) ) /* 你有50枚金币,需要分配给以下几个人:Matthew,Sarah,Augustus,Heidi,Emilie,Peter,Giana,Adriano,Aaron,Elizabeth。 分配规则如下: a. 名字中每包含1个'e'或'E'分1枚金币 b. 名字中每包含1个'i'或'I'分2枚金币 c. 名字中每包含1个'o'或'O'分3枚金币 d: 名字中每包含1个'u'或'U'分4枚金币 写一个程序,计算每个用户分到多少金币,以及最后剩余多少金币? 程序结构如下,请实现 ‘dispatchCoin’ 函数 */ func dispatchCoins() { for _, user := range users { for _, c := range user { switch c { case 'e' , 'E' : distribution[user]++ coins-- case 'i' , 'I' : distribution[user] += 2 coins -= 2 case 'o' , 'O' : distribution[user] += 3 //分金币 coins -= 3 //分出金币之后需要从总金币数量扣除 case 'u' , 'U' : distribution[user] += 4 coins -= 4 } } } fmt.Println(distribution) //打印每人分到的金币 fmt.Println( "剩余" , coins) //剩余的金币 } func main() { dispatchCoins() } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
2019-03-29 CloudStack 云计算平台框架
2017-03-29 Python购物车