社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
函数是组织好的、可重复使用的、用于执行指定任务的代码块。
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
形式参数:定义函数时,用于接收外部传入的数据,叫做形式参数,简称形参。
实际参数:调用函数时,传给形参的实际的数据,叫做实际参数,简称实参。
函数调用:
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语言中的可变参数通过在参数名后加...来标识。
注意:可变参数通常要作为函数的最后一个参数。
func funcName(arg ...int) {}
arg ...int
告诉Go这个函数接受不定数量的参数。注意,这些参数的类型全部是int。在函数体中,变量arg是一个int类型的slice
go语言函数的参数也是存在值传递和引用传递
数据类型:
按照数据类型来分:
按照数据的存储特点来分:
值传递:值类型的数据传递为值传递,传递的是数据的副本。修改数据,对原始数据没有影响。
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("%Tn", fun1) //fun1的类型是func(int, int)
fmt.Printf("%Tn", 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 = %vn", 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
活了
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!