G

[Golang基础语法] 函数

RoLingG Golang 2024-03-15

函数

  1. 在Go语言中,函数是通过func关键字进行定义的。

    func hello(text string) {
        fmt.Println(text)
    }
    
    func hello1(text1, text2 string) {    //如果前后参数类型相同,则可省略前面的类型定义。
        fmt.Println(text1,text2)
    }
    
    func add(numberList ...int) {    //如果要传多个同类型参数,可以这样写。
        var sum int
        for _, i := range numberList {
            sum += i
        }
    }
    
    //没有返回值的函数要用return,或者直接不写。
    func returntest() {
        return
    }
    
    //如果有单个返回值,则要写返回值的类型。
    func returntest1() bool{    //单个也可以用命名式进行返回。
        var ok bool
        return ok    //这样声明一个值作为返回值给return返回也是可以的。
        //return false
    }
    
    //如果有多个返回值,则要用(),在里面写返回值的类型。
    func returntest2()(string, bool){
        return "你好", false
        //这种写法不好的就是如果你多个判断有多个return,那么每次都要重新定义两个值给return回去。
    }
    
    //除了上面这种多值返回,还有命名式函数返回方式
    func returntest3()(val string, ok bool){
        if ... {
            ...
            return val, ok
        }
        //这样命名之后返回就方便很多,精简很多
    }
    
    func showtype(args ...interface{}) {    //使用...可用来解序列,将函数的可变参数一个个的举出来传递给另一个可变参数的函数,不是传递可变参数本身
        for _, arg := range args {
            switch arg.(type) {
            case string:
                fmt.Println(arg, "type is string")
            case int:
                fmt.Println(arg, "type is int")
            }
        }
    }
    
    /*
            //parameter_list(参数列表,是局部变量,非必须)
            //result_list(返回值列表,包含返回值名称、类型,非必须)
    
            func function_name(parameter_list) (result_list){
                    //函数体
            }
    
            函数可见性
            1.函数首字母大写,对于所有包都是public,其他包任意调用
            2.函数首字母小写,对于其他包都不能调用
    */
  2. 在别的语言中,会有在函数中创建函数的写法。但在Go语言中,这种写法被优化成了匿名函数,不支持套娃函数写法。

    func hello(text string) {
        var gethello = func() string {
        return "你好"
    }    
        //这样就是匿名函数,通过声明一个变量等于函数进行值的获取。
        //也就相当于在函数里面调用函数
        fmt.Println(gethello)
    }
    
    func hello1(text string) {
        var gethello = func(hello string) {
            fmt.Println(hello)
            return
    }    
        gethello("你好")
    }

高阶函数:

  1. 高阶函数是把一个函数作为参数传入另一个函数中,或者把一个函数传入另一个数据类型中。(难用,但好用的一个东西)

    例如有一个登录小系统,有login()register()usercenter()三个函数,通过输入1,2,3进入不同的界面,我们可以通过Switch case将这三个数分配给三个函数作为进入条件。

    var index int
       fmt.Scan(&index)
       var funcMap = map[int]func() {
           1: login,
           2: register,
           3: usercenter,
       }
       fun, ok := funcMap[index]
       if ok {
           fun()
       }
       //Switch index {
        //   case 1:
        //   register()
        //   case 2:
        //   login()
        //   case 3:
        //   usercenter()
       //}
       //这里funcMap直接代替了Switch

闭包

例如:设计一个函数,西安传一个参数表示延时,后面再次传参就是将参数求和。

//func(...int) int←还要个int是因为return里还有个return,是这个函数的。
func awaitAdd(awaitSecond int) func(...int) int{
    //time.Sleep(time.Duration(awaitSecond) * time.Second)
    return func(numberList ...int) (sum int) {
        time.Sleep(time.Duration(awaitSecond) * time.Second)
        for _, i2 := range numberList {
            sum += i2
        }
        return sum
    }
}

func main() {
    //方法一:在统计时间里面等待
    t1 :=timeNow()
    sum := awaitAdd(2)(1,2,3)
    
    subTime := time.Since(t1)
       fmt.Println(sum, subTime)
    
    //方法二:在统计时间外面等待
    add := awaitAdd(2)
    t1 :=timeNow()
    sum := add(1,2,3)
    
    subTime := time.Since(t1)
       fmt.Println(sum, subTime)
    
    //结果会发现方法一和方法二有差别,方法二没了延时。因为add := awaitAdd(2)进去后直接执行了等待命令,这样sum := add(1,2,3)在计算总值时就不会算进等待的时间。
    
    //但如果我们将awaitAdd()内的等待放进return内的函数中,这样每次调用add的时候才会延时。因为代码放的位置就会有不同的效果。
    //以此类推Go语言中的函数可以一直这样嵌套,这种也就叫闭包,也可以叫高阶函数。
    //像上述例子这种return里面的函数用到了外层壳awaitAdd()的参数(即awaitSecond)时,就叫闭包、 
}

值传递和引用传递

讲之前先说个知识,我们存放东西在电脑里主要都是存在硬盘里;而我们在使用计算机时所见的所有操作的界面、程序啥的其实都放在内存里。(这里就是操作系统要讲的东西了)

我们打开一个软件,就是硬盘通过cpu将软件从静态的代码块变成动态的程序到内存的一个过程。

这也就意味着我们定义的每一个变量,都是将它暂时保存在了内存中。

正常的情况下来说,函数的参数是将之前块地复制了一份出来。

func change(num int) {
    fmt.Println(&num)    //可以看到这两个&num输出的内存地址是不一样的。
    //这也就说明了函数是拷贝了一份参数的值进行操作。
    num = 2    //尝试在函数中改变num的值。
}

func realchange(num *int) {
    fmt.Println(&num)    //这里就一样了
    //num = 5    这里就不能这样改了,因为现在的num是一个内存地址
    *num = 5    //只能通过内存地址去找值
}

func main() {
    num := 20
    fmt.Println(&num)
    change(num)
    fmt.Println(num)    //20
    realchange(num)
}
//所以如果硬是要影响原值,则就要使用引用传递。
//&是取地址。*是解引用,去这个地址指向的值。

init函数和defer函数

init函数是一个特殊的函数,有着以下特性:

  1. 不能被其他函数调用,而是在main函数执行之前,自动被调用。
  2. init函数不能作为参数传入。
  3. init函数不能有传入参数和返回值。
  4. 一个Go文件可以有多个init函数,谁在前面谁先执行。

所以init函数一般用于初始化。

defer函数又称为延迟函数。

  1. 关键字defer用于注册延迟调用。
  2. 这些调用知道return前才被执行。因此可用于资源清理、异常处理、资源操作、网络编程。
  3. 多个defer语句,按先进后出的方式执行,谁离return近谁先执行。
  4. defer语句中的变量,在defer声明时就决定了。
  5. defer函数只是延迟调用,不代表变量后改变会影响前面调用的还未改变的变量。上面的4.也是为了证明这个。

综上这两个函数在一些实用场景里会经常用到。

PREV
[Golang基础语法] For循环
NEXT
[Golang基础语法] Switch语句

评论(0)

发布评论