• go语言之函数及闭包


    一:函数

    1 概述:

    函数是 Go 程序源代码的基本构造单位,一个函数的定义包括如下几个部分,函数声明关键字 也町、 函数名、参数列表、返回列表和函数体。
    函数名遵循标识符的命名规则, 首字母的大小写决定该函数在其他包的可见性:大写时其他包可见,小写时只有相同的包可以访问;
    func 函数名 (参数列表) (返回值列表) { 
            函数体
    }        

    2 特点

    函数声明的格式

    方法名首字母大写是public,方法名首字母小写private私有方法

    1)函数类型

    package main
    
    import (
        "fmt"
    )
    //1 无参无返回值
    func test(){
        fmt.Println("三无产品")
    }
    //2 有参无返回值
    func test1(v1 int,v2 int){
        fmt.Println(v1,v2)
    }
    //3 有不定参数无返回值
    func test2(args ...int){
        for _,n :=range args{
            fmt.Println(n)
        }
    }
    
    //调用以上函数
    func main(){
        test()
        test1(1,2)
        test2(1,2,3,4)
    }
    package main
    
    import "fmt"
    
    //无参有返回值
    func test4(a int ,str string){
    	a = 666
    	str= "我是沙雕"
    	return
    }
    func test5() (int,string){
    	return 250,"字符串"
    }
    func main(){
    	a,str := test4()  //test4没有返回值,运行会报错
    	fmt.Println(a,str)
    	_,s :=test5()
    	fmt.Println(s)
    }
    
    package main
    
    import (
        "fmt"
    )
    //有参有返回值
    //求两个数的最大值和最小值
    func test1(num1 int,num2 int) (min int,max int){
        if num1>num2{
            min=num2
            max=num1
        }else {
            max=num2
            min=num1
        }
        return
    }
    func main(){
        min,max := test1(33,22)
        fmt.Println(min,max)
    }

    求1~100的和代码实现两种方法

    package main
    
    import "fmt"
    //循环实现1到100累加
    func test01() int {
        sum :=0
        for i :=1;i<=100;i++{
            sum +=i
        }
        return sum
    }
    //递归实现1到100的累加
    func test02(num int)int{
        if num==1{
            return 1
        }
        return num +test02(num-1)
    }
    
    func main()  {
        fmt.Println(test01())
        fmt.Println(test02(100))
    }

    2)注意:

    (1) 有参无返回值 ,参数名就相当于函数体内层的局部变量,命名返回值变量会被初始化类型零值
    (2)不支持默认值参数。
    (3)不支持函数重载。
    (4)不支持函数嵌套,严格地说是不支持命名函数的嵌套定义,但支持嵌套匿名函数

    3 多值返回

    func   swap(a,b int) (int,int){
             return  b,a      
    }

    4 实参到形参的传递

    package  main
    import "fmt"
    func chvalue(a int) int{
        a=a+1
        return a
    }
    func chpointer(a *int){
        *a = *a +1
        return
    }
    func main()  {
        a :=10
        chvalue(a)   //实参传递给形参是值拷贝
        fmt.Println(a)
    
        chpointer(&a) //实参传递给形参然仍是值拷贝,只不过复制的是a的地址值
        fmt.Println(a)
    }

    5 不定参数

    不定参数声明使用 (参数 .. . type) 的语法格式

    特点:

    (1) 所有的不定参数类型必须是相同的
    (2)不定参数必须是函数的最后一个参数。
    (3)不定参数名在函数体 内相当于切片,对切片的操作同样适合对不定参数的操作
    package main
    func sum(arr ...int)(sum int){
        for _, v := range arr{   //arr相当于切片
            sum += v
        }
        return 
    }

    6 切片可以作为参数传递给不定参数,切片名后要加上 ”...“  

    package main
    
    //import "go/types"
    
    func sum(arr ...int)(sum int){
        for _,v :=range arr{
            sum += v
        }
        return 
    }
    func main()  {
        slice := []int{1,2,3,4}
        array := [...]int{1,2,3,4}
        //数组不可以作为实参传递给不定参数的函数 所以sum(array...)会报错  cannot use array (type [4]int) as type []int in argument to sum
        sum(slice...)  
    }

    7  形参为不定参数的函数的函数和形参为切片的函数类型不相同

    package main
    
    import (
        "fmt"
    )
    
    func suma(arr ...int)(sum int){
        for  v := range arr{
            sum += v
        }
        return
    }
    
    func sumb(arr []int) (sum int)  {
        for v := range arr{
            sum += v
        }
        return
    }
    func main()  {
        //suma 和sumb的类型不一样
        fmt.Printf("%T
    ",suma)
        fmt.Printf("%T
    ",sumb)
    }

    8  函数签名

    概述:

    函数类型又 函数签名 个函 类型就是函数定义首行去掉函数名、参数名和{,可以
    使用台nt.Printf 的”%T”格式化参数打印函数的类型。
    package main
    
    import "fmt"
    
    func add(a,b int) int {
        return  a + b
    }
    func main()  {
        fmt.Printf("%T
    ",add)   //打印效果 func(int, int) int
    }

    两个函数类型相同的条件是:拥有相同的形参列表和返回值列表(列表元素的次序、个数和类型相同)形参名可以不同

    func add(a,b int) int { return a+b) 
    func sub (x int, y int) (c int) { c=x- y ; return c ) 

    可以使用 type 定义函数类型,函数类型变量可以作为函数的参数或返

    package main
    import "fmt"
    func add(a,b int) int {
        return a + b
    }
    func sub(a,b int) int {
        return a - b
    }
    type Op func(int,int) int //定义一个函数类型,输入的是两个int类型
                                //返回值是一个int类型
    func do(f Op,a,b int) int  {  //定义一个函数,第一个参数是函数类型Op
        return f(a,b)         //函数类型变量可以直接用来进行函数调用
    }
    func main(){
        a := do(add,1,2)        //函数名add可以当作相同函数类型的形参
        fmt.Println(a)                   //3
        s := do(sub,1,2)
        fmt.Println(s)                  //-1
    }

    总结:

    1 实际函数类型变 和函数名都可以当作指针变量,该指针指向函数代码开始位置 通常说函数类型变量是一
    种引用类型,未初始化的函数类型变量的默认值是nil
    2 有名函数的函数名可以看作函数类型的常 ,可以
    直接使用函数名调用函数,也可以直接赋值给函数类型变量,
    package main
    
    func sum(a,b int) int{
        return a + b
    }
    func main(){
        sum(3,4)  //直接调用
        f := sum        //有名函数可以直接赋值给变脸
        f(1,2)
    }

    9  匿名函数

    Go 提供两种函数 有名函数和匿名函数。匿名函数可以看作函数字面量 所有直接使用函
    数类型变量的地方都可以由匿名函数代替。医名函数可以直接赋值给函数变量,可以当作实参,
    也可以作为返回值,还可以直接被调用
    package main
    
    import "fmt"
    
    //匿名函数被直接赋值函数变量
    var sum = func(a,b int) int {
        return a + b
    }
    
    func doinput(f func(int,int) int,a,b int)  {
        return 
    }
    
    //匿名函数作为返回值
    func wrap(op string) func(int, int) int{
        switch op {
        case "add":
            return func(a int, b int) int {
                return a +b
            }
        case "sub":
            return func(a int, b int) int {
                return  a + b
            }
        default:
            return nil
        }
    }
    func  main() {
        //匿名函数被直接调用
        defer func() {
            if err :=recover();err !=nil{
                fmt.Println(err)
            }
        }()
        sum(1,2)
        //匿名函数作为实参
        doinput(func(x , y int) int {
            return  x + y
        },1,2)
        opFunc :=wrap("add")
        re := opFunc(2,3)
        fmt.Printf("%d
    ",re)
        fmt.Println(f)    
    }

    二 : defer关键字

    使用
    Go 函数里提供了 defe 关键字,可以注册多个延迟调用,这些调用以先进后出( FILO )的
    顺序在函数返回前被执行
    package main
    
    import "fmt"
    
    func test(x int){
        fmt.Println(100/x)
    }
    func main()  {
        //defer 是延迟操作
        defer fmt.Println("aaa")
        defer fmt.Println("bbb")
        //报错并不影响程序的运行
        defer test(0)
        defer fmt.Println("ccc")
    }

     注意 :

    1  defer 后面必须是函数或方法的调用,不能是语句,否则会报
     express on in defer must be function call 错误。
    2 defer 函数的实参在注册时通过值拷贝传递进去。
    package main
    func f() int{
        a := 0
        defer func(i int) {
            println("defer i=",i)  //defer i= 0
        }(a)
        a++
        return a
    }
    func main()  {
        f()
    }
    //注:实参a 的值在defer注册时通过值拷贝传递进去,后续语句a++不会影响defer语句最后输出结果
    3 defer语句必须先注册后才能执行,如果defer位于return之后,则defer因为没有注册,不会执行
    package main
    
    func main(){
    	defer func() {
    		println("first")
    	}()
    	a := 0
    	println(a)  //0
    	return
    
    	defer func() {
    		println("second")   //first	
    	}()
    }
    4 defer的好处是可以在一定程度上避免资源泄漏,特别是在有很多return语句,有多个资源需要关闭的场景,
    很容易漏掉资源关闭操作
    func CopyFile (dst , src string) (w int64 , err error) {
        src , err := os.Open(src) 
        if err != nil {
        return 
        }
        dst, e rr := os . Create(dst) 
        if err != nil { 
        //src 很容易忘记关闭
        src.Close () 
            returη 
        }
        w, err =工 Copy dst src ) 
        dst.Close() 
        src.Close () 
        return 
    }
    

      在打开资源无报错后直接调用 defer 关闭资源

    func CopyFile (dst , src string) (w nt64 err error) { 
        src , err := os.Open (src ) 
        if err != nil { 
        return
        } 
        defer src . Close() 
        dst , err := os . Create(dst) 
        if err != nil { 
            return
        } 
        defer dst. Close () 
        w, err =工 Copy(dst src) 
        return 
    }
    //总结 1defer 语句的位置不当,有可能导致 panic 一般 def1 语句放在错误检查语句之后。
    //2
    defer 也有明显的副作用: defer 会推迟资源的释放, defer 尽量不要放到循环语句里面,将大函数内部的defer语句单独拆分成
    //一个小函数是一种很好的实践方式。另外, defer 相对于普通的函数调用需要间接的数据结构的支持,相对于普通函数调用有一定的性能损耗
    //3 defer r 中最好不要对有名返回值参数进行操作
    
    

    四 :闭包

    1 概述:
    闭包是由函数及其相关引用环境组合而成的实体,一般通过在匿
    名函数中引用外部函数的局部变量或包全局变构成。
    2 引用:闭包=函数+引用环境
    详解:
    闭包对闭包外的环境引入是直接引用,编译器检测到闭包,会将闭包引用的外部变量分配到堆上
    如果函数返回的闭包引用了该函数的局部变量( 参数或函数内部变量〉
    (1)多次调用该函数,返回的多个闭包所引用的外部变量是多个副本,原因是每次调用函
    数都会为局部变量分配内存
    (2)用一个闭包函数多次,如果该闭包修改了其引用的外部变量,则每一次调用该闭包对
    该外部变量都有影响,因为闭包函数共享外部引用。

    示例:

    package main
    
    func fa(a int) func(i int) int{
        return func(i int) int {
            println(&a,a)
            a= a+i
            return a
        }
    }
    func main(){
        f :=fa(1)  //f 引用的外部的闭包环境包括本次函数调用的形参a的值1
        g := fa(1) //g 引用的外部的闭包环境包括本次函数调用的形参a的值1
        //此时f、g引用的闭包环境中的a的值并不是同一个,而是两次函数调用产生的副本
        println(f(1))      //0xc00006c000 1       2 
        //多次调用f引用的同一个副本a
        println(f(1))      //0xc00006c000 2       3
        //g中a的值然仍是1
        println(g(1))      //0xc00006c008 1        2
        println(f(1))      //0xc00006c000 3        4
    }
    //f和g引用的是不同的a
    (3)如果函数返回的闭包引用的是全局变量 ,则多次调用该函数返回的多个闭包引用的都是同一个a。
    同理,调用 个闭包多次引用的也是同一个 。此时如果闭包中修改了a 值的逻辑,
    每次闭包调用都会影响全局变量 的值。

    示例:

    package main
    
    var(
        a=0
    )
    
    func fa() func(i int) int  {
        return func(i int) int {
            println(&a,a)
            a = a +i
            return a
        }
    }
    func main()  {
        f :=fa()  //f 引用的外部的闭包环境包括全局交量a
        g :=fa()  //f 引用的外部的闭包环境包括全局变量a
        ///此时f、g 引用的闭包环境中的a 的值是同一个
        println(f(1))     //0x4d68b8 0      1
        println(g(1))     //0x4d68b8 0      2
        println(g(1))     //0x4d68b8 0      3
        println(g(1))     //0x4d68b8 0      4
    }

      (4)同一个函数返回的多个闭包共享该函数的局部

    package main
    
    func fa(base int) (func(int) int,func(int) int) {
        print(&base,base)    //0xc00006c00000xc00006c00800xc00006c000 1
        add := func(i int)  int {
            base += i
            println(&base,base)
            return base
        }
        sub := func(i int) int{
            base -= i
            println(&base,base)
            return base
        }
        return add,sub
    }
    func main() {
        //f、g 闭包引用的 base 是同一个,是fa函数调用传递过来的实参值
        f,g := fa(0)
        //s、k 包引用的base是同一个是fa函数调用传递过来的实参值
        s,k := fa(0)
        //f、g和s、k 引用不同的闭包交量,这是由于fa每次调用都妥重新分配形参
        println(f(1))
        println(g(2))
        println(s(1))
        println(k(2))
    }
  • 相关阅读:
    洛谷 1341 无序字母对
    POJ 2774 后缀数组 || 二分+哈希
    HDU 1251 统计难题
    【解题报告】AtCoder ABC115 (附英文题目)
    【模板】后缀数组
    洛谷 3567/BZOJ 3524 Couriers
    Beta 冲刺 (1/7)
    团队项目评测
    beta冲刺前准备
    Alpha冲刺——事后诸葛亮
  • 原文地址:https://www.cnblogs.com/liucsxiaoxiaobai/p/10801361.html
Copyright © 2020-2023  润新知