golang 函数

本文视频教程:https://www.bilibili.com/vide...

关注公众号,支付课程材料及源码

golang函数简介

函数的go语言中的一级公民,咱们把所有的性能单元都定义在函数中,能够重复使用。函数蕴含函数的名称、参数列表和返回值类型,这些形成了函数的签名(signature)。

go语言中函数个性

  1. go语言中有3种函数:一般函数、匿名函数(没有名称的函数)、办法(定义在struct上的函数)。receiver
  2. go语言中不容许函数重载(overload),也就是说不容许函数同名。
  3. go语言中的函数不能嵌套函数,但能够嵌套匿名函数。
  4. 函数是一个值,能够将函数赋值给变量,使得这个变量也成为函数。
  5. 函数能够作为参数传递给另一个函数。
  6. 函数的返回值能够是一个函数。
  7. 函数调用的时候,如果有参数传递给函数,则先拷贝参数的正本,再将正本传递给函数。
  8. 函数参数能够没有名称。

go语言中函数的定义和调用

函数在应用之前必须先定义,能够调用函数来实现某个工作。函数能够反复调用,从而达到代码重用。

go语言函数定义语法

func function_name( [parameter list] ) [return_types]{   函数体}

语法解析:

  • func:函数由 func 开始申明
  • function_name:函数名称,函数名和参数列表一起形成了函数签名。
  • [parameter list]:参数列表,参数就像一个占位符,当函数被调用时,你能够将值传递给参数,这个值被称为理论参数。参数列表指定的是参数类型、程序、及参数个数。参数是可选的,也就是说函数也能够不蕴含参数。
  • return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些性能不须要返回值,这种状况下 return_types 不是必须的。
  • 函数体:函数定义的代码汇合。

go语言函数定义实例

定义一个求和函数

func sum(a int, b int) (ret int) {    ret = a + b    return ret}

定义一个比拟两个数大小的函数

func compare(a int, b int) (max int) {    if a > b {        max = a    } else {        max = b    }    return max}

go语言函数调用

当咱们要实现某个工作时,能够调用函数来实现。调用函数要传递参数,如何有返回值能够取得返回值。

func main() {    s := sum(1, 2)    fmt.Printf("s: %v\n", s)    max := compare(1, 2)    fmt.Printf("max: %v\n", max)}

运行后果

s: 3max: 2

golang函数的返回值

函数能够有0或多个返回值,返回值须要指定数据类型,返回值通过return关键字来指定。

return能够有参数,也能够没有参数,这些返回值能够有名称,也能够没有名称。go中的函数能够有多个返回值。

  1. return关键字中指定了参数时,返回值能够不必名称。如果return省略参数,则返回值局部必须带名称
  2. 当返回值有名称时,必须应用括号突围,逗号分隔,即便只有一个返回值
  3. 但即便返回值命名了,return中也能够强制指定其它返回值的名称,也就是说return的优先级更高
  4. 命名的返回值是事后申明好的,在函数外部能够间接应用,无需再次申明。命名返回值的名称不能和函数参数名称雷同,否则报错提醒变量反复定义
  5. return中能够有表达式,但不能呈现赋值表达式,这和其它语言可能有所不同。例如return a+b是正确的,但return c=a+b是谬误的。

go语言函数返回值实例

没有返回值

func f1() {    fmt.Printf("我没有返回值,只是进行一些计算")}

有一个返回值

func sum(a int, b int) (ret int) {    ret = a + b    return ret}

多个返回值,且在return中指定返回的内容

func f2() (name string, age int) {    name = "老郭"    age = 30    return name, age}

多个返回值,返回值名称没有被应用

func f3() (name string, age int) {    name = "老郭"    age = 30    return // 等价于return name, age}

return笼罩命名返回值,返回值名称没有被应用

func f4() (name string, age int) {    n := "老郭"    a := 30    return n, a}

Go中常常会应用其中一个返回值作为函数是否执行胜利、是否有错误信息的判断条件。例如return value,existsreturn value,okreturn value,err等。

当函数的返回值过多时,例如有4个以上的返回值,应该将这些返回值收集到容器中,而后以返回容器的形式去返回。例如,同类型的返回值能够放进slice中,不同类型的返回值能够放进map中。

但函数有多个返回值时,如果其中某个或某几个返回值不想应用,能够通过下划线_来抛弃这些返回值。例如上面的f1函数两个返回值,调用该函数时,抛弃了第二个返回值b,只保留了第一个返回值a赋值给了变量a

package mainimport "fmt"func f1() (int, int) {    return 1, 2}func main() {    _, x := f1()    fmt.Printf("x: %v\n", x)}

运行后果

x: 2

golang函数的参数

go语言函数能够有0或多个参数,参数须要指定数据类型

申明函数时的参数列表叫做形参,调用时传递的参数叫做实参。

go语言是通过传值的形式传参的,意味着传递给函数的是拷贝后的正本,所以函数外部拜访、批改的也是这个正本。

go语言能够应用变长参数,有时候并不能确定参数的个数,能够应用变长参数,能够在函数定义语句的参数局部应用ARGS...TYPE的形式。这时会将...代表的参数全副保留到一个名为ARGS的slice中,留神这些参数的数据类型都是TYPE。

go语言函数的参数实例

go语言传参

// 形参列表func f1(a int, b int) int {    if a > b {        return a    } else {        return b    }}func main() {    // 实参列表    r := f1(1, 2)    fmt.Printf("r: %v\n", r)}

演示参数传递,按值传递

func f1(a int) {    a = 200    fmt.Printf("a1: %v\n", a)}func main() {    a := 100    f1(a)    fmt.Printf("a: %v\n", a)}

运行后果

a1: 200a: 100

从运行后果能够看到,调用函数f1后,a的值并没有被扭转,阐明参数传递是拷贝了一个正本,也就是拷贝了一份新的内容进行运算。

mapsliceinterfacechannel这些数据类型自身就是指针类型的,所以就算是拷贝传值也是拷贝的指针,拷贝后的参数依然指向底层数据结构,所以批改它们可能会影响内部数据结构的值。
package mainimport "fmt"func f1(a []int) {    a[0] = 100}func main() {    a := []int{1, 2}    f1(a)    fmt.Printf("a: %v\n", a)}

运行后果

a: [1 2]a: [100 2]
从运行后果发现,调用函数后,slice内容被扭转了。

变长参数

package mainimport "fmt"func f1(args ...int) {    for _, v := range args {        fmt.Printf("v: %v\n", v)    }}func f2(name string, age int, args ...int) {    fmt.Printf("name: %v\n", name)    fmt.Printf("age: %v\n", age)    for _, v := range args {        fmt.Printf("v: %v\n", v)    }}func main() {    f1(1, 2, 3)    fmt.Println("------------")    f1(1, 2, 3, 4, 5, 6)    fmt.Println("------------")    f2("tom", 20, 1, 2, 3)}

运行后果

v: 1v: 2v: 3------------v: 1v: 2v: 3v: 4v: 5v: 6------------name: tomage: 20v: 1v: 2v: 3

golang函数类型与函数变量

能够应用type关键字来定义一个函数类型,语法格局如下:

type fun func(int, int) int

下面语句定义了一个fun函数类型,它是一种函数类型,这种函数接管两个int类型的参数,并且返回一个int类型的返回值。

上面咱们定义两个这样构造的两个函数,一个求和,一个比拟大小:

func sum(a int, b int) int {    return a + b}func max(a int, b int) int {    if a > b {        return a    } else {        return b    }}

上面定义一个fun函数类型,把summax赋值给它

package mainimport "fmt"type fun func(int, int) intfunc sum(a int, b int) int {    return a + b}func max(a int, b int) int {    if a > b {        return a    } else {        return b    }}func main() {    var f fun    f = sum    s := f(1, 2)    fmt.Printf("s: %v\n", s)    f = max    m := f(3, 4)    fmt.Printf("m: %v\n", m)}

运行后果

s: 3m: 4

golang高阶函数

go语言的函数,能够作为函数的参数,传递给另外一个函数,能够能够作为,另外一个函数的返回值返回。

go语言函数作为参数

package mainimport "fmt"func sayHello(name string) {    fmt.Printf("Hello,%s", name)}func f1(name string, f func(string)) {    f(name)}func main() {    f1("tom", sayHello)}

运行后果

Hello,tom

go语言函数作为返回值

package mainimport "fmt"func add(x, y int) int {    return x + y}func sub(x, y int) int {    return x - y}func cal(s string) func(int, int) int {    switch s {    case "+":        return add    case "-":        return sub    default:        return nil    }}func main() {    add := cal("+")    r := add(1, 2)    fmt.Printf("r: %v\n", r)    fmt.Println("-----------")    sub := cal("-")    r = sub(100, 50)    fmt.Printf("r: %v\n", r)}

运行后果

r: 3-----------r: 50

golang匿名函数

go语言函数不能嵌套,然而在函数外部能够定义匿名函数,实现一下简略性能调用。

所谓匿名函数就是,没有名称的函数。

语法格局如下:

func (参数列表)(返回值)
当然能够既没有参数,能够没有返回值

匿名函数实例

func main() {    max := func (a int, b int) int {        if a > b {            return a        } else {            return b        }    }    i := max(1, 2)    fmt.Printf("i: %v\n", i)}

运行后果

i: 2

本人执行

func main() {    // 本人执行    func(a int, b int) {        max := 0        if a > b {            max = a        } else {            max = b        }        fmt.Printf("max: %v\n", max)    }(1, 2)}

运行后果

max: 2

golang闭包

闭包能够了解成定义在一个函数外部的函数。在实质上,闭包是将函数外部和函数内部连接起来的桥梁。或者说是函数和其援用环境的组合体。

闭包指的是一个函数和与其相干的援用环境组合而成的实体。简略来说,闭包=函数+援用环境。 首先咱们来看一个例子:

package mainimport "fmt"// 返回一个函数func add() func(int) int {    var x int    return func(y int) int {        x += y        return x    }}func main() {    var f = add()    fmt.Println(f(10))    fmt.Println(f(20))    fmt.Println(f(30))    fmt.Println("-----------")    f1 := add()    fmt.Println(f1(40))    fmt.Println(f1(50))}

运行后果

103060-----------4090

变量f是一个函数并且它援用了其内部作用域中的x变量,此时f就是一个闭包。 在f的生命周期内,变量x也始终无效。 闭包进阶示例1:

package mainimport "fmt"func add(x int) func(int) int {    return func(y int) int {        x += y        return x    }}func main() {    var f = add(10)    fmt.Println(f(10))    fmt.Println(f(20))    fmt.Println(f(30))    fmt.Println("----------")    f1 := add(20)    fmt.Println(f1(40))    fmt.Println(f1(50))}

运行后果

204070----------60110

闭包进阶示例2:

func makeSuffixFunc(suffix string) func(string) string {    return func(name string) string {        if !strings.HasSuffix(name, suffix) {            return name + suffix        }        return name    }}func main() {    jpgFunc := makeSuffixFunc(".jpg")    txtFunc := makeSuffixFunc(".txt")    fmt.Println(jpgFunc("test"))     fmt.Println(txtFunc("test")) }

运行后果

test.jpgtest.txt

闭包进阶示例3:

func calc(base int) (func(int) int, func(int) int) {    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)    fmt.Println(f1(1), f2(2))     fmt.Println(f1(3), f2(4))     fmt.Println(f1(5), f2(6)) }

运行后果

11 912 813 7

闭包其实并不简单,只有牢记闭包=函数+援用环境

golang递归

函数外部调用函数本身的函数称为递归函数。

应用递归函数最重要的三点:

  1. 递归就是本人调用本人。
  2. 必须先定义函数的退出条件,没有退出条件,递归将成为死循环。
  3. go语言递归函数很可能会产生一大堆的goroutine,也很可能会呈现栈空间内存溢出问题。

go语言递归实例

阶乘

package mainimport "fmt"func a(n int) int {    // 返回条件    if n == 1 {        return 1    } else {        // 本人调用本人        return n * a(n-1)    }}func main() {    n := 5    r := a(n)    fmt.Printf("r: %v\n", r)}

运行后果

r: 120

斐波那契数列

它的计算公式为f(n)=f(n-1)+f(n-2)f(2)=f(1)=1

package mainfunc f(n int) int {    // 退出点判断    if n == 1 || n == 2 {        return 1    }    // 递归表达式    return f(n-1) + f(n-2)}func main() {    r := f(5)    fmt.Printf("r: %v\n", r)}

运行后果

r: 5

golang defer语句

go语言中的defer语句会将其前面追随的语句进行提早解决。在defer归属的函数行将返回时,将提早解决的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最初被执行,最初被defer的语句,最先被执行。stack

defer个性

  1. 关键字 defer 用于注册提早调用。
  2. 这些调用直到 return 前才被执。因而,能够用来做资源清理。
  3. 多个defer语句,按先进后出的形式执行。
  4. defer语句中的变量,在defer申明时就决定了。

defer用处

  1. 敞开文件句柄
  2. 锁资源开释
  3. 数据库连贯开释

go语言defer语句实例

查看执行程序

func main() {    fmt.Println("start")    defer fmt.Println("step1")    defer fmt.Println("step2")    defer fmt.Println("step3")    fmt.Println("end")}

运行后果

startendstep3step2step1

golang init函数

golang有一个非凡的函数init函数,先于main函数执行,实现包级别的一些初始化操作。

init函数的次要特点

  • init函数先于main函数主动执行,不能被其余函数调用;
  • init函数没有输出参数、返回值;
  • 每个包能够有多个init函数;
  • 包的每个源文件也能够有多个init函数,这点比拟非凡;
  • 同一个包的init执行程序,golang没有明确定义,编程时要留神程序不要依赖这个执行程序。
  • 不同包的init函数依照包导入的依赖关系决定执行程序。

golang 初始化程序

初始化程序:变量初始化->init()->main()

实例

package mainimport "fmt"var a int = initVar()func init() {    fmt.Println("init2")}func init() {    fmt.Println("init")}func initVar() int {    fmt.Println("init var...")    return 100}func main() {    fmt.Println("main...")}

运行后果

init var...init2initmain...