10-Go语言函数

极客江南 · · 1400 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

函数

  • Go语言和C语言一样也有函数的概念, Go语言中函数除了定义格式和不用声明以外,其它方面几乎和C语言一模一样
  • 格式:
func 函数名称(形参列表)(返回值列表){
    函数体;
}
  • 无参数无返回值函数
func say()  {
    fmt.Println("Hello World!!!")
}
  • 有参数无返回值函数
func say(name string)  {
    fmt.Println("Hello ", name)
}
  • 无参数有返回值函数
func sum() int { // 只有一个返回值时,返回值列表的()可以省略
    return 1 + 1
}
  • 有参数有返回值函数
func sum(a int, b int) int {
    return a + b
}

和C语言函数差异

  • 和C语言不同的是,Go语言中可以给函数的返回值指定名称
// 给返回值指定了一个名称叫做res, return时会自动将函数体内部res作为返回值
// 其实本质就是提前定义了一个局部变量res, 在函数体中使用的res就是这个局部变量,返回的也是这个局部变量
func sum() (res int) { 
    res = 1 + 1
    return
}
  • 和C语言不同的是,Go语言中的函数允许有多个返回值函数
func calculate(a int, b int) (sum int, sub int) {
    sum = a + b
    sub = a - b
    return
}
  • 相邻同类型形参OR返回值类型可以合并, 可以将数据类型写到最后一个同类型形参OR返回值后面
// a, b都是int类型, 所以只需要在b后面添加int即可
func calculate(a, b int) (sum, sub int) {
    sum = a + b
    sub = a - b
    return
}
  • 和C语言不同的是Go语言中的函数不需要先声明在使用
package main
import "fmt"
func main() {
    say();
}
func say()  { // 在后面定义也可以在前面使用
    fmt.Println("Hello World!!!")
}
  • 和C语言不同的是Go语言中的函数不支持嵌套定义
func test1()  {
    func test2()  {

    }
}

值传递和引用传递

  • Go语言中值类型有: int系列、float系列、bool、string、数组、结构体
    • 值类型通常在栈中分配存储空间
    • 值类型作为函数参数传递, 是拷贝传递
    • 在函数体内修改值类型参数, 不会影响到函数外的值
package main
import "fmt"
func main() {
    num := 10
    change(num)
    fmt.Println(num) // 10
}
func change(num int)  {
    num = 998
}
package main
import "fmt"
func main() {
    arr := [3]int{1, 3, 5}
    change(arr)
    fmt.Println(arr) // 1, 3, 5
}
func change(arr [3]int)  {
    arr[1] = 8
}
package main
import "fmt"
type Person struct {
    name string
    age int
}
func main() {
    p := Person{"lnj", 33}
    change(p)
    fmt.Println(p.name) // lnj
}
func change(p Person)  {
    p.name = "zs"
}
  • Go语言中引用类型有: 指针、slice、map、channel
    • 引用类型通常在堆中分配存储空间
    • 引用类型作为函数参数传递,是引用传递
    • 在函数体内修改引用类型参数,会影响到函数外的值
package main
import "fmt"
func main() {
    num := 10
    change(&num)
    fmt.Println(num) // 998
}
func change(num *int)  {
    *num = 998
}
package main
import "fmt"
func main() {
    arr := []int{1, 3, 5}
    change(arr)
    fmt.Println(arr) // 1, 8, 5
}
func change(arr []int)  {
    arr[1] = 8
}
package main
import "fmt"
func main() {
    mp := map[string]string{"name":"lnj", "age":"33"}
    change(mp)
    fmt.Println(mp["name"]) // zs
}
func change(mp map[string]string)  {
    mp["name"] = "zs"
}

匿名函数

  • 匿名函数也是函数的一种, 它的格式和普通函数一模一样,只不过没有名字而已
    • 普通函数的函数名称是固定的, 匿名函数的函数名称是系统随机的
  • 匿名函数可以定义在函数外(全局匿名函数),也可以定义在函数内(局部匿名函数), Go语言中的普通函数不能嵌套定义, 但是可以通过匿名函数来实现函数的嵌套定义
    • 全局匿名函数
    package main
    import "fmt"
    // 方式一
    var a = func()  {
      fmt.Println("hello world1")
    }
    // 方式二
    var (
        b  = func()  {
          fmt.Println("hello world2")
        }
    )
    func main() {
        a()
        b()
    }
    
  • 一般情况下我们很少使用全局匿名函数, 大多数情况都是使用局部匿名函数, 匿名函数可以直接调用、保存到变量、作为参数或者返回值
  • 直接调用
package main
import "fmt"
func main() {
    func(s string){
        fmt.Println(s)
    }("hello lnj")
}
  • 保存到变量
package main
import "fmt"
func main() {
    a := func(s string) {
        fmt.Println(s)
    }
    a("hello lnj")
}
  • 作为参数
package main
import "fmt"
func main() {
    test(func(s string) {
        fmt.Println(s)
    })
}
func test(f func(s string))  {
    f("hello lnj")
}
  • 作为返回值
package main
import "fmt"
func main() {
    res := test()
    res(10, 20)
}
func test() func(int, int) {
    return func(a int, b int) {
        fmt.Println(a + b)
    }
}
  • 匿名函数应用场景
    • 当某个函数只需要被调用一次时, 可以使用匿名函数
    • 需要执行一些不确定的操作时,可以使用匿名函数
package main
import "fmt"
func main() {
    // 项目经理的一天
    work(func() {
        fmt.Println("组织部门开会")
        fmt.Println("给部门员工分配今天工作任务")
        fmt.Println("检查部门员工昨天提交的代码")
        fmt.Println("... ...")
    })
    // 程序员的一天
    work(func() {
        fmt.Println("参加部门会议")
        fmt.Println("修改测试提交的BUG")
        fmt.Println("完成老大今天安排的任务")
        fmt.Println("... ...")
    })
}
// 假设我们需要编写一个函数,用于描述一个人每天上班都需要干嘛
// 职场中的人每天上班前,上班后要做的事几乎都是相同的, 但是每天上班过程中要做的事确实不确定的
// 所以此时我们可以使用匿名函数来解决, 让上班的人自己觉得自己每天上班需要干什么
func work(custom func())  {
    // 上班前
    fmt.Println("起床")
    fmt.Println("刷牙")
    fmt.Println("洗脸")
    fmt.Println("出门")
    fmt.Println("上班打卡")
    fmt.Println("开电脑")

    // 上班中
    custom()

    // 上班后
    fmt.Println("关电脑")
    fmt.Println("下班打卡")
    fmt.Println("出门")
    fmt.Println("到家")
    fmt.Println("吃饭")
    fmt.Println("睡觉")

}

+为了提升代码的可读性,我们还可以将这个大函数拆解为独立的匿名函数

func work(custom func())  {
    // 这种写法的好处是代码层次清晰,并且如果有一些变量
    // 只需要在上班前或上班后使用,还可以将这些变量隔离,不对外界造成污染
    // 上班前
    func(){
        fmt.Println("起床")
        fmt.Println("刷牙")
        fmt.Println("洗脸")
        fmt.Println("出门")
        fmt.Println("上班打卡")
        fmt.Println("开电脑")
    }()

    // 上班中
    custom()

    // 上班后
    func(){
        fmt.Println("关电脑")
        fmt.Println("下班打卡")
        fmt.Println("出门")
        fmt.Println("到家")
        fmt.Println("吃饭")
        fmt.Println("睡觉")
    }()

}
func work(custom func())  {
    // 前提条件是这个函数只在work函数中使用, 两者有较强的关联性, 否则建议定义为普通函数
    pre := func(){
        fmt.Println("起床")
        fmt.Println("刷牙")
        fmt.Println("洗脸")
        fmt.Println("出门")
        fmt.Println("上班打卡")
        fmt.Println("开电脑")
    }
    latter := func(){
        fmt.Println("关电脑")
        fmt.Println("下班打卡")
        fmt.Println("出门")
        fmt.Println("到家")
        fmt.Println("吃饭")
        fmt.Println("睡觉")
    }
    
    // 上班前
    pre()
    // 上班中
    custom()
    // 上班后
    latter()
}

闭包

  • 闭包是一个特殊的匿名函数, 它是匿名函数和相关引用环境组成的一个整体
    • 也就是说只要匿名函数中用到了外界的变量, 那么这个匿名函数就是一个闭包
    package main
    import "fmt"
    func main() {
        num := 10
        a := func() {
            num++ // 在闭包中用到了main函数中的num, 所以这个匿名函数就是一个闭包
            fmt.Println(num) // 11
        }
        a()
    }
    
    • 闭包中使用的变量和外界的变量是同一个变量, 所以可以闭包中可以修改外界变量
    package main
    import "fmt"
    func main() {
        num := 10
        a := func() {
            num = 6 // 在闭包中用到了main函数中的num, 所以这个匿名函数就是一个闭包
            fmt.Println(num) // 6
        }
        fmt.Println("执行闭包前", num) // 10
        a()
        fmt.Println("执行闭包后", num) // 6
    }
    
    • 只要闭包还在使用外界的变量, 那么外界的变量就会一直存在
    package main
    import "fmt"
    func main() {
          res := addUpper() // 执行addUpper函数,得到一个闭包
      fmt.Println(res()) // 2 
      fmt.Println(res()) // 3
      fmt.Println(res()) // 4
      fmt.Println(res()) // 5
    }
    func addUpper() func() int {
        x := 1
        return func() int {
            x++ // 匿名函数中用到了addUpper中的x,所以这是一个闭包
            return x
        }
    }
    

延迟调用

  • Go语言中没有提供其它面向对象语言的析构函数, 但是Go语言提供了defer语句用于实现其它面向对象语言析构函数的功能
  • defer语句常用于释放资源解除锁定以及错误处理
    • 例如C语言中我们申请了一块内存空间,那么不使用时我们就必须释放这块存储空间
    • 例如C语言中我们打开了一个文件,那么我们不使用时就要关闭这个文件
    • 例如C语言中我们打开了一个数据库, 那么我们不使用时就要关闭这个数据库
    • 这一类的操作在Go语言中都可以通过defer语句来完成
  • 无论你在什么地方注册defer语句,它都会在所属函数执行完毕之后才会执行, 并且如果注册了多个defer语句,那么它们会按照后进先出的原则执行
    • 正是因为defer语句的这种特性, 所以在Go语言中关闭资源不用像C语言那样用完了再关闭, 我们完全可以打开的同时就关闭, 因为无论如何defer语句都会在所属函数执行完毕之后才会执行
  package main
  import "fmt"
  func main() {
    defer fmt.Println("我是第一个被注册的") // 3
    fmt.Println("main函数中调用的Println") // 1
    defer fmt.Println("我是第二个被注册的") // 2
  }

init函数

  • golang里面有两个保留的函数:
    • init函数(能够应用于所有的package)
    • main函数(只能应用于package main)
    • 这两个函数在定义时不能有任何的参数和返回值
  • go程序会自动调用init()和main(),所以你不能在任何地方调用这两个函数
  • package main必须包含一个main函数, 但是每个package中的init函数都是可选的
  • 一个package里面可以写任意多个init函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个package中每个文件只写一个init函数
  • 单个包中代码执行顺序如下
    • main包-->常量-->全局变量-->init函数-->main函数-->Exit
package main
import  "fmt"
const constValue  = 998 // 1
var gloalVarValue int = abc() // 2
func init() { // 3
    fmt.Println("执行main包中main.go中init函数")
}
func main() { // 4
    fmt.Println("执行main包中main.go中main函数")
}
func abc() int {
    fmt.Println("执行main包中全局变量初始化")
    return 998
}
  • 多个包之间代码执行顺序如下


  • init函数的作用

    • init函数用于处理当前文件的初始化操作, 在使用某个文件时的一些准备工作应该放到这里

有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:极客江南

查看原文:10-Go语言函数

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1400 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传