随笔 - 241  文章 - 1  评论 - 58  阅读 - 85万 

前言

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)
}
View Code
复制代码
为什么引用类型的参数可以被修改?
至于/关于(with respect to...)使用切片作为函数参数,意味着该函数将会得到1个该切片的副本 - 该副本切片指向了该切片的底层数组的起始位置。
切片中有什么元素由该切片指向的底层数组决定, 2个不同的切片引用,指向了同1个底层数组;
2个切片中如果有1个修改了这个底层数组同1个位置,会影响另1个切片的元素,如果是追加则不会。

  

可变参数

可变长的参数 

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.需求在不改变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)
 
}

闭包面试题

  

递归函数 

递归。递 、 归 所说的就是 递和归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

阶乘

  

 有N个台阶,1次可以走1步,也可以走2步,请问有多少种走法?

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

}
sqlx事务操作
复制代码

 

声明1个defer 就是开辟1层独立的栈针进行逐层压栈操作在函数体内语句执行完毕之后,按照先进后出(后进先出)的顺序出栈

 

defer关键字执行时机

Go语言的函数中return语句在底层并不是原子操作。

它分为2部执行:

1.返回值赋值

2.执行RET指令两步。

defer语句执行的时机就在返回值赋值操作后,RET指令执行前

具体如下图所示:

defer的执行步骤如下:

1.先执行函数中语句内容

2.遇到defer关键字 开辟独立的defer栈空间(不同于函数)逐一压栈(不执行)

3.给函数中return值=赋值

4.按先入后出的顺序 执行defer栈中的语句内容

5.函数返回真正的返回值(执行ret指令)
 
 
 

  

当defer的函数语句中遇到函数调用先执行函数然后再压栈

defer func1("1", a, fun2("10", a, b)

defer压栈时遇到函数调用,先把函数执行完毕之后,再执行压栈。

 

 

如何在嵌套函数内修改函数外部的变量?指针啊!

复制代码
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()
}
defer嵌套
复制代码

 

 

 

 

 

分金币

你有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’ 函数
 

  

 

参考

posted on   Martin8866  阅读(6502)  评论(0编辑  收藏  举报
编辑推荐:
· 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购物车
点击右上角即可分享
微信分享提示