Go基础编程——函数function

函 数 function
1、 Go 函 数 不 支 持 嵌套 、 重 载 和 默 认 参 数
2、但 支 持 以 下 特 性
无 需 声 明 原 型 、 不 定 长 度 变 参 、 多 返 回 值 、 命 名 返 回 值 参 数
匿 名 函 数 、 闭 包
3、定 义 函 数 使 用 关 键 字 func, 并且 左 大 括 号 不 能 另 起 一 行
4、函 数 也 可 以 作 为 一 种 类 型 使 用
对于放回值有两种情况要注意
(1)、只说明了返回值的类型,在return的时候要带上返回值
如下:

func testFunc()(int,int,int){
   a,b,c:=1,2,3
   return a,b,c
}

(2)在声明的时候就已经说明返回值类型和返回值,return后面就不需要跟上返回值,Go语言底层机制会自动带上的。

func testFunc()(a int,b int,c int){
   a,b,c=1,2,3
   return
}

这两种方法还有一个地方要注意,就是声明地方指出了返回值的变量名称的话在函数中就不需要再次使用:(冒号来代替var)
上边的第一种方法初始化并赋值使用

  a,b,c:=1,2,3

第二种方法只需要复制就好了,上边已经声明。

  a,b,c=1,2,3

为了代码的可读性,第二种方法也建议带上返回值的名称

return a,b,c

不定长度变参的表示如下:

func testFunc(m...int)(a int,b int,c int){
   a,b,c=1,2,3
   return a,b,c
}

其中第一个圆括号中m…int就表示接受变长的参数。
其实这里很清楚可以理解,m…的底层其实就是一个Slice
用程序说话:

func main(){
testFunc(1,2,3,4,5)

   }
   func testFunc(m...int){
      fmt.Println(m)
   }

运行结果:

[1 2 3 4 5]

还有一个规定-使用不定长参数的后面不可以再跟其他类型的参数,也不可以同时传递两个不定长参数的声明:
比如以下几种错误类型

func testFunc(m...int,n...string){
   fmt.Println(m)
}

这样就是报错的,报错如下

Can only use '...' as final argument in list less... (Ctrl+F1)
Reports possible issues in functions with variadic parameters
func testFunc(m...int,n string){
   fmt.Println(m)
}

报错同上。
正确方式如下:

func main(){
testFunc("Hello",1,2,3,4,5)

   }
   func testFunc(n string,m...int){
      fmt.Println(m)
   }

也就是说要放在变长参数的类型前边才不会报错。
下边补充一点关于Slice引用类型的深入理解:

func main(){
      a,b:=5,6
testFunc(a,b)
fmt.Println(a,b)

   }
   func testFunc(s...int){
      s[0]=1
      s[1]=2
      fmt.Println(s)
   }

运行结果

[1 2]
5 6

这里看到原始的ab并没有被Slice改变,因为Slice是一个引用类型,它只是进行的是值拷贝
而下一种情况进行地址拷贝就不一样,
看程序:

func main(){
      s1:=[]int{8,9,3,4,5}
      //a,b:=5,6
testFunc(s1)
fmt.Println(s1)

   }
   func testFunc(s []int){
      s[0]=1
      s[1]=2
      fmt.Println(s)
   }

运行结果,

[1 2 3 4 5]
[1 2 3 4 5]

这样把变长参数换成直接的底层的Slice传入的话就是一个内存地址的拷贝给它,这时候函数中就能够修改原内存地址的变量值。这也是值类型和引用类型传递的不同之处。同理传递指针也能改变元内存地址对应的值。看程序

func main(){
      a:=99

testFunc(&a)
fmt.Println(a)

   }
   func testFunc(a *int){
      *a=100

      fmt.Println(*a)
   }

运行结果

100
100

接下来说说函数function是如何作为一种类型的

func main(){
   F:=testFunc
   F()
}
func testFunc(){
   fmt.Println("testFunc")
}

运行结果

testFunc

这里如果某一个函数的使用斌率比较少,建议使用匿名函数,把上边程序改造如下:
**func main(){
F:=func(){
fmt.Println(“testFunc”)
}
F()
}**
运行结果同上。这就是匿名函数的使用。

闭包
作用——返回一个匿名函数

func main(){
   f:=closure(10)
   fmt.Println(f(1))
   fmt.Println(f(5))

}
func closure(x int) func(int) int{
   return func(y int) int {
      return x+y

   }
}

运行结果

11
15

这里的底层实现是x的内存地址的拷贝。看程序

func main(){
   f:=closure(10)
   fmt.Println(f(1))
   fmt.Println(f(5))

}
func closure(x int) func(int) int{
   fmt.Printf("%p\n",&x)
   return func(y int) int {
      fmt.Printf("%p\n",&x)
      return x+y

   }
}

运行结果

0xc0420080a8
0xc0420080a8
11
0xc0420080a8
15

三次打印地址都是一样的,证明是地址拷贝。

defer
1、defer 的 执 行 方 式 类 似 其 它 语 言 中 的 析 构 函 数 , 在 函 数 体 执行结束后按照调用顺序的相反顺序逐 个 执 行
2、 即 使 函 数 发 生 严 重错误 也 会 执 行
3、支 持 匿 名 函 数 的 调 用
4、常 用 于资源清 理 、 文 件 关 闭 、 解 锁 以 及 记 录时间等操作
5、通 过 与 匿 名 函 数 配 合 可 在 return 之 后 修 改 函 数 计算结果
6、如 果 函 数 体 内 某 个 变 量 作为 defer 时 匿 名 函数的 参 数 , 则 在 定 义 defer
时 即 已 经 获 得 了 拷 贝 , 否 则 则 是 引 用 某 个 变 量 的 地址
7、Go 没 有 异 常 机 制 , 但 有 panic/recover 模 式 来 处 理 错 误
8、Panic 可 以 在 任 何 地 方 引 发 , 但 recover 只 有 在 defer调用 的 函 数 中 有 效

先看defer知识点的第一条,相反顺序执行

fmt.Println("a")
defer fmt.Println("b")
defer fmt.Println("c")
defer fmt.Println("d")
defer fmt.Println("e")

运行结果

a
e
d
c
b

这就有些类似于Java中JDBC关闭数据库资源的顺序,反向来操作。
再来看defer支持匿名函数

for i:=0;i<3 ;i++  {
   defer func() {
      fmt.Println(i)
   }()
}

运行结果

3
3
3

首先来解释最后一个括号的原因,它就类似于将一个匿名函数作为一个类型赋给一个变量,变量名加上()表示执行这个匿名函数。类似于如下代码

func main(){
   F:=testFunc
   F()
}
func testFunc(){
   fmt.Println("testFunc")
}

中的F之后的().
这里还有要重点理解为什么打印的三个都是3.
这里实际上是一个闭包,在闭包中i一直引用局部变量i,这个循环体退出的时候i实际上已经是3,这就导致三次打印出来的都是3.最开始没有defer作用时候i作为一个参数传入,而有defer作用时i作为一个引用,一直都在引用局部变量。
用程序确保pnic()后面的程序依旧执行的时候就要用到recover()
接下来对比着看代码
首先是不使用recover的时候

func main(){
              A()
              C()
              D()
}
func A(){
   fmt.Println("func A")
}
func C(){
   panic("panic in C")
}
func D(){
   fmt.Println("func D")
}

运行结果

func A
panic: panic in C

goroutine 1 [running]:
main.C()
    D:/worplace/sorter/src/main/new_file.go:135 +0x40
main.main()
    D:/worplace/sorter/src/main/new_file.go:127 +0x2c

Process finished with exit code 2

第二种是使用recover的情况

func main(){
               A()
               C()
               D()
   }
   func A(){
      fmt.Println("func A")
   }
   func C(){
      defer func() {
         if err:=recover();err!=nil{
fmt.Println("recover in C")
         }
      }()

      panic("panic in C")
   }
   func D(){
      fmt.Println("func D")
   }

运行结果

func A
recover in C
func D

Process finished with exit code 0

这样就表示正常的运行了panic之后的程序。所以recover是用在不管出现什么情况下都可以补救,也只有在defer调用的函数中才能起到这种绝对的执行作用,也就这样才有他的意义

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值