本文参考:https://www.liwenzhou.com/posts/Go/09_function/
函数
函数概述
函数是一段能够重复使用的代码的封装。函数参数定义了外界给函数输入的数据。返回值定义了函数给外界输出的数据。Go语言函数支持不定长参数和多个返回值。
函数定义
Go语言中定义函数使用func 关键字,具体格式如下:
func 函数名(参数)(返回值){
函数体
}
函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。函数名也不能重复命名。
参数:参数由参数变量和参数变量的类型组成,多个参数之间使用, 分割
返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用() 包裹,并用, 分割
函数体:实现指定功能的代码块
// 实现求两个数之和的函数
func intSum(x int, y int) int{
return x + y
}
当然,函数的参数和返回值都是可选的,例如我们可以实现一个既不需要参数也没有返回值的函数
func sayHello(){
fmt.Println("fuck off")
}
函数的调用
定义了函数之后,可以通过函数名() 的方式调用函数。注意:调用有返回值的函数时,可以不接收其返回值。
func main(){
sayHello()
ret := intSum(1,2)
fmt.Println(ret)
}
参数
类型简写
函数的参数中如果相邻的变量的类型相同,则可以省略类型。
// 函数接收两个参数,这两个参数的类型均为int,因此可以省略x的类型,y后面有类型说明
func intSum(x,y int)int{
return x + y
}
可变参数(不定长参数)
可变参数是指函数短的参数数量不固定,Go语言中的可变参数通过在参数名后加... 来标识
注意:可变参数通常要座位函数的最后一个参数
func intSum(x ...int)int{
fmt.Println(x) // x是一个切片
sum := 0
for _,v := range x{
sum += v
}
return sum
}
func main(){
fmt.Println(intSum()) // 0
fmt.Println(intSum(10)) // 10
fmt.Println(intSum(10,20)) // 30
fmt.Println(intSum(10,20,30)) // 60
}
如果固定参数和可变参数混合使用,可变参数必须是在固定参数的后面
func intSum(x int, y ...int) int {
fmt.Println(x, y)
sum := x
for _,v := range y{
sum += v
}
return sum
}
func main(){
fmt.Println(intSum(10)) // 10
fmt.Println(intSum(10,20)) // 30
fmt.Println(intSum(10,20,30)) // 60
}
本质上,函数的可变参数是通过切片来实现的。
返回值
通过return 关键字向外输出返回值。
多返回值
函数如果有多个返回值时必须用() 将所有返回值包裹起来。
func calc(x,y int)(int, int){
sum := x + y
sub := x - y
return sum,sub
}
返回值命名
函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return 关键字返回。
func calc(x,y int)(sum,sub int){
sum = x + y
sum = x - y
return
}
返回值补充
当一个函数返回值类型是slice时,nil可以看做是一个有效的slice,没必要显示返回一个长度为0的切片。
func sliceFunc(x string) []int {
if x == ""{
return nil // 没必要返回[]int{}
}
}
函数进阶
变量作用域
全局变量
全局变量是定义在函数外部的变量,它在程序整个运行周期内都有效。在函数中可以访问到全局变量。
package main
import "fmt"
// 定义全局变量
var num int64 = 10 // 不能使用 num := 10
func globalVar(){
fmt.Println(num) // 函数中可以访问到全局变量num
}
func main(){
globalVar() // num=10
}
局部变量
局部变量又分为以下几种:
-
函数内部定义的变量无法在该函数外使用
func localVar(){
// 定义一个函数局部变量x,仅在该函数内生效
x := 100
fmt.Println(x)
}
func main(){
localVar()
fmt.Println(x) // 此时无法使用变量x
}
-
如果局部变量和全局变量重名,优先访问局部变量
package main
import "fmt"
// 定义全局变量num
var num int64 = 100
func testNum(){
num := 10
fmt.Println(num) // 10 函数优先使用局部变量
}
fumc main(){
testNum() // 10
fmt.Println(num) // 100
}
-
在if ,for ,switch 等语句块中定义的变量,也只能在语句块中访问
func localVar(x, y int){
fmt.Println(x,y) // 函数的参数也只能在本函数中生效
if x > 0{
z := 100 // 变量z只能在if语句块生效
fmt.Println(z)
}
// fmt.Println(z) // 此处无法使用变量z
}
-
for循环语句中定义的变量,也是只能在for语句中生效
func localVar(){
for i := 0; i < 10; i++ {
fmt.Println(i) // 变量i只能在当前for语句块中生效
}
// fmt.Println(i) // 此处无法使用变量i
}
函数类型与变量
定义函数类型
我们可以通过type 关键字来定义一个函数类型
type calc func(int, int) int
上面的语句定义了一个calc 函数类型,这种函数类型接收两个int类型的参数并且返回一个int类型的返回值。
简单说,凡是满足这个条件的函数都是calc 类型的函数。例如下面的sum和sub都是calc类型
func sum(x, y int)int{
return x + y
}
func sum(x, y int)int{
return x - y
}
sum和sub都能赋值给calc类型的变量
var c calc
c = add
函数类型变量
我们可以声明函数类型的变量并未该变量赋值
func main(){
var c calc //声明一个calc类型的变量c
c = sum // 把sum赋值给c
fmt.Printf("type of c:%T\n",c) //type of c:main.calc
fmt.Println(c(1,2)) // 像调用sum一样调用c
d := sub // 把函数sub赋值给变量d
fmt.Printf("type if d:%T\n", d) // type of d:func(int,int)
fmt.Println(d(20,10)) // 像调用sub一样调用d
}
高阶函数
函数作为参数
func sum(x,y int)int{
return x + y
}
func calc(x,y int, op func(int,int)int)int{
return op(x,y)
}
func main(){
ret := calc(10,20,sum)
fmt.Println(ret) // 30
}
函数作为返回值
func do(s string)(func(int,int)int,error){
switch s {
case "+":
return sum,nil
case "-":
return sub,nil
default:
err := errors.New("无法识别的操作符")
return nil,err
}
}
匿名函数
匿名函数主要作用是封装一段一次性执行的代码,它无所谓复用,所以无需起名,之所以进行封装的意义在于使一段代码成为 一个整体。
func(参数)(返回值){
函数体
}
匿名函数因为没有函数名,所以没有办法像普通函数那样调用,所以匿名函数需要保存到某个变量或作为立即执行的函数。
func main(){
// 将匿名函数保存到变量
add := func(x,y int){
fmt.Println(x + y)
}
add(10,20) // 通过变量调用匿名函数
// 自执行函数:匿名函数定义完加()直接执行
func (x,y int){
fmt.Println(x + y)
}(10,20)
}
匿名函数多用于实现回调函数和闭包。
闭包
闭包指一个函数和与其相关的引用环境组合而成的实体,简单来说,闭包=函数+引用环境 。
func adder()func(int)int{
var x int // 初始为0
return func(y int)int{
x += y
return x
}
}
func main(){
var a = adder() // x = 0
fmt.Println(a(10)) // adder()(10) x=0, y=10 x=10
fmt.Println(a(20)) // adder()(20) x=10,y=20 x=30
fmt.Println(a(30)) // adder()(30) x=30,y=30 x=60
b := adder() // x = 0
fmt.Println(b(40)) // adder()(40) x=0,y=40 x=40
fmt.Println(b(50)) // adder()(50) x=40,y=50 x=90
}
变量a 和b 是一个函数并且它们引用了其外部作用域中x 变量。此时a 和b 就是闭包。在a 和b 的生命周期内,变量x 一直有效。
闭包进阶示例1:
func adder(x int) func(int) int {
return func(y int) int {
x += y
return x
}
}
func main(){
var a = adder(10) // x=10
fmt.Println(a(10)) // adder(10)(10) x=10,y=10,x=20
fmt.Println(a(20)) // adder(20)(20) x=20,y=20,x=40
fmt.Println(a(30)) // adder(40)(30) x=40,y=30,x=70
b := adder(20) // x=20
fmt.Println(b(40)) // adder(20)(40) x=20,y=40,x=60
fmt.Println(b(50)) // adder(60)(50) x=60,y=50,x=110
}
闭包进阶示例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")) // test.jpg
fmt.Println(txtFunc("test")) // test.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(){
a,b := calc(10) // base = 10
fmt.Println(a(1),b(2)) // a(1)->add(10)(1)->base=10,i=1,base=11;
// b(2)->sub(11)(2)->base=11,i=2,base=9
fmt.Println(a(3),b(4)) // a(3)->add(9)(3)->base=12, b(4)->8
}
闭包的好处:内层函数的状态被保存在闭包中,不使用闭包就要开辟多个全局变量来保存函数以外的数据。如果这个函数被多方调用,大家都需要各自保存各自的数据 ,此时就需要开辟多个全局变量,具体使用哪个全局变量,还要在函数内做判断,增大了重复代码。令代码开起来比较垃圾。
使用多个全局变量保存多套副本的索引
// 全局变量
var heros = [...]string{"关胜","林冲","秦明","呼延灼","武松","鲁达"}
// 宋江的索引
var index1 = 0
// 吴用的索引
var index2 = 0
// 脑补卢员外的索引,柴进的索引...
func useNormal(){
for i:=0; i<10;i++{
fmt.Println(giveMeOne("宋江"))
}
for i:=0;i<10;i++{
fmt.Println(giveMeOne("吴用"))
}
}
func giveMeOne(who string) string{
var theOne = ""
// 差不多的东西写两遍,很垃圾
// 万一卢俊义也来带队,那就需要三个全局变量,三个if分支。。。
if who == "宋江"{
theOne = heros[index1]
index1++
if index1 > len(heros) -1{
index1 = 0
}
} else {
theOne = heros[index2]
index2 ++
if index2 >len(heros)-1{
index2 = 0
}
}
return theOne
}
使用闭包函数
// 全局变量
var heros = [...]string{"关胜","林冲","秦明","呼延灼","武松","鲁达"}
// 使用函数闭包的案例
func useClosure(){
// 得到返回的闭包内函数
songjiang := giveHimOne(0)
wuyong := giveHimOne(4)
for i:=0;i<10;i++{
fmt.Println("宋江线", songjiang("黑子"))
}
for i:=0;i<10;i++{
fmt.Println("吴用线", wuyong("大坏比"))
}
}
// 闭包函数:返回函数的函数
func giveHimOne(start int)func(name string)string{
// 保存闭包系统内的状态
var index int = start
// 内层函数
return func(name string) string{
theOne := heros[index]
// 状态被保存在外层的闭包中
index++
if index > len(heros)-1{
index = 0
}
return name + ":" + theOne
}
}
defer语句
Go语言中的defer 语句会将其后面跟随的语句进行延迟处理。在defer 归属的函数即将返回时,将延迟处理的语句按defer 定义的逆序进行执行,也就是说,先被defer 的语句最后被执行,最后被defer 的语句,最先被执行。
示例:
func main(){
fmt.Println("start")
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
fmt.Println("end")
}
输出:
start
end
3
2
1
由于defer 语句延迟调用的特性,所以defer 语句能非常方便的处理资源释放问题,比如:资源清理,文件关闭,解锁及记录时间等。
defer执行时机
在Go语言的函数中return 语句在底层并不是原子操作,它分为给返回值赋值和RET指令两部。而defer 语句执行的时机就在返回值赋值操作后,RET指令执行前。
func f1() int {
x := 5
defer func(){
x++ // 改变的是x,不是返回值
}()
return x
}
func f2() (x int){
defer func(){
x++
}()
return 5 // 最终返回x,先给x赋值,x=5,执行defer,x++,此时x=6, 最后返回x
}
func f3() (y int){
x := 5
defer func(){
x++ // 改变的是y
}()
return x // 返回x 5
}
func f4() (x int){
defer func(x int){
x++ // 改变x的副本
}(x)
return 5 // 5
}
func f5()(x int){
defer func(x int) int {
x ++
return x // 改变x的副本
}(x)
return 5 // 5
}
// 传一个x的指针
func f6()(x int){
defer func(x *int){
*x ++
}(&x)
return 5 // 1.返回值=x=5; 2.defer x=6, 3.返回x
}
func main(){
fmt.Println(f1()) // 5
fmt.Println(f2()) // 6
fmt.Println(f3()) // 5
fmt.Println(f4()) // 5
fmt.Println(f5()) // 5
fmt.Println(f6()) // 6
}
defer面试题
func calc(index string, a, b int)int{
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
func main(){
x := 1
y := 2
defer calc("AA",x,calc("A",x,y))
x = 10
defer calc("BB",x,calc("B",x,y))
y = 20
}
输出结果:
// 分析
//1: a = 1, b = 2
//2: calc("A",1,2)->3
//3: defer calc("AA",x,3)->defer calc("AA",1,3)
//4: x = 10
//5: calc("B",10,2)->12
//6: defer calc("BB",10,12)
//7: y = 20
//8: 执行6
//9:执行3
递归函数
递归:函数自己调用自己
// 计算n的阶乘
func f(n uint64)uint64{
if n<= 1{
return 1
}
return n * f(n-1)
}
// 上台阶面试题
// n个台阶,一次走一步,也可以走两步,有多少种走法
func taijie(n uint64) uint64{
if n == 1{
return 1
}
if n == 2{
return 2
}
return taijie(n-1) + taijie(n-2)
}
内置函数介绍
内置函数 |
介绍 |
close |
主要用来关闭channel |
len |
用来求长度,比如string、array、slice、map、channel |
new |
用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针 |
make |
用来分配内存,主要用来分配引用类型,比如chan、map、slice |
append |
用来追加元素到数组、slice中 |
panic和recover |
用来做错误处理 |
panic/recover
使用panic/recover 模式来处理错误。panic 可以再任何地方引发,但recover 只有在defer 调用的函数中有效。
func f1(){
fmt.Println("func f1")
}
func f2(){
panic("panic in f2")
}
func f3(){
fmt.Println("func f3")
}
func main(){
f1()
f2()
f3()
}
输出:
func f1
panic:panic in f2
goroutine 1 [running]:
main.f2(...)
.../code/func/main.go:12
main.main()
.../code/func/main.go:20 +0x98
程序运行期间f2 中引发panic 导致程序崩溃,异常退出了,这时候我们就可以通过recover 将程序恢复回来,继续往后执行。
func funcA(){
fmt.Println("func A")
}
func funcB(){
defer func(){
err := recover()
// 如果程序出现了panic错误,可以通过recover恢复过来
if err != nil{
fmt.Println("recover in B")
}
}()
panic("panic in B")
}
func funcC(){
fmt.Println("func C")
}
func main(){
funcA()
funcB()
funcC()
}
注意:
recover() 必须搭配defer 使用
defer 一定要在可能引发panic 的语句之前定义
练习题
你有50枚金币,需要分配给一下几个人:Mathew,Sarah,Augustus,Heidi,Emilie,Peter,Giana,Adriano,Aaron,Elizabeth.
分配规则如下:
a.名字中每包含1个'e'或'E'分1枚金币
b.名字中没包含1个'i'或'I'分2枚金币
c.名字中没包含1个'o'或'I'分O枚金币
b.名字中没包含1个'u'或'I'分U枚金币
写一个程序,计算每个用户分到多少金币,以及最后剩余多少金币?
程序结构如下,请实现'dispatchCoin'函数
var (
coins = 50
users = [] string{"Matthew","Sarah","Augustus","Heidi","Emilie","Peter","Giana","Adriano","Aaron","Elizabeth",}
distribution = make(map[string]int, len(users))
)
func main() {
left := dispatchCoin()
fmt.Println("剩下:",left)
fmt.Println(distribution)
}
func dispatchCoin()(left int){
for _,name:=range users{
for _,c:=range name{
switch c {
case 'e','E':
// 满足这个条件,分一枚金币
distribution[name] ++
coins --
case 'i','I':
distribution[name] += 2
coins -= 2
case 'o','O':
distribution[name] +=3
coins -= 3
case 'u', 'U':
distribution[name] += 4
coins -= 4
}
}
}
return coins
}
本文参考:https://www.liwenzhou.com/posts/Go/09_function/
|
请发表评论