老虞要学golang-凯发k8网页登录

那些青春的岁月

  凯发k8网页登录-凯发天生赢家一触即发官网 :: 凯发k8网页登录首页 :: 联系 :: 聚合  :: 管理
  500 posts :: 0 stories :: 135 comments :: 0 trackbacks

不可或缺的函数,在go中定义函数的方式如下:

func (p mytype ) funcname ( a, b int , c string ) ( r , s int ) {     return } 

通过函数定义,我们可以看到go中函数和其他语言中的共性和特性

共性

  • 关键字——func
  • 方法名——funcname
  • 入参——— a,b int,b string
  • 返回值—— r,s int
  • 函数体—— {}

特性

go中函数的特性是非常酷的,给我们带来不一样的编程体验。

为特定类型定义函数,即为类型对象定义方法

在go中通过给函数标明所属类型,来给该类型定义方法,上面的 p mytype 即表示给mytype声明了一个方法, p mytype 不是必须的。如果没有,则纯粹是一个函数,通过包名称访问。packagename.funcationname

如:

//定义新的类型double,主要目的是给float64类型扩充方法 type double float64  //判断a是否等于b func (a double) isequal(b double) bool {     var r = a - b     if r == 0.0 {         return true     } else if r < 0.0 {         return r > -0.0001     }     return r < 0.0001 }  //判断a是否等于b func isequal(a, b float64) bool {     var r = a - b     if r == 0.0 {         return true     } else if r < 0.0 {         return r > -0.0001     }     return r < 0.0001 }  func main() {     var a double = 1.999999     var b double = 1.9999998     fmt.println(a.isequal(b))     fmt.println(a.isequal(3))     fmt.println( isequal( (float64)(a), (float64)(b) ) )  } 

上述示例为 float64 基本类型扩充了方法isequal,该方法主要是解决精度问题。 其方法调用方式为: a.isequal(double) ,如果不扩充方法,我们只能使用函数isequal(a, b float64)

入参中,如果连续的参数类型一致,则可以省略连续多个参数的类型,只保留最后一个类型声明。

如 func isequal(a, b float64) bool 这个方法就只保留了一个类型声明,此时入参a和b均是float64数据类型。 这样也是可以的: func isequal(a, b float64, accuracy int) bool

变参:入参支持变参,即可接受不确定数量的同一类型的参数

如 func sum(args ...int) 参数args是的slice,其元素类型为int 。经常使用的fmt.printf就是一个接受任意个数参数的函数 fmt.printf(format string, args ...interface{})

支持多返回值

前面我们定义函数时返回值有两个r,s 。这是非常有用的,我在写c#代码时,常常为了从已有函数中获得更多的信息,需要修改函数签名,使用out ,ref 等方式去获得更多返回结果。而现在使用go时则很简单,直接在返回值后面添加返回参数即可。

如,在c#中一个字符串转换为int类型时逻辑代码

int v=0;  if ( int.trypase("123456",out v) ) {     //code } 

而在go中,则可以这样实现,逻辑精简而明确

if v,isok :=int.trypase("123456") ; isok {     //code } 

同时在go中很多函数充分利用了多返回值

  • func (file *file) write(b []byte) (n int, err error)
  • func sincos(x float64) (sin, cos float64)

那么如果我只需要某一个返回值,而不关心其他返回值的话,我该如何办呢? 这时可以简单的使用符号下划线”_“ 来忽略不关心的返回值。如:

_, cos = math.sincos(3.1415) //只需要cos计算的值 

命名返回值

前面我们说了函数可以有多个返回值,这里我还要说的是,在函数定义时可以给所有的返回值分别命名,这样就能在函数中任意位置给不同返回值复制,而不需要在return语句中才指定返回值。同时也能增强可读性,也提高godoc所生成文档的可读性

如果不支持命名返回值,我可能会是这样做的

func readfull(r reader, buf []byte) (int, error) {     var n int     var err error      for len(buf) > 0  {         var nr int         nr, err = r.read(buf)          n  = nr         if err !=nil {             return n,err         }         buf = buf[nr:]     }     return n,err } 

但支持给返回值命名后,实际上就是省略了变量的声明,return时无需写成return n,err 而是将直接将值返回

func readfull(r reader, buf []byte) (n int, err error) {     for len(buf) > 0 && err == nil {         var nr int         nr, err = r.read(buf)         n  = nr         buf = buf[nr:]     }     return } 

函数也是“值”

和go中其他东西一样,函数也是值,这样就可以声明一个函数类型的变量,将函数作为参数传递。

声明函数为值的变量(匿名函数:可赋值个变量,也可直接执行)

//赋值 fc := func(msg string) {     fmt.println("you say :", msg) } fmt.printf("%t \n", fc) fc("hello,my love") //直接执行 func(msg string) {     fmt.println("say :", msg) }("i love to code") 

输出结果如下,这里表明fc 的类型为:func(string)

func(string)  you say : hello,my love say : i love to code 

将函数作为入参(回调函数),能带来便利。如日志处理,为了统一处理,将信息均通过指定函数去记录日志,且是否记录日志还有开关

func log(title string, getmsg func() string) {     //如果开启日志记录,则记录日志     if true {         fmt.println(title, ":", getmsg())     } } //---------调用-------------- count := 0 msg := func() string {     count       return "您没有即使提醒我,已触犯法律" } log("error", msg) log("warring", msg) log("info", msg) fmt.println(count) 

这里输出结果如下,count 也发生了变化

error : 您没有即使提醒我,已触犯法律 warring : 您没有即使提醒我,已触犯法律 info : 您没有即使提醒我,已触犯法律 3 

函数也是“类型”

你有没有注意到上面示例中的 fc := func(msg string)... ,既然匿名函数可以赋值给一个变量,同时我们经常这样给int赋值 value := 2 ,是否我们可以声明func(string) 类型 呢,当然是可以的。

//一个记录日志的类型:func(string) type savelog func(msg string)  //将字符串转换为int64,如果转换失败调用savelog func stringtoint(s string, log savelog) int64 {      if value, err := strconv.parseint(s, 0, 0); err != nil {         log(err.error())         return 0     } else {         return value     } }  //记录日志消息的具体实现 func mylog(msg string) {     fmt.println("find error:", msg) }  func main() {     stringtoint("123", mylog) //转换时将调用mylog记录日志     stringtoint("s", mylog) } 

这里我们定义了一个类型,专门用作记录日志的标准接口。在stringtoint函数中如果转换失败则调用我自己定义的接口函数进行日志处理,至于最终执行的哪个函数,则无需关心。

defer 延迟函数

defer 又是一个创新,它的作用是:延迟执行,在声明时不会立即执行,而是在函数return后时按照后进先出的原则依次执行每一个defer。这样带来的好处是,能确保我们定义的函数能百分之百能够被执行到,这样就能做很多我们想做的事,如释放资源,清理数据,记录日志等

这里我们重点来说明下defer的执行顺序

func deferfunc() int {     index := 0      fc := func() {          fmt.println(index, "匿名函数1")         index            defer func() {             fmt.println(index, "匿名函数1-1")             index           }()     }      defer func() {         fmt.println(index, "匿名函数2")         index       }()      defer fc()      return func() int {         fmt.println(index, "匿名函数3")         index           return index     }() }  func main() {     deferfunc() } 

这里输出结果如下,

0 匿名函数3 1 匿名函数1 2 匿名函数1-1 3 匿名函数2 

有如下结论:

  • defer 是在执行完return 后执行
  • defer 后进先执行

另外,我们常使用defer去关闭io,在正常打开文件后,就立刻声明一个defer,这样就不会忘记关闭文件,也能保证在出现异常等不可预料的情况下也能关闭文件。而不像其他语言:try-catch 或者 using() 方式进行处理。

file , err :=os.open(file) if err != nil {     return err } defer file.close()  //dosomething with file 

后续,我将讨论: 作用域、传值和传指针 以及 保留函数init(),main()

本笔记中所写代码存储位置:

    posted on 2017-08-02 16:39 abin 阅读(421) 评论(0)  编辑  收藏 所属分类: golang

    只有注册用户后才能发表评论。


    网站导航:
                  
     
    网站地图