Golang函数

函数

Golang函数特点

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

函数声明

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

func test(str,x string,y int)(int,string){
 //相同的2个类型 可以省略type合并称一个
 //(int,string)返回参数
}
//函数是第一类对象 可以作为参数传递 
  • 参数

    函数定义时指出,函数定义时有参数,该变量可被称为函数的形参,形参就像定义在函数的局部变量。但是当调用函数的时候,传递过来的变量就是函数的实参,函数可以通过2种方式传递参数

    1. 值传递:在 在调用函数的时候将实际参数复制一份传递到函数中,这样在函数中如果对参数修改,不会影响到实际参数
    func swap(x ,y int)int{
    
    }
    
    1. 引用传递: 引用传递是指在调用函数时把实际参数的地址传递到函数中,那么函数中对参数进行修改就是对实际参数修改,会影响到实际参数。

      func swap(x,y *int){//传入指针
       var temp int //创建一个临时变量
          temp =*x  //x的地址的值赋值给temp临时变量
          *x=*y //y的地址的值给x 
          *y=temp   //temp的地址给到y
      }
      

      在默认情况下Go语言使用的是值传递,也就是在调用过程中不会影响到实际参数

      无论是值传递,还是引用传递,传递给函数的都是变量的副本,不过,值传递是值得拷贝。引用传递是地址得拷贝,一般来说,地址拷贝更加高效。而值拷贝取决于对象得大小,对象越大 性能越低

      map\slice\array\chan\指针\interface 默认都是以引用的方式传递

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

  • 返回值

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

    //直接返回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返回前,会先修改命名返回参数
    }
    

匿名函数

匿名函数是指不需要定义函数名的一种函数实现方式。在GO里面,函数可以像普通变量一样被传递或使用,Go语言支持随时在代码里定义匿名函数。匿名函数由一个不带函数声明和函数体组成。匿名函数的优越性可以直接使用函数内的变量不必声明。

func main() {
  var sum = func() int {
      return 100 * 100
  >   }
  fmt.Println(sum())//10000
}
//Golang匿名函数可赋值给变量,作为结构字段,或者在channel里传送

func main() {
  //创建一个函数变量
  fun := func() {
      fmt.Println("111")
  }
  fun() //111
  //fun 数组 创建多个函数 函数的格式为接受一个参数返回一个int类型
  funs := [](func(x int) int){
      func(x int) int {
          return x + 1
      },
      func(x int) int {
          return x + 2
      },
  }
  fmt.Println(funs[0](100)) //101

  //作为结构体的一个field
  d := struct {
      fn func(x int) int
  }{ //创建一个函数变量
      fn: func(x int) int { //函数变量赋值
          return 1
      },
  }
  fmt.Println(d.fn(1)) //1

  //channel
  c := make(chan func() string, 2)
  c <- func() string {
      return "hello world"
  }
  fmt.Println((<-c)())//hello world

}

闭包

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

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

  • 访问所在的作用域
  • 函数嵌套
  • 在所在作用域外被调用
func bb() func(i int) int {
 var s int = 0
   fmt.Println("ccccc")
 fmt.Println("ccccc")
 b := func(i int) int {
     s = s + 1
     fmt.Printf("地址%#v", &s)
     return s
 >   }
 >   return b
 > }
 > func main() {
 >   a := bb()
 >   b := bb()
 >   fmt.Println(a(1))
 >   fmt.Println(a(1))
 >   fmt.Println(b(1))
 >   fmt.Println(b(1))
 > }
 > //ccccc
 > //ccccc
 > //ccccc
 > //ccccc
 > //地址(*int)(0xc00000a0a8)1
 > //地址(*int)(0xc00000a0a8)2
 > //地址(*int)(0xc00000a0c0)1
 > //地址(*int)(0xc00000a0c0)2
 > //可以看到a和b变量的调用 s被存在了不同的内存当中
 > //当采用a调用的时候 s的变量被保存到了0xc00000a0a8当中
 > //当采用b调用的时候 s变量被保存到了0xc00000a0c0 中 因为这是在调用bb()方法的时候 都申请了一块内存
 > 
 > //把s定义成全局变量
 > var s int = 0
 > func bb() func(i int) int {
 >   b := func(i int) int {
 >       s = s + 1
 >       fmt.Printf("地址%#v", &s)
 >       return s
 >   }
 >   return b
 > }
 > func main() {
 >   a := bb()
 >   b := bb()
 >   fmt.Println(a(1))
 >   fmt.Println(a(1))
 >   fmt.Println(b(1))
 >   fmt.Println(b(1))
 > }
 > //ccccc
 > //ccccc
 > //ccccc
 > //ccccc
 > //地址(*int)(0x85dc68)1
 > //地址(*int)(0x85dc68)2
 > //地址(*int)(0x85dc68)3
 > //地址(*int)(0x85dc68)4
 > //可以看到当s提升为全局变量 申请的一块地址 用于a和b调用的时候 是共享的内存变量
 > ```
 >
 > 通过以上案例我们可以得出在a:=bb()的时候执行了bb函数,实际上这里的指向是直接指向的bb()函数的内部子函数。
 >
 > 当我们调用a()的时候 直接执行的也是内部的子函数。
 >
 > 通过局部变量和全局变量可以发现,申请的内存方式是不同的

递归函数

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

构成递归函数得条件:

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

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

func factorial(i int) int {
  if i <= 1 {
      return 1
  }
  return i * factorial(i-1)
}
func main() {
  fmt.Println(factorial(7))//5040
}

延迟调用(defer)

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

func main() {

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


defer 与return

//defer 与return
func foo() (i int) {

 i = 0
 defer func() {
     fmt.Println(i)
 }()
 //在具名返回函数中,执行return 2的时候已经将i的值赋值为2了。所以输出的时候结果为2不是0
 return 2
}

func main() {
 foo()
}

defer nil报错

//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()
}

在错误的地方使用defer

func do() error {
 res, err := http.Get("http://www.google.com")
 defer res.Body.Close()
 if err != nil {
     return err
 }
 return nil
}

func main() {
 do()
 //panic: runtime error: invalid memory address or nil pointer dereference
 //因为google无法访问因为网络原因 defer 直接使用了res变量 res为nill未判断
//可以在defer 的时候加上一层判断解决该错误
if res!=nil{
   defer res.Body.Close()
}
}

不检查错误

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

 if f != nil {
     defer f.Close()
 }


 return nil
}

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

释放相同的资源

func do() error {
f, err := os.Open("book.txt")
if err != nil {
   return err
}
if f != nil {
   defer func() {
       if err := f.Close(); err != nil {
           fmt.Printf("defer close book.txt err %v\n", err)
       }
   }()
}

f, err = os.Open("another-book.txt")
if err != nil {
   return err
}
if f != nil {
   defer func() {
       if err := f.Close(); err != nil {
           fmt.Printf("defer close another-book.txt err %v\n", err)
       }
   }()
}

return nil
}

func main() {
do()//输出结果: defer close book.txt err close ./another-book.txt: file already closed
//当延迟函数执行时候,只有最后一个变量会被用到,因此f会成为最后那个资源。而且2个资源都将这一个资源作为关闭对象
}
//解决方案
//可以把f当作参数传递进去
defer func(f io.Closer){

}(f)

异常处理

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

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

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

延迟调用

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

  defer func() {
      fmt.Println("第一次调用")
      panic("defer panic")
  }()

  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() //无效!
      }()
  }()

  panic("test panic")
}

func main() {
  test()
}


Go实现类似try catch的异常处理

func Try(fn func(), handler func(interface{})) {
  defer func() {
      if err := recover(); err != nil {
          handler(err)
      }
  }()
  fn()
}
func main() {
  Try(func() {
      fmt.Println("测试")
  }, func(err interface{}) {

  })
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,290评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,107评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,872评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,415评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,453评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,784评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,927评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,691评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,137评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,472评论 2 326
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,622评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,289评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,887评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,316评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,490评论 2 348

推荐阅读更多精彩内容