zoukankan      html  css  js  c++  java
  • Go语言基础之7--函数详解

    一、 函数介绍

    1.1 定义

    函数:有输入、有输出,用来执行一个指定任务的代码块。

    func functionname([parametername type]) [return type] {
    //function body
    }             //其中参数列表和返回值列表都是可选的

    解释:

    func 函数名([参数名 类型])[返回值 类型] {
    函数体
    }   

    1.2 特点

    golang函数的特点:

    1)不支持重载,即一个包不能有两个名字一样的函数

    2)函数也是一种类型,一个函数可以赋值给变量

    3)匿名函数

    4)多返回值

    1.3无参数和无返回值的函数

    func functionname() {
    //function body
    }

    1.4 小练习 实现两个数相加

    func add(a int, b int) int {
       Return a + b
    }

    1.5 如果连续的一系列参数的类型是一样,前面的类型可以不写

    func add(a, b int) int {
      Return a + b
    }

    1.6 函数调用

    函数隶属于同一个包:

    func add(a, b int) int {
      Return a + b
    }
    func main() {
      sum := add(2, 3)
    }

    函数隶属于不同的包:

    func add(a, b int) int {
      Return a + b
    }
    func main() {
      sum := 包名.add(2, 3)
    }

    1.7 注意

    函数传参,传递的是值的副本。

    只不过值类型传递的是值,指针类型(引用类型)传递的是内存地址

    二、 多返回值和可变参数

    2.1 多返回值

    package main
    
    import "fmt"
    
    func calc(a, b int) (int, int) {
        sum := a + b
        sub := a - b
        return sum, sub
    }
    func main() {
    sum, sub := calc(2, 3) fmt.Println(sum, sub) }

    2.2 对返回值进行命名

    package main
    
    import "fmt"
    
    func calc(a, b int) (sum, sub int) {
        sum = a + b
        sub = a - b
        return
    }
    func main() {
        sum, sub := calc(2, 3)
        fmt.Println(sum, sub)
    }

     实例2-1 活学活用

    package main
    
    import (
        "fmt"
    )
    
    // Calc 函数
    func Calc(a, b int) (int, int) {
        return a + b, a - b
    }
    
    // Calc2 函数
    func Calc2(a, b int) (s1, s2 int) {
        s1 = a + b //这里直接赋值,不在定义s1变量是因为上面函数返回值那里已经定义了s1变量并且为int类型
        s2 = a - b
        return
    }
    
    // Calc3 函数
    func Calc3(a, b int) (int, int) {
        s1 := a + b
        s2 := a - b
        return s1, s2
    }
    
    func main() {
        var sum int
        var sub int
        sum, sub = Calc(2, 5)
        sum1, sub1 := Calc2(3, 6)
        sum2, sub2 := Calc3(1, 9)
        fmt.Printf("sum:%d sub:%d sum1:%d sub1:%d sum2:%d sub2:%d", sum, sub, sum1, sub1, sum2, sub2)
    }

     执行结果如下:

    2.3 _标识符

    当我们函数有2个返回值,但是我们只想输出其中一个,这时候就要用到_占位符拉进行忽略掉

    实例2-2

    package main
    
    import (
        "fmt"
    )
    
    func calc(a, b int) (sum int, sub int) {
        sum = a + b
        sub = a - b
        return
    }
    func main() {
        sum, _ := calc(2, 3)
        fmt.Println(sum)
    }

     执行结果如下:

    2.4 可变参数 

    变长函数被调用的时候可以有可变的参数个数;

    在参数列表最后的类型名称前使用省略号...可以声明一个变长的函数;

    可变参数可传递0个或多个参数,其在底层其实就是一个切片

    例如:

    0个或多个参数
    func add(arg ...int) int{
    
    }
    1个或多个参数
    func add(a int,arg ...int) int{
    
    }
    2个或多个参数
    func add(a int,b int,arg ...int)int{
    
    }

    或者

    func calc_v1(b …int) (sum int, sub int) {  //0个或多个参数
        return
    }
    func calc_v2(a int, b …int) (sum int, sub int) { //1个或多个参数
        return
    }
    func calc_v3(a int, b int, c …int) (sum int, sub int) {  //2个或多个参数
        return
    }

    实例2-3

    package main
    
    import (
        "fmt"
    )
    
    func Add(a ...int) int { //边长参数a,需要传入0个或多个参数
        fmt.Printf("func args count:%d
    ", len(a)) //变长参数在底层存储就是一个切片,计算切片的长度,其实就是计算传入参数的个数
        var sum int
        for index, args := range a { //遍历切片,打印出传入参数值
            fmt.Printf("args[%d]=%d
    ", index, args)
            sum = sum + args //求和
        }
        return sum
    }
    
    func testAdd() {
        sum := Add() //传入0个参数
        fmt.Printf("sum=%d
    ", sum)
    
        sum = Add(2) //传入1个参数
        fmt.Printf("sum=%d
    ", sum)
    
        sum = Add(5, 98) //传入2个参数
        fmt.Printf("sum=%d
    ", sum)
    }
    func main() {
        testAdd()
    }

    执行结果如下:

    三、 defer语句

    3.1 defer

    特性:

    1)在函数退出时去执行

    2)defer语句经常使用成对的操作,比如打开和关闭,连接和断开,加锁和解锁

    defer用途

    1)当函数返回时,执行defer语句,因此可以用来做资源清理

    2)多个defer语句,按先进后出的方式执行

    3)defer语句中的变量,在defer声明时就决定了

    实例3-1          

    下面拿关闭一个打开文件操作为例子,当我们通过os.Open()打开一个文件的时候可以在后面添加defer f.Close() 这样在函数结束时就可以帮我们自动关闭一个打开的文件

    package main
    
    import (
        "fmt"
        "os"
    )
    
    func testDefer() {
        file, err := os.Open("C:/tmp.txt") //os也是go语言标准包(用于操作系统),打开一个文件会返回2个值,第一个是文件打开成功,会返回一个文件的对象,通过这个文件对象,就可以去操纵这个文件了(打开、修改等),第二个返回的是错误信息
        if err != nil {
            fmt.Printf("open file failed,err:%v
    ", err)
            return
        }
    
        defer file.Close()  //这里我们使用defer语句,因为defer语句是在函数退出时最后执行,所以当我们文件对象成功生成后,在这里加上该条语句,那么不论下面哪一条语句成功执行要退出函数时,都会在退出函数时执行该语句,这样就不需要我们去在每一条语句写file.close了。
    
        var buf [4096]byte
        n, err := file.Read(buf[:]) //要传的是切片,如果是数组,值类型改不了外面真正的值,改的是副本。
        if err != nil {
            fmt.Printf("read file failed,err:%v
    ", err)
            //file.Close()  //文件成功打开后需要关闭
            return
        }
        fmt.Printf("read %d byte succ, content:%v
    ", n, string(buf[:]))
        //file.Close()    //文件成功打开后需要关闭
        return
    }
    func main() {
        testDefer()
    }

     执行结果如下:

    3.2 多个defer语句

    多个defer语句,遵循栈的特性:先进后出。

    见如下实例:

    实例3-2

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        defer fmt.Println("hello world")
        defer fmt.Println("hello world2")
        defer fmt.Println("hello world3")
    }

     执行结果如下:

    四、 内置函数

    1. close:主要用来关闭channel

    2. len:用来求长度,比如string、 array、 slice、 map、 channel

    3. new:用来分配内存,主要用来分配值类型,比如int、 struct。返回的是指针

    4. make:用来分配内存,主要用来分配引用类型,比如chan、 map、 slice

    5. append:用来追加元素到数组、 slice中

    6. panic和recover:用来做错误处理

    五、 变量作用域

    5.1 全局变量

    也就是在函数外面的变量,被称为全局变量,该变量对整个程序都生效。

    例如:下面例子a变量在全局都是生效的

    var a int = 100
    
    func test() {
    
    }
    
    func main() {
    
    }

    5.2 局部变量 

    局部变量,分为两种:

    1)函数内定义

    仅仅在变量所定义的函数内生效

    2)语句块内定义。

    仅仅在变量所定义的语句块内生效

    理解见如下例子:

    func add(a int, b int) int {
        var sum int = 0
        //sum是局部变量
        if a > 0 {
        var c int = 100
        //c是布局变量,尽在if语句块有效
        }
    }

    5.3 可见性

    之前我们已经学过了针对函数的首字母大小写,函数名首字母大写,意味着其是可以导出的,能够被其他包访问或调用。函数名首字母小写表示是私有的,不能被外部的包访问调用。

    这里,变量也是和函数一样,遵循一样的规则

    可见性,包内任何变量或函数都是能访问的。包外的话,变量首字母大写是可导出的

    能够被其他包访问或调用。变量小写表示是私有的,不能被外部的包访问。

    实例5-1         测试变量在包内

    package main
    
    import (
        "fmt"
    )
    
    var (
        a = 100
        A = 200
    )
    
    func main() {
        fmt.Println(a, A)
    }

    执行结果如下:

    实例5-2         测试变量在包外

    test包代码

    package test
    
    var (
        b = 90
        B = 8
    )

    主程序main包代码

    包外变量小写b:

    package main
    
    import (
        "fmt"
        "three/test"
    )
    
    func main() {
        fmt.Println(test.b)
    }

    执行结果:直接报b变量未定义

    包外变量大写B:

    package main
    
    import (
        "fmt"
        "three/test"
    )
    
    func main() {
        fmt.Println(test.B)
    }

    执行结果:

    六、 匿名函数

    6.1 声明

    func(参数列表) 返回值列表 {
    
          函数体...
    
    }

    6.2 应用场景

    1)函数也是一种类型,因此可以定义一个函数类型的变量,也就是匿名函数,通过将匿名函数赋值给变量,这样我们就可以一直使用了(匿名函数可以直接赋值给一个变量或直接执行)。

    2)匿名函数,也就是没有名字的函数。

    针对上述2点通过如下实例进行解释:

    实例6-1          

    package main
    
    import (
        "fmt"
    )
    
    func testA1() {
        sum := func(a, b int) int { //将匿名函数赋值给变量
            sum := a + b
            return sum
        }(2, 3)
        fmt.Printf("sum = %d
    ", sum)
    }
    
    func testA2() {
        f1 := func(a, b int) int {
            sum := a + b
            return sum
        }
    
        s1 := f1(2, 5)
        s2 := f1(8, 10) //因为是变量,所以多次调用
        fmt.Printf("s1=%d,s2=%d
    ", s1, s2)
    }
    
    func main() {
        testA1()
        testA2()
    }

     执行结果如下:

     

    3)defer中使用匿名函数

    实例6-2

    package main
    
    import (
        "fmt"
        "os"
    )
    
    func testDefer() {
        file, err := os.Open("C:/Go/robots.txt")
        /* 写法1:
        defer func(f *os.File) {   //函数声明参数名和类型
                if f != nil {
                    f.Close()
                }
            }(file)
        */
        //写法2
        defer func() { //匿名函数配合闭包使用匿名函数外的变量file
            if file != nil {   //file其实就是指针类型,如果不判断是否为空,会出问题(如果file是空,flie.close()调用就会报错)
                file.Close()
            }
        }()
    
        if err != nil {
            fmt.Printf("open file failed, err:%v
    ", err)
            return
        }
    
        //defer file.Close()
    
        var buf [4096]byte
        n, err := file.Read(buf[:])
        if err != nil {
            fmt.Printf("read file failed, err:%v
    ", err)
            //file.Close()
            return
        }
    
        fmt.Printf("read %d byte succ, content:%s
    ", n, string(buf[:]))
        //file.Close()
        return
    }
    
    func main() {
        testDefer()
    
    }

    因为之前已经用过这个例子,所以此处就不在贴出执行结果了。重点是体会defer中使用匿名函数。

    4)函数作为一个参数

    实例6-3

    package main
    
    import (
        "fmt"
    )
    
    func calc(op func(args ...int) int, op_args ...int) int { //第一个参数op是一个函数类型的参数,函数是一个接收可变类型参数,有一个返回值;第二个参数直接是一个可变参数
        result := op(op_args...) //将第2个参数op_args传给op函数,并且是切片,要传递需要将切片展开,也就是切片名...
        fmt.Printf("result = %d
    ", result)
        return result
    }
    
    func add(args ...int) int { 
        var sum int
        for i := 0; i < len(args); i++ {
            sum = sum - args[i]
            fmt.Println(i)
        }
        return sum
    }
    
    func main() {
        calc(func(args ...int) int { //因为参数类型是匿名函数,所以我们此处调用传参,也需要传入匿名函数
            //return 0 //1就是op的返回值
            var sum int
            for i := 0; i < len(args); i++ { //这里args的参数其实就是calc函数中第二个可变参数的值(因为上面op函数已经将可变(切片)参数值展开了)
                sum = sum + args[i]
                fmt.Println(i)
            }
            return sum
        }, 1, 2, 3, 4, 5)
    
        calc(func(args ...int) int { //因为参数类型是匿名函数,所以我们此处调用传参,也需要传入匿名函数
            //return 0 //1就是op的返回值
            var sum int
            for i := 0; i < len(args); i++ {
                sum = sum - args[i]
            }
            return sum
        }, 1, 2, 3, 4, 5)
    
        calc(add, 1, 3) //不单单是匿名函数可以传参,有名函数传的值、类型和给的返回值是同一类型,其就是同一类型函数
    }

    执行结果如下:

    七、 闭包

    7.1 定义

    闭包是由函数及其相关引用环境组合而成的实体(即:闭包=函数+引用环境)。

    下面来看一个demo来理解一下闭包:

    func f(i int) func() int {
        return func() int {
            i++
            return i
        }
    }

    函数f返回了一个函数,返回的这个函数就是一个闭包。这个函数中本身是没有定义变量i的,而是引用了它所在的环境(函数f)中的变量i,这个变量i就和匿名函数打包为一个整体,在闭包的这个有效区间内一直是生效存在的。

    7.2 实例1

    package main
    
    import "fmt"
    
    func main() {
        var f = Adder()     //f就是一个闭包实例
        fmt.Print(f(1), "-")  // x=0 d=1 x=0+1=1
        fmt.Print(f(20), "-") //x=1 d=20 x=1+20=21
        fmt.Print(f(300))     //x=21 d=300 x=21+300=321
    }
    func Adder() func(int) int {
        var x int //x未赋值,默认为0
        return func(d int) int {
            x += d //匿名函数引用外部函数的x变量,x就相当于匿名函数中的全局变量了,不在是Adder函数中的局部变量了。
            return x
        }
    }

     执行结果如下:

    注意:f(1),这个传值1是传给返回值匿名函数的参数d的。

    7.3 实例2

    package main
    
    import (
        "fmt"
    )
    
    func Adder() func(int) int {
        var x int
        return func(d int) int {
            x = x + d
            return x
        }
    }
    
    func main() {
        f := Adder()
        fmt.Println(f(100))
        fmt.Println(f(200))
    
        f1 := Adder() //Adder函数重新做了实例化
        fmt.Println(f1(1))
        fmt.Println(f1(2))
    }

     执行结果如下:

    7.4 实例3

    package main
    
    import "fmt"
    
    func add(base int) func(int) int { //base在闭包中是全局变量
        return func(i int) int {
            base += i
            return base
        }
    }
    func main() {
        tmp1 := add(10)               //base=10
        fmt.Println(tmp1(1), tmp1(2)) //i=1 base=10+1=11 i=2 base=11+2=13
        tmp2 := add(100)              //base=100
        fmt.Println(tmp2(1), tmp2(2)) //i=1 base=100+1=101 i=2 base=101+2=103
    }

     执行结果如下:

    7.5 实例4

    package main
    
    import (
        "fmt"
        "strings"
    )
    
    func makeSuffixFunc(suffix string) func(string) string {  //suffix是全局变量
        return func(name string) string {
            if !strings.HasSuffix(name, suffix) { //string.HasSuffix(name,suffix) 表达的是:name以suffix为结尾返回true
                return name + suffix
            }
            return name
        }
    }
    func main() {
        func1 := makeSuffixFunc(".bmp")  //suffix=.bmp
        func2 := makeSuffixFunc(".jpg")  //suffx=.jpg
        fmt.Println(func1("test")) //name=test
        fmt.Println(func2("test")) //name=test
    }

     执行结果如下:

    7.6 实例5

    package main
    
    import "fmt"
    
    func calc(base int) (func(int) int, func(int) int) { //base是全局变量 ,返回值是2个匿名函数
        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 := calc(10)        //f1和f2引用的是同一个base变量(base=10),相当于base变量在add和sub两个匿名函数内都是生效的,是同一个。
        fmt.Println(f1(1), f2(2)) //f1:base=10 i=1 base=10+1=11 f2:base=11 i=2 base=11-2=9
        fmt.Println(f1(3), f2(4)) //f1:base=9 i=3 base=9+3=12 f2:base=12 i=4 base=12-4=8
        fmt.Println(f1(5), f2(6))
        fmt.Println(f1(7), f2(8))
    }

     执行结果如下:

  • 相关阅读:
    Sharepoint 2007 Forms认证与File Not Found错误
    完全控制SharePoint站点菜单(Get full control of SharePoint ActionMenus) Part 1
    从WSS 3.0到MOSS 2007
    如何备份sharepoint中的文档库?
    图片与文本的对齐方式
    backgroundimage 背景图片的设置
    css中三种隐藏方式
    font(字体)所使用的属性
    display属性
    margin中的bug解决方法
  • 原文地址:https://www.cnblogs.com/forever521Lee/p/9322946.html
Copyright © 2011-2022 走看看