函数

Golang函数特点

  • 无需声明原型
  • 支持多返回值
  • 不定参数传参 也就是函数的参数个数不是固定的 但是后面的类型是固定的
  • 支持命名返回参数
  • 支持匿名函数和闭包
  • 函数也是一种类型 可以赋值给一个变量
  • 不支持 嵌套 一个包不能有2个名字一样的函数
  • 不支持重载
  • 不支持默认参数

函数声明

函数声明包含一个函数名,参数列表,返回值列表和函数体,如果函数没有返回值,则返回列表可以省略。函数从第一条语句开始执行,直到执行return语句或者执行函数的最后一条语句。

  1. func test(str,x string,y int)(int,string){
  2. //相同的2个类型 可以省略type合并称一个
  3. //(int,string)返回参数
  4. }
  5. //函数是第一类对象 可以作为参数传递
  • 参数 函数定义时指出,函数定义时有参数,该变量可被称为函数的形参,形参就像定义在函数的局部变量。但是当调用函数的时候,传递过来的变量就是函数的实参,函数可以通过2种方式传递参数
  1. 值传递:在 在调用函数的时候将实际参数复制一份传递到函数中,这样在函数中如果对参数修改,不会影响到实际参数

```go func swap(x ,y int)int{

}

  1. > 2.
  2. 引用传递: 引用传递是指在调用函数时把实际参数的地址传递到函数中,那么函数中对参数进行修改就是对实际参数修改,会影响到实际参数。
  3. ```go
  4. func swap(x,y *int){//传入指针
  5. var temp int //创建一个临时变量
  6. temp =*x //x的地址的值赋值给temp临时变量
  7. *x=*y //y的地址的值给x
  8. *y=temp //temp的地址给到y
  9. }


在默认情况下Go语言使用的是值传递,也就是在调用过程中不会影响到实际参数
无论是值传递,还是引用传递,传递给函数的都是变量的副本,不过,值传递是值得拷贝。引用传递是地址得拷贝,一般来说,地址拷贝更加高效。而值拷贝取决于对象得大小,对象越大 性能越低
map\slice\array\chan\指针\interface 默认都是以引用的方式传递

Golang可变参数本质上就是slice。只能有一个,且必须是最后一个。在参数赋值的时候不用一个个的赋值。可以直接传递一个数组或者切片,注意最后一个参数要加上args...int

  • 返回值 “_”标识符,用来忽略函数的某个返回值。Go的返回值可以被命名,并且像在函数开头声明的变量那样使用。返回值的名称应当具有一定的意义,可以作为文档使用。没有参数的return语句返回各个变量的当前值。这种用法被称作”裸返回”。直接返回语句仅应用在像下面这样的短函数中,在长的函数中它们会影响代码的可读性。

```go //直接返回sum func add(x, y int) (sum int) { sum = x + y return } func main() { sum := add(10, 11) fmt.Println(sum)//21 } //命名返回参数允许defer延迟调用通过闭包读取和修改 //直接返回sum

//直接返回sum func add(x, y int) (sum int) { defer func() { fmt.Println(“加100”) sum += 100 }() sum = x + y return } func main() { sum := add(10, 11) fmt.Println(sum) //加100 121 //显示return返回前,会先修改命名返回参数 }

  1. <a name="0153bdbc"></a>
  2. #### 匿名函数
  3. > > 匿名函数是指不需要定义函数名的一种函数实现方式。在GO里面,函数可以像普通变量一样被传递或使用,Go语言支持随时在代码里定义匿名函数。匿名函数由一个不带函数声明和函数体组成。匿名函数的优越性可以直接使用函数内的变量不必声明。
  4. > ```go
  5. func main() {
  6. var sum = func() int {
  7. return 100 * 100
  8. > }
  9. fmt.Println(sum())//10000
  10. }
  11. //Golang匿名函数可赋值给变量,作为结构字段,或者在channel里传送
  12. func main() {
  13. //创建一个函数变量
  14. fun := func() {
  15. fmt.Println("111")
  16. }
  17. fun() //111
  18. //fun 数组 创建多个函数 函数的格式为接受一个参数返回一个int类型
  19. funs := [](func(x int) int){
  20. func(x int) int {
  21. return x + 1
  22. },
  23. func(x int) int {
  24. return x + 2
  25. },
  26. }
  27. fmt.Println(funs[0](100)) //101
  28. //作为结构体的一个field
  29. d := struct {
  30. fn func(x int) int
  31. }{ //创建一个函数变量
  32. fn: func(x int) int { //函数变量赋值
  33. return 1
  34. },
  35. }
  36. fmt.Println(d.fn(1)) //1
  37. //channel
  38. c := make(chan func() string, 2)
  39. c <- func() string {
  40. return "hello world"
  41. }
  42. fmt.Println((<-c)())//hello world
  43. }

闭包

闭包可以理解为一种保存函数状态的方法,当我们调用一个函数,或者执行操作,或者返回结果,函数运行结束之后,随即消亡,因为函数一般都是在堆上,当系统检测当前内存空间没有被引用就会回收

闭包的作用就是保存函数的运行状态 避免函数被回收。

  • 访问所在的作用域
  • 函数嵌套
  • 在所在作用域外被调用
  1. func bb() func(i int) int {
  2. var s int = 0
  3. fmt.Println("ccccc")
  4. fmt.Println("ccccc")
  5. b := func(i int) int {
  6. s = s + 1
  7. fmt.Printf("地址%#v", &s)
  8. return s
  9. }
  10. return b
  11. }
  12. func main() {
  13. a := bb()
  14. b := bb()
  15. fmt.Println(a(1))
  16. fmt.Println(a(1))
  17. fmt.Println(b(1))
  18. fmt.Println(b(1))
  19. }
  20. //ccccc
  21. //ccccc
  22. //ccccc
  23. //ccccc
  24. //地址(*int)(0xc00000a0a8)1
  25. //地址(*int)(0xc00000a0a8)2
  26. //地址(*int)(0xc00000a0c0)1
  27. //地址(*int)(0xc00000a0c0)2
  28. //可以看到a和b变量的调用 s被存在了不同的内存当中
  29. //当采用a调用的时候 s的变量被保存到了0xc00000a0a8当中
  30. //当采用b调用的时候 s变量被保存到了0xc00000a0c0 中 因为这是在调用bb()方法的时候 都申请了一块内存
  31. //把s定义成全局变量
  32. var s int = 0
  33. func bb() func(i int) int {
  34. b := func(i int) int {
  35. s = s + 1
  36. fmt.Printf("地址%#v", &s)
  37. return s
  38. }
  39. return b
  40. }
  41. func main() {
  42. a := bb()
  43. b := bb()
  44. fmt.Println(a(1))
  45. fmt.Println(a(1))
  46. fmt.Println(b(1))
  47. fmt.Println(b(1))
  48. }
  49. //ccccc
  50. //ccccc
  51. //ccccc
  52. //ccccc
  53. //地址(*int)(0x85dc68)1
  54. //地址(*int)(0x85dc68)2
  55. //地址(*int)(0x85dc68)3
  56. //地址(*int)(0x85dc68)4
  57. //可以看到当s提升为全局变量 申请的一块地址 用于a和b调用的时候 是共享的内存变量

通过以上案例我们可以得出在a:=bb()的时候执行了bb函数,实际上这里的指向是直接指向的bb()函数的内部子函数。

当我们调用a()的时候 直接执行的也是内部的子函数。

通过局部变量和全局变量可以发现,申请的内存方式是不同的 ```

递归函数

递归就是在运行的过程中调用自己。一个函数调用自身叫做递归函数

构成递归函数得条件:

子问题必须与原始问题为同样的事,且更为简单

不能无限制调用必须有个出口,化简为分递归状态处理

  1. func factorial(i int) int {
  2. if i <= 1 {
  3. return 1
  4. }
  5. return i * factorial(i-1)
  6. }
  7. func main() {
  8. fmt.Println(factorial(7))//5040
  9. }

延迟调用(defer)

```go defer特性: 1.关键字defer用于注册延迟调用

  1. 这些调用直到return前才会被执行。因此可以用来做资源清理 3.多个defer,按照先进后出的方式执行 4.defer语句中的变量,在defer声明时就决定了 defer用途: 1.关闭文件句柄 2.锁资源释放 3.数据库连接释放

func main() {

  1. for i := 0; i < 10; i++ {
  2. //defer是先进后出的 defer 调用的函数参数的值 defer 被定义时就确定了.
  3. //defer fmt.Print(strconv.Itoa(i) + "\t") //9 8 7 6 5 4 3 2 1 0
  4. //defer 碰上闭包
  5. defer func() {
  6. fmt.Println(&i) //defer func内部所使用的变量的值需要在这个函数运行时才确定
  7. //也就是讲这在闭包用到的时候这个变量已经变成了10,所以输出全都是10
  8. }()
  9. }

}

  1. <a name="2419eecb"></a>
  2. #### defer 与return
  3. >
  4. > ```go
  5. //defer 与return
  6. func foo() (i int) {
  7. i = 0
  8. defer func() {
  9. fmt.Println(i)
  10. }()
  11. //在具名返回函数中,执行return 2的时候已经将i的值赋值为2了。所以输出的时候结果为2不是0
  12. return 2
  13. }
  14. func main() {
  15. foo()
  16. }

defer nil报错

```golang //defer nil函数报错

func test() { //声明的时候未被调用 var run func() = nil //被调用 报错 defer run() fmt.Println(“runs”) }

func main() { //捕捉异常 defer func() { if err := recover(); err != nil { fmt.Println(“错误日志”) fmt.Println(err) } }() //调用 runtime error: invalid memory address or nil pointer dereference //main从上到下开始执行。defer 延迟执行 //执行test方法。test方法调用的时候,出test的时候进行报错。 当test调用完成的时候 defer run才会被调用 test() }

  1. <a name="840772d1"></a>
  2. #### 在错误的地方使用defer
  3. >
  4. > ```go
  5. func do() error {
  6. res, err := http.Get("http://www.google.com")
  7. defer res.Body.Close()
  8. if err != nil {
  9. return err
  10. }
  11. return nil
  12. }
  13. func main() {
  14. do()
  15. //panic: runtime error: invalid memory address or nil pointer dereference
  16. //因为google无法访问因为网络原因 defer 直接使用了res变量 res为nill未判断
  17. //可以在defer 的时候加上一层判断解决该错误
  18. if res!=nil{
  19. defer res.Body.Close()
  20. }
  21. }

不检查错误

```go //对于f.Close()可能会返回一个错误,但是这个错误会被我们忽略掉 func do() error { f, err := os.Open(“book.txt”) if err != nil { return err }

  1. if f != nil {
  2. defer f.Close()
  3. }
  4. return nil

}

func main() { do() } // 改进 if f!=nill{ defer func(){ if err:=f.Close();err!=nill{ //code } } }

  1. <a name="9094edd1"></a>
  2. #### 释放相同的资源
  3. >
  4. > ```go
  5. func do() error {
  6. f, err := os.Open("book.txt")
  7. if err != nil {
  8. return err
  9. }
  10. if f != nil {
  11. defer func() {
  12. if err := f.Close(); err != nil {
  13. fmt.Printf("defer close book.txt err %v\n", err)
  14. }
  15. }()
  16. }
  17. f, err = os.Open("another-book.txt")
  18. if err != nil {
  19. return err
  20. }
  21. if f != nil {
  22. defer func() {
  23. if err := f.Close(); err != nil {
  24. fmt.Printf("defer close another-book.txt err %v\n", err)
  25. }
  26. }()
  27. }
  28. return nil
  29. }
  30. func main() {
  31. do()//输出结果: defer close book.txt err close ./another-book.txt: file already closed
  32. //当延迟函数执行时候,只有最后一个变量会被用到,因此f会成为最后那个资源。而且2个资源都将这一个资源作为关闭对象
  33. }
  34. //解决方案
  35. //可以把f当作参数传递进去
  36. defer func(f io.Closer){
  37. }(f)

异常处理

Golang没有结构化异常,使用panic抛出异常,recover捕获错误。

异常的使用场景很简单:Go可以抛出一个panic异常,然后在defer中通过recover捕获这个异常。然后正常处理

  1. panic:
  2. 1.内置函数
  3. 2.加入函数F中书写了panic语句 ,会终止其后要执行得代码,在panic所在函数F内如果存在要执行得defer函数列表。按照defer先进后出执行
  4. 3.返回函数F得调用者G,在G中,调用函数F语句之后得代码不会执行,加入函数G在存在要执行得defer先进后厨执行
  5. 4.直到goroutine整个退出,并报告错误
  6. recover:
  7. 1.内置函数
  8. 2.用来控制一个goroutinepanicking行为,捕获panic,从而影响应用得行为
  9. 3.一般的调用建议
  10. 1.defer函数中,通过recever来终止一个goroutinepanicking过程,从而恢复正常代码的执行
  11. 2.可以获取通过panic传递的error
  12. 注:
  13. 1.利用recover处理panic指令,defer必须放在panic之前定义,另外recover只有在defer调用的函数中才有效,否则当panicreceover无法捕捉到panic,无法防止panic扩散
  14. 2.recover处理异常后,逻辑并不会恢复到panic那个点去,函数跑到defer之后的那个点
  15. 3.多个defer会形成defer栈,后定义的defer语句最先被调用。

延迟调用

```go //延迟调用中引发错误,可被后续异常调用捕获,但仅最后一个错误可被捕获 func test() { defer func() { fmt.Println(“最后调用”) fmt.Println(recover()) }()

  1. defer func() {
  2. fmt.Println("第一次调用")
  3. panic("defer panic")
  4. }()
  5. panic("test panic")

}

func main() { test() } //捕获函数recover 只有在延迟调用内直接调用才会终止错误,否则总是返回nill。任何未捕获的错误都会沿调用堆栈向外传递 package main

import ( “fmt” )

func test() { defer func() { fmt.Println(recover()) //有效 }() defer recover() //无效! defer fmt.Println(recover()) //无效! defer func() { func() { println(“defer inner”) recover() //无效! }() }()

  1. panic("test panic")

}

func main() { test() }

  1. > Go实现类似try catch的异常处理
  2. > ```go
  3. func Try(fn func(), handler func(interface{})) {
  4. defer func() {
  5. if err := recover(); err != nil {
  6. handler(err)
  7. }
  8. }()
  9. fn()
  10. }
  11. func main() {
  12. Try(func() {
  13. fmt.Println("测试")
  14. }, func(err interface{}) {
  15. })
  16. }