函数是组织好的、可重复使用的、用于执行指定任务的代码块。
Go语言中支持函数、匿名函数和闭包,而且函数在Go语言中属于“一等公民”。数据库
Go语言中定义函数使用func关键字,具体格式以下:编程
func funcName(parametername type) (output type) { //这里是处理逻辑代码 //返回多个值 return valu }
()
,多个返回值时必须加()
{}
里面的内容。能够经过funcName(parameter)
的方式调用函数。调用有返回值的函数时,能够不接收其返回值。数组
package main import "fmt" func main() { a := 10 b := 20 res := sum(a, b) fmt.Printf("%v + %v = %v", a, b, res) } func sum(a, b int) int { return a + b }
运行结果闭包
10 + 20 = 30
形式参数:定义函数时,用于接收外部传入的数据,叫作形式参数,简称形参。
实际参数:调用函数时,传给形参的实际的数据,叫作实际参数,简称实参。
函数调用:app
package main import "fmt" func main() { a := 10 b := 20 res := add(a, b) fmt.Printf("%v + %v = %v", a, b, res) } func add(a, b int) sum int { sum = a + b return }
可变参数是指函数的参数数量不固定。Go语言中的可变参数经过在参数名后加...来标识。socket
注意:可变参数一般要做为函数的最后一个参数。函数式编程
func funcName(arg ...int) {}
arg ...int
告诉Go这个函数接受不定数量的参数。注意,这些参数的类型所有是int。在函数体中,变量arg是一个int类型的slice函数
go语言函数的参数也是存在值传递和引用传递指针
数据类型:code
按照数据类型来分:
按照数据的存储特色来分:
值传递:值类型的数据传递为值传递,传递的是数据的副本。修改数据,对原始数据没有影响。
package main import "fmt" func main() { arr1 := [3]int{1, 2, 3} fmt.Println("函数调用前,数组的数据:", arr1) fun(arr1) fmt.Println("函数调用后,数组的数据:", arr1) } func fun(arr2 [3]int) { fmt.Println("函数中,数组的数据:", arr2) arr2[0] = 100 fmt.Println("函数中,修改后数据的数据:", arr2) }
运行结果
函数调用前,数组的数据: [1 2 3] 函数中,数组的数据: [1 2 3] 函数中,修改后数据的数据: [100 2 3] 函数调用后,数组的数据: [1 2 3]
package main import "fmt" func main() { slice1 := []int{1, 2, 3} fmt.Println("函数调用前,切片的数据:", slice1) fun(slice1) fmt.Println("函数调用后,切片的数据:", slice1) } func fun(slice2 []int) { fmt.Println("函数中,切片的数据:", slice2) slice2[0] = 100 fmt.Println("函数中,修改后切片的数据:", slice2) }
运行结果
函数调用前,切片的数据: [1 2 3] 函数中,切片的数据: [1 2 3] 函数中,修改后切片的数据: [100 2 3] 函数调用后,切片的数据: [100 2 3]
函数的返回值:
一个函数的执行结果,返回给函数调用处,执行结果就叫函数的返回值。
return语句:
一个函数的定义上有返回值,那么函数中必须有return语句,将执行结果返回给函数的调用处。
函数的返回结果必须和函数定义的一致,类型、数量、顺序。
func add(x, y int) (sum int) { sum = x + y return }
func calc(x, y int) (sum, sub int) { sum = x + y sub = x - y return }
-
可用来舍弃某些返回值。func calc(x, y int) (sum, sub int) { sum = x + y sub = x - y return } _, sub := calc(10, 20) //舍弃sum
做用域:变量可使用的范围。
全局变量是定义在函数外部的变量,它在程序整个运行周期内都有效。 全部的函数均可以使用,并且共享这一份数据。
package main import "fmt" var a = 10 func main() { fmt.Println("test调用前,main中访问a:", a) test() fmt.Println("test调用后,main中访问a:", a) } func test() { fmt.Println("操做前,test中访问a: ", a) a = 20 fmt.Println("操做后,test中访问a: ", a) }
运行结果
test调用前,main中访问a: 10 操做前,test中访问a: 10 操做后,test中访问a: 20 test调用后,main中访问a: 20
一个函数内部定义的变量,就叫作局部变量
局部变量只能在定义的范围内访问操做
package main import "fmt" func main() { test() fmt.Println("main中访问a:", a) //undefined: a } func test() { a := 20 fmt.Println("test中访问a: ", a) }
运行结果
# command-line-arguments .\main.go:7:35: undefined: a
局部变量和全局变量重名,优先访问局部变量。
package main import "fmt" var a = 100 func main() { test() } func test() { a := 20 fmt.Println("test中访问a: ", a) }
运行结果
test中访问a: 20
另外,if
,switch
,for
语句中声明的变量也属于局部变量,在代码块外没法访问。
函数也是Go语言中的一种数据类型,能够做为另外一个函数的参数,也能够做为另外一个函数的返回值。
package main import "fmt" func main() { fmt.Printf("%T\n", fun1) //fun1的类型是func(int, int) fmt.Printf("%T\n", fun2) //fun2的类型是func(int, int) int } func fun1(a, b int) { fmt.Println(a, b) } func fun2(c, d int) int { fmt.Println(c, d) return 0 }
运行结果
func(int, int) func(int, int) int
var f fun(int, int) int
上面语句定义了一个变量f,它是一个函数类型,这种函数接收两个int类型的参数而且返回一个int类型的返回值。
全部参数和返回值符合条件的函数能够赋值给f变量
package main import "fmt" func main() { var f func(int, int) int f = sum res := f(20, 10) fmt.Println("20 + 10 = ", res) f = sub res = f(20, 10) fmt.Println("20 - 10 = ", res) } func sum(a, b int) int { return a + b } func sub(a, b int) int { return a - b }
运行结果
20 + 10 = 30 20 - 10 = 10
匿名函数就是没有函数名的函数
func (参数) (返回值) { 函数体 }
匿名函数由于没有函数名,因此没办法像普通函数那样调用,因此匿名函数须要保存到某个变量或者做为当即执行函数。
package main import "fmt" func main() { // 将匿名函数保存到变量中 sum := func(a, b int) int { return a + b } // 经过变量调用匿名函数 res := sum(10, 20) fmt.Println("10 + 20 =", res) // 自执行函数,匿名函数定义完直接加()执行 func(c, d int) { fmt.Printf("%v + %v = %v\n", c, d, c+d) }(10, 20) }
运行结果
10 + 20 = 30 10 + 20 = 30
go语言支持函数式编程:
一个函数被做为参数传递给另外一个函数,那么这个函数就叫作回调函数。
回调函数并不会立刻被调用执行,它会在包含它的函数内的某个特定的时间点被“回调”(就像它的名字同样)。
package main import "fmt" func main() { res := calc(10, 20, add) fmt.Println(res) } // add是一个func(int, int)int类型的函数,能够做为参数传递给calc函数 func add(a, b int) int { return a + b } // calc 高阶函数,它有两个int类型的参数和一个func(int, int)int函数类型的参数 // oper 回调函数,它被做为参数传递给calc函数 func calc(a, b int, oper func(int, int) int) int { res := oper(a, b) return res }
运行结果
30
package main import "fmt" func main() { fun := calc("+") res := fun(10, 20) fmt.Println("10 + 20 =", res) } func sum(a, b int) int { return a + b } func sub(a, b int) int { return a - b } func calc(s string) func(int, int) int { switch s { case "+": return sum case "-": return sub default: fmt.Println("你传的是个啥玩意!") return nil } }
运行结果
10 + 20 = 30
一个外层函数,有内层函数,该内层函数会操做外层函数的局部变量(外层函数的参数,或外层函数定义的变量),而且该内层函数做为外层函数的返回值。
这个内层函数和外层函数的局部变量,统称为闭包结构。
局部变量的生命周期会发生改变。正常的局部变量随着函数的调用而建立,随着函数的结束而销毁。
可是闭包结构的外层函数的局部变量并不会随着外层函数的结束而销毁,由于内层函数还要继续使用。
package main import "fmt" func main() { fun := add() res := fun() fmt.Println("第一次调用,res=", res) res = fun() fmt.Println("第二次调用,res=", res) res = fun() fmt.Println("第二次调用,res=", res) } func add() func() int { i := 0 return func() int { i++ return i } }
运行结果
第一次调用,res= 1 第二次调用,res= 2 第二次调用,res= 3
defer是Go语言中的延迟执行语句,用来添加函数结束时执行的代码,经常使用于释放某些已分配的资源、关闭数据库链接、断开socket链接、解锁一个加锁的资源。
Go语言机制担保必定会执行defer语句中的代码。
package main import "fmt" func main() { a := 1 b := 2 c := 3 d := 4 //defer a++ //a++ 是一个语句,并不是函数或方法,程序报错 defer fmt.Println("defer", a) defer fmt.Println("defer", b) defer fmt.Println("defer", c) defer fmt.Println("defer", d) fmt.Println(a) fmt.Println(b) fmt.Println(c) fmt.Println(d) }
运行结果
1 2 3 4 defer 4 defer 3 defer 2 defer 1
延迟并不只仅局限于函数。延迟一个方法调用也是彻底合法的。
package main import "fmt" // Student 学生结构体 type Student struct { name string city string } func (s Student) hello() { fmt.Printf("我叫%v, 我来自%v。\n", s.name, s.city) } func main() { s := Student{ name: "jack", city: "北京市", } defer s.hello() fmt.Print("你们好,") }
运行结果
你们好,我叫jack, 我来自北京市
defer声明时会先计算肯定参数的值,defer推迟执行的仅是其函数体。
package main import "fmt" func main() { a := 1 defer fun(a) a++ fmt.Println("main中的a =", a) } func fun(a int) { fmt.Println("fun中的a =", a) }
运行结果
main中的a = 2 fun中的a = 1
package main import "fmt" func main() { fmt.Println(fun1()) } func fun1() int { var i int defer func() { i++ }() return i }
运行结果
0
package main import "fmt" func main() { fmt.Println(fun2()) } func fun2() (i int) { defer func() { i++ }() return i }
运行结果
1
分析:
fun1()int
函数的返回值没有被提早声名,其值来自于其余变量的赋值,而 defer 中修改的也是其余变量(其实该 defer 根本没法直接访问到返回值),所以函数退出时返回值并无被修改。fun2()(i int)
函数的返回值被提早声名,这使得 defer 能够访问该返回值,所以在 return 赋值返回值 i 以后,defer 调用返回值 i 并进行了修改,最后导致 return 调用 RET 退出函数后的返回值才会是 defer 修改过的值。经典案例
package main import "fmt" func main() { fmt.Println(f1()) fmt.Println(f2()) fmt.Println(f3()) fmt.Println(f4()) } func f1() int { x := 5 defer func() { x++ // defer 访问的是变量x,访问不到返回值 // fmt.Println("f1函数defer中的x =", x) //6 }() return x // 返回值 = 5 //返回5 } func f2() (x int) { defer func() { x++ //defer 访问x, 能够访问返回值,在RET以前,将返回值修改成6 // fmt.Println("f2函数defer中的x =", x) //6 }() return 5 // 返回值(x) = 5 //返回6 } func f3() (y int) { x := 5 defer func() { x++ // defer 访问变量x,将变量x修改成6 // fmt.Println("f3函数defer中的x =", x) //6 }() return x // 返回值(y) = 5 //返回5 } func f4() (x int) { defer func(x int) { x++ // 这里修改的defer时传入的x(0),将其修改成1 // fmt.Println("f4函数defer中的x =", x) //1 }(x) // defer 语句调用时传入x的值为int类型的默认值0 return 5 // 返回值(x) = 5 //返回5 }
运行结果
5 6 5 5
package main import ( "fmt" // "os" ) func main() { fmt.Println("start") // panic("崩溃了") // defer和以后的语句都再也不执行 // os.Exit(1) // defer和以后的语句都再也不执行 defer fmt.Println("defer") // go func() { // panic("崩溃了") // }() // defer不被执行 // panic("崩溃了") // defer会执行,但后面的语句再也不执行 fmt.Println("over") // os.Exit(1) // defer不被执行 }
内置函数 | 介绍 |
---|---|
close | 主要用来关闭channel |
len | 用来求长度,好比string、array、slice、map、channel |
new | 用来分配内存,主要用来分配值类型,好比int、struct。返回的是指针 |
make | 用来分配内存,主要用来分配引用类型,好比chan、map、slice |
append | 用来追加元素到数组、slice中 |
panic和recover | 用来作错误处理 |
panic和recover
Go语言中目前是没有异常机制,可是使用panic/recover模式来处理错误。 panic能够在任何地方引起,但recover只有在defer调用的函数中有效。
package main import ( "fmt" ) func main() { fmt.Println("start") defer func() { err := recover() if err != nil { fmt.Println("recover") fmt.Println("活了") } }() panic("panic") fmt.Println("over") }
运行结果
start recover 活了