1. 定义格式
  • 函数构成代码执行的逻辑结构。在Go语言中,函数的基本组成为:关键字func、函数名、参数列表、返回值、函数体和返回语句。

  • Go 语言函数定义格式如下:

    func FuncName(/*参数列表*/) (o1type1,o2type2/*返回类型*/) {
        //函数体
    
        return v1, v2//返回多个值
    }
    
  • 函数定义说明:

  1. func:函数由关键字 func 开始声明
  2. FuncName:函数名称,根据约定,函数名首字母小写即为private,大写即为public
  3. 参数列表:函数可以有0个或多个参数,参数格式为:变量名类型,如果有多个参数通过逗号分隔,不支持默认参数
  4. 返回类型:
    ① 上面返回值声明了两个变量名o1和o2(命名返回参数),这个不是必须,可以只有类型没有变量名
    ② 如果只有一个返回值且不声明返回值变量,那么你可以省略,包括返回值的括号
    ③ 如果没有返回值,那么就直接省略最后的返回信息
    ④ 如果有返回值,那么必须在函数的内部添加return语句
2. 自定义函数

2.1 无参无返回值

func Test() {//无参无返回值函数定义
    fmt.Println("this is a test func")
}

func main() {
    Test() //无参无返回值函数调用
}

2.2 有参无返回值

2.2.1 普通参数列表

func Test01(v1 int,v2 int) {//方式1
    fmt.Printf("v1=%d,v2=%d\n",v1,v2)
}
func Test02(v1,v2int) {//方式2, v1, v2都是int类型
    fmt.Printf("v1=%d,v2=%d\n",v1,v2)
}
func main() {
    Test01(10,20) //函数调用
    Test02(11,22) //函数调用
}

2.2.2 不定参数列表

  • 不定参数类型参数名 ... 数据类型表示不定参数

  • 形如…type格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数

  • 不定参一定要放在最后

  • 固定参数必须传值,不定参数根据需要决定是否传值/font>
    不定参数是指函数传入的参数个数为不定数量。为了做到这点,首先需要将函数定义为接受不定参数类型:

    //形如...type格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数
    func Test(args ... int) {
        for_,n := range args{//遍历参数列表
            fmt.Println(n)
        }
    }
    func main() {
        //函数调用,可传0到多个参数
        Test()
        Test(1)
        Test(1,2,3)
    }
    
    • 不定参数的传递
    func MyFunc01(args...int) {
        fmt.Println("MyFunc01")
        for_,n := range args{//遍历参数列表
            fmt.Println(n)
        }
    }
    func MyFunc02(args ... int) {
        fmt.Println("MyFunc02")
        for_,n:=range args{//遍历参数列表
            fmt.Println(n)
        }
    }
    func Test(args ... int) {
        MyFunc01(args...)//按原样传递,Test()的参数原封不动传递给MyFunc01
        MyFunc02(args[1:]...)//Test()参数列表中,第1个参数及以后的参数传递给MyFunc02
    }
    func main() {
        Test(1,2,3)//函数调用
    }
    

2.3 无参有返回值

  • 有返回值的函数,必须有明确的终止语句,否则会引发编译错误。

2.3.1 一个返回值

func Test01() int {//方式1
    return 250
}
//官方建议:最好命名返回值,因为不命名返回值,虽然使得代码更加简洁了,但是会造成生成的文档可读性差
func Test02() (value int) {//方式2,给返回值命名
    value=250
    returnvalue
}
func Test03() (value int) {//方式3,给返回值命名
    value = 250
    return
}
funcmain(){
    v1 := Test01()//函数调用
    v2 := Test02()//函数调用
    v3 := Test03()//函数调用
    fmt.Printf("v1=%d,v2=%d,v3=%d\n", v1, v2, v3)
}

2.3.2 多个返回值

func Test01() (int,string) {//方式1
    return 250,"sb"
}
func Test02() (aint,str string) {//方式2,给返回值命名
    a=250
    str="sb"
    return
}
funcmain(){
    v1,v2:=Test01()//函数调用
    _,v3:=Test02()//函数调用,第一个返回值丢弃
    v4,_:=Test02()//函数调用,第二个返回值丢弃
    fmt.Printf("v1=%d,v2=%s,v3=%s,v4=%d\n",v1,v2,v3,v4)
}

2.4 有参有返回值

//求2个数的最小值和最大值
func MinAndMax(num1 int, num2 int) (min int, max int) {
    if num1 > num2{//如果num1大于num2
        min = num2
        max = num1
    } else {
        max = num2
        min = num1
    }
    return
}
func main() {
    min,max := MinAndMax(33,22)
    fmt.Printf("min=%d,max=%d\n", min, max)  //min=22,max=33
}
3. 递归函数
  • 递归指函数可以直接或间接的调用自身。
  • 递归函数通常有相同的结构:一个跳出条件和一个递归体。所谓跳出条件就是根据传入的参数判断是否需要停止递归,而递归体则是函数自身所做的一些处理。
    //通过循环实现1+2+3……+100
    func Test01() int {
        i := 1
        sum := 0
        for i = 1; i <= 100; i++ {
            sum += i
        }
    
        return sum
    }
    
    //通过递归实现1+2+3……+100
    func Test02(num int) int {
        if num == 1 {
            return1
        }
    
        return num + Test02(num-1)//函数调用本身
    }
    
    //通过递归实现1+2+3……+100
    func Test03(num int) int {
        if num == 100 {
            return 100
        }
    
        return num + Test03(num+1)  //函数调用本身
    }
    
    func main() {
    
        fmt.Println(Test01())    //5050
        fmt.Println(Test02(100))    //5050
        fmt.Println(Test03(1))    //5050
    }
    
4. 函数类型
  • 内存四区模型
    Go语言:函数_函数调用

  • 函数名是一个地址,代表函数在代码区中的地址。

  • 在Go语言中,函数也是一种数据类型,我们可以通过type来定义它,它的类型就是所有拥有相同的参数,相同的返回值的一种类型。

    type FuncType func(int,int) int //声明一个函数类型,func后面没有函数名
    
    //函数中有一个参数类型为函数类型:fFuncType
    func Calc(a, b int,f FuncType) (result int) {
        result = f(a, b)//通过调用f()实现任务
        return
    }
    
    func Add(a, b int) int {
        return a + b
    }
    
    func Minus(a, b int) int {
        return a - b
    }
    
    func main() {
    //函数调用,第三个参数为函数名字,此函数的参数,返回值必须和FuncType类型一致
        result := Calc(1, 1, Add)
        fmt.Println(result)    //2
    
        var f FuncType = Minus
        fmt.Println("result=",f(10, 2))    //result=8
    }
    
5. 匿名函数与闭包
  • 所谓闭包就是一个函数“捕获”了和它在同一作用域的其它常量和变量。这就意味着当闭包被调用的时候,不管在程序什么地方调用,闭包能够使用这些常量或者变量。它不关心这些捕获了的变量和常量是否已经超出了作用域,所以只有闭包还在使用它,这些变量就还会存在。

  • 在Go语言里,所有的匿名函数(Go语言规范中称之为函数字面量)都是闭包。匿名函数是指不需要定义函数名的一种函数实现方式,它并不是一个新概念,最早可以回溯到1958年的Lisp语言。

    func main() {
        i := 0
        str := "mike"
    
        //方式1
        f1 := func(){//匿名函数,无参无返回值
            //引用到函数外的变量
            fmt.Printf("方式1:i=%d,str=%s\n",i,str)
        }
    
        f1()//函数调用
    
        //方式1的另一种方式
        type FuncType func()    //声明函数类型,无参无返回值
        var f2 FuncType = f1
        f2()    //函数调用
    
        //方式2
        var f3 FuncType = func(){
            fmt.Printf("方式2:i=%d,str=%s\n",i,str)
        }
        f3()//函数调用
    
        //方式3
        func(){  //匿名函数,无参无返回值
            fmt.Printf("方式3:i=%d,str=%s\n",i,str)
    }()    //别忘了后面的(),()的作用是,此处直接调用此匿名函数
    
        //方式4,匿名函数,有参有返回值
        v := func(a,b int) (result int) {
            result=a+b
            return
    }(1,1)//别忘了后面的(1,1),(1,1)的作用是,此处直接调用此匿名函数,并传参
        fmt.Println("v=",v)
    
    }
    
  • 闭包捕获外部变量特点:

    func main() {
        i := 10
        str := "mike"
        func(){
            i = 100
            str = "go"
            //内部:i=100,str=go
            fmt.Printf("内部:i=%d,str=%s\n",i,str)
        }()    //别忘了后面的(),()的作用是,此处直接调用此匿名函数
    
        //外部:i=100,str=go
        fmt.Printf("外部:i=%d,str=%s\n",i,str)
    }
    
  • 函数返回值为匿名函数:

    //squares返回一个匿名函数,func()int
    //该匿名函数每次被调用时都会返回下一个数的平方。
    func squares() func() int {
        var x int
        return func() int {//匿名函数
            x++//捕获外部变量
            return x*x
        }
    }
    
    func main() {
        f:=squares()
        fmt.Println(f())    //"1"
        fmt.Println(f())    //"4"
        fmt.Println(f())    //"9"
        fmt.Println(f())    //"16"
    }
    
  • 函数squares返回另一个类型为func() int的函数。对squares的一次调用会生成一个局部变量x并返回一个匿名函数。每次调用时匿名函数时,该函数都会先使x的值加1,再返回x的平方。第二次调用squares时,会生成第二个x变量,并返回一个新的匿名函数。新匿名函数操作的是第二个x变量。

  • 通过这个例子,我们看到变量的生命周期不由它的作用域决定:squares返回后,变量x仍然隐式的存在于f中。

6. 函数作用域

6.1 局部变量

  • 前面我们定义的函数中,都经常使用变量。那么我们看一下如下程序的输出结果:

    func Test() {
        a := 5
        a += 1
    } 
    
    func main() {
        a := 9
        Test()
        fmt.Println(a)
    }
    
  • 最终的输出结果是9,为什么呢?在执行fmt.Println(a)语句之前,我们已经调用了函数Test(),并在该函数中我们已经重新给变量a赋值了。但是为什么结果没有发生变化呢?这就是变量的作用范围(作用域)的问题。在Test( )函数中定义的变量a,它的作用范围只在改函数中有效,当Test( )函数执行完成后,在该函数中定义的变量也就无效了。也就是说,当Test( )函数执行完以后,定义在改函数中所有的变量,所占有的内存空间都会被回收。

  • 所以,我们把定义在函数内部的变量称为局部变量

  • 局部变量的作用,为了临时保存数据需要在函数中定义变量来进行存储,这就是它的作用。

  • 并且,通过上面的案例我们发现:不同的函数,可以定义相同的名字的局部变量,但是各用个的不会产生影响。例如:我们在main( )函数中定义变量a,在Test( )函数中也定义了变量a,但是两者之间互不影响,就是因为它们属于不同的函数,作用范围不一样,在内存中是两个存储区域。

6.2 全局变量

  • 有局部变量,那么就有全局变量。所谓的全局变量:既能在一个函数中使用,也能在其他的函数中使用,这样的变量就是全局变量.也就是定义在函数外部的变量就是全局变量。全局变量在任何的地方都可以使用。案例如下:

    var a int   //变量定义在函数外面
    func Test() {
        a := 5
        a += 1
    }
    
    func main() {
        a := 9
        Test()
        fmt.Println(a)
    }
    
  • 注意:在上面的案例中,我们在函数外面定义了变量a,那么该变量就是全局变量,并且Test( )函数和main( )函数都可以使用该变量。该程序的执行流程是:先执行main( )函数,给变量a赋值为9,紧接着调用Test( )函数,在改函数中完成对变量a的修改。

  • 由于main( )函数与Test( )函数所使用的变量a是同一个,所以当Test( )函数执行完成后,变量的a已经变成了6.回到main( )函数执行后面的代码,也就是 fmt.Println(a),输出的值就是6.

  • 可能有同学已经发现该程序和我们前面写的程序还有一点不同的地方是:第一个程序我们是a:=9,但是第二个程序执行修改成了a=9, 现在修改一下第二个程序如下:

    var a int   //变量定义在函数外面
    func Test() {
        a := 5
        a += 1
    }
    
    func main() {
        a := 9    // ====》注意这里
        Test()
        fmt.Println(a)
    }
    
  • 该程序与上面的程序不同之处在于,该程序是a:=9,上面的程序是a=9.现在大家思考一下该程序的结果是多少?最终结果是9.原因是:a:=9等价于var a int a=9也就是定义一个整型变量a,并且赋值为9.那么现在的问题是,我们定义了一个全局变量a,同时在main( )中又定义了一个变量也叫a,但是该变量是一个局部变量。当全局变量与局部变量名称一致时,局部变量的优先级要高于全局变量。所以在main( )函数中执行fmt.Println(a)时输出的是局部变量a的值。但是Test( )函数中的变量a还是全局变量。注意:大家以后在开发中,尽量不要让全局变量的名字与局部变量的名字一样。所以大家,思考以下程序执行的结果:

    var a int   //变量定义在函数外面
    func Test() {
        a := 5
        a += 1
        fmt.Println("Test",a)
    
    }
    
    func main() {
        a := 9   
        Test()
        fmt.Println("main",a)
    }
    

总结:
① 在函数外边定义的变量叫做全局变量。
② 全局变量能够在所有的函数中进行访问
③ 如果全局变量的名字和局部变量的名字相同,那么使用的是局部变量的,小技巧强龙不压地头蛇