go语言函数支持多返回值。
func name(parameter-list)(result-list){
body
}
当函数存在返回列表的时候,必须显示以return结束。(除了break,抛异常等操作)
func sub(x,y int) (z int){ z = x-y;return} //如果形参类型相同,可以写到一起;返回值也可以命名,这时候,每个命名的返回值会声明一个局部变量,并初始化零值,最后跟一个return即可 func first(x int,_ int) int{return x} //空白标识强调这个形参在函数中未使用 func zero(int,int) int{return 0}
形参和返回值名字不会影响函数签名。
Go语言没有默认参数值概念也不能指定实参名(和python不一样)。go语言是值传递,指针、slice、map、函数、通道是引用类型,使用时可能会间接改变实惨值。
如果函数的声明没有函数体,就说明这个函数使用了除了go以外的语言实现。
许多编程语言使用固定长度的函数调用栈,大小在64KB到2MB之间。递归的深度会受限于固定长度的栈大小,所以当进行深度递归调用时必须提防栈溢出。相比于固定长度的栈,Go语言的实现使用了可变长度的栈,栈的大小会随着使用而增长,可达到1GB左右上限。
题外话:Go语言的垃圾回收机制将会回收未使用的内存,但不能指望它会释放未使用的操作系统资源,比如打开的文件以及网络连接,必须显式的关闭它们。
调用者使用多返回值的函数时候,必须有变量承接返回值,如果不想使用,用‘_‘承接。
func f(a string)([]string,error){ /*...*/ return f(a); } //返回一个多值结果可以是调用另一个多值返回的函数 //一个多值调用可以作为单独的实参传递给拥有多个形参的函数中 log.Println(f(a)) //等价于 strings, err:=f(a) log.Println(strings,err)
即使在高质量代码中,也不能保证一定能够成功返回,习惯上将错误值作为最后一个结果返回。如果错误只有一种情况,结果通常设置为布尔类型。
如果错误原因很多,那么错误类型往往是error,error是内置的接口类型。目前我们了解到,错误可能是空值(成功)和非空值(失败),通过调用fmt.Println(err)或fmt.Printf("%v",err)输出错误信息。与其他语言不一样,go语言通过使用普通的值而非异常来报告错误。
首先最常用的是将错误传递下去:return nil,fmt.Errorf("parsing %s as HTML: %v",url,err)。错误返回信息要可读、有意义、格式一致。
第二种情况是对于不固定或者不可预测的错误,在短暂的间隔后对操作进行重试是合乎情理的,超出一定的重试次数和限定时间后再报错退出。
如果还不能顺利执行,调用者能够输出错误然后优雅的停止程序。
if err:=WaitForServer(url);err!=nil{ fmt.Fprintf(os.Stderr,"Site is down:%v\n",err) os.Exit(1) //更加方便的调用 //log.Fatalf("Site is down:%v\n",err) }
第四,在一些错误情况下,只记录错误信息然后程序继续运行。
第五,在某些罕见的情况下我们可以直接安全地忽略掉整个日志。
go语言的错误处理有特定的规律,一般都是开头一连串的检查用来返回错误,检测到失败往往都是在成功之前。
io包保证任何由文件结束引起的读取错误,始终都将会得到一个与众不同的错误——io.EOF。
package io import "errors" var EOF = errors.New("EOF") //示例 in:=bufio.NewReader(os.Stdin) for{ r,_,err:=in.ReadRune() if err==io.EOF{ break } if err != nil{ return fmt.Errorf("read failed:%v",err) } }
类似于C语言中函数指针的概念。函数变量的类型就是函数的签名,它们可以赋给变量或者传递或者从其他函数中返回。用法基本和C语言中的函数指针一样。
func square(n int) int{return n*n} func product(man int) int{return m*n} f:=square f(3) f=product //错误,类型不一致 var f func(int) int //定义一个空值函数变量 f(3) //宕机:调用空函数 //函数变量只能和nil比较
匿名函数的作用类似于java里面的匿名函数,通常一些短小不常用的函数嵌入到调用者形参里。匿名函数func关键字后面没有函数名,他的值是一个函数变量。
func squares() func() int{ var x int return func()int{ x++ return x*x } //匿名函数可以使用外层的变量 }
func main(){
f:=suares()
fmt.Println(f()) //1
fmt.Println(f()) //4
fmt.Println(f()) //9
}
函数变量类似于使用闭包方法实现的变量,Go程序员通常把函数变量称为闭包(闭包就是能够读取其他函数内部变量的函数,所以闭包可以理解成“定义在一个函数内部的函数“)。我们看到变量的生命周期不是由它的作用域决定的,由于在定义squares( )函数时指定了返回的类型是一个匿名函数,并且该匿名函数返回的类型是整型。所以在squares( )函数中定义了一个匿名函数,并且将整个匿名函数返回,匿名函数返回的是整型。在main( )函数中定义了一个变量f,该变量的类型是匿名函数,f( )表示调用执行匿名函数。最终执行完成后发现,实现了数字的累加。虽然squares()已经返回了,但是返回的值:func()还在全局变量中使用,三次调用 f(),因此返回值会保存在堆上,即使栈释放了内存资源,但func()保存在堆中,数据不会释放。
因为匿名函数(闭包),有一个很重要的特点:
它不关心这些捕获了的变量和常量是否已经超出了作用域,所以只有闭包还在使用它,这些变量就还会存在。
如果匿名函数需要进行递归,必须先声明一个变量然后将你ing函数赋值给这个变量。如果将两个步骤合成一个生命,函数字面量将不能存在于函数变量的作用域中,这样也就不能递归调用自己了。
visitALL:=func(items []string){
//...
visitAll[m[item]] //compile error: undefined:visitAll
//...
}
这个是go语言词法作用域 规则的陷阱,即使是有经验的程序员也会掉入这个陷阱。
var rmdirs []func() for _,d:=range tempDirs(){ dir:=d //这一行是必须的 os.MakeAll(dir,0755) rmdir = append(rmdir,func(){ os.RemoveAll(dir) }) } for _,rmdir:=range rmdirs{ rmdir() }
为什么需要在循环体内将循环变量赋给一个新的局部变量dir,而不是直接使用?这个原因是循环变量的作用域的规则限制。在循环里创建的所有函数变量共享相同的变量——一个可访问的存储位置,而不是一个固定的值。dir变量的值在不断地迭代更新,因此当调用清理函数的时候,dir变量已经被每一次for循环更新多次,因此dir变量的实际值是最后一次迭代时的值。
//当需要存储迭代变量的时候,我们通常声明一个同名变量去饮用它 for_,dir:=range tempDirs(){ dir:=dir //... }
在参数列表最后的类型名称之前使用省略号“...”表示声明一个变长函数,调用这个函数的时候可以传递该类型任意数目的参数。
func sum(val ...int)int{ //... } sum(1,2,3,4) //实参是slice时候,在最后一个参数后面放一个省略号 values:=[]int{1,2,3,4} sum(values...)
语法上,一个defer语句就是在普通函数或方法的调用之前加上defer关键字。无论是正常情况还是异常情况,实际的调用推迟到包含defer语句的函数结束后才执行,defer语句经常用于成对的操作,比如打开和关闭,加锁和解锁,即使是再复杂的控制流,资源在任何情况下都能够正确释放。正确使用defer语句的地方是在成功获得资源之后。
func ReadFile(filename string)([]byte,error){ f,err:=os.Open(filename) if err!=nil{ return nil,err } defer f.Close() return ReadAll(f) } //解锁一个互斥锁 var mu sync.Mutex var m=make(map[string]int) func lookup(key string) int{ mu.Lock() defer mu.Unlock() return m[key] }
延迟执行的函数在return语句之后执行,并且可以更新函数的结果变量。因为匿名函数可以捕获其外层函数作用域内的变量,所以延迟执行的匿名函数可以观察到函数的返回结果。
func double(x int)int{ return x+ x } //增加defer func double(x int)(result int){ defer func(){fmt.Printf("dounble(%d) = %d\n",result)}() //圆括号不要忘记 return x+x }
出现运行时错误时会发生宕机,正常程序执行会终止,goroutine中的所有延迟函数会执行,然后程序会异常退出并留下一条日志消息。当碰到“不可能发生”的状况时,调用内置的宕机函数是最好的处理方式。
//只有发生严重错误时才会使用宕机,否则会毫无意义,只会带来多余的检查 func Reset(x *Buffer){ if x==nil{ panic("x is nil") } x.elements = nil }
当宕机发生时,所有延迟函数以倒序执行,从栈的最上面函数开始一直返回到main函数。
退出程序通常是正确处理宕机的方式,但也有例外,在一定情况是可以进行恢复的,至少有时候可以在退出前理清当前混乱的情况。如果内置的recover函数在延迟函数的内部调用,而且这个包含defer语句的函数发生宕机,revover会终止当前的宕机状态并且返回宕机的值。函数不会从之前宕机的地方继续运行而是正常返回。如果recover在其他任何情况下运行则它没有任何效果且返回nil。
func Parse(input string)(s *Syntax,err error){ defer func(){ if p:=recover();p!=nil{ err = fmt.Error("internal error:%v",p) } }() }
这里方法的概念就是面向对象编程中的概念,go语言也支持面向对象编程,只不过没有class概念,取而代之的是用struct来完成面向对象。
我们知道,方法是属于某一个类的,因此,在go语言中,只是在函数名字前面多一个参数,这个参数把这个方法绑定到参数对应的struct上。
type Point struct{x,y float64} //普通函数 func Distance(p,q Point) float64{ return math.Hypot(q.x-p.x,q.y=p.y) }//包级别 //Point类型方法 func (p Point) Distance(q Point) float64{ return math.Hypot(q.x-p.x,q.y-p,y) }//这两个函数不会冲突,Distance相当于在struct结构里里面,和x,y在同一个作用域
附加参数p称为方法接受者,go语言中,接受者不适用特殊名字(this,self),而是我们自己选择。(最常用方法是取类型名称首字母)。
go和其他语言不一样,它可以将方法绑定到任何类型上。可以很方便的为简单类型定义附加行为。类型拥有的方法名必须是唯一的,但不同类型可以使用相同方法名。
习惯上遵循如果Point的任何一个方法使用指针接受者,那么所有的Point方法都应该使用指针接受者。当实参接收者很大的时候,为了避免复制开销,通常将这种接受者定义为指针接收者。
func (p *Point) ScaleBy(factor float64){ p.x*=factor p.y*=factor } //调用 r:=&Point{1,3} r.ScaleBy(2) p:=Point{1,2} p.ScaleBy(2) //等价于(&)p.ScaleBy(2),编译器会隐式转换,只有变量才允许这么做。 //不能够对一个不能取地址的接受者参数调用*Point方法,因为无法获取临时变量地址 Point{1,2}.ScaleBy(2) //compile error
如果实参接受者是*Point类型,调用Point.Distance方法是合法的,因为我们有办法从地址中获取Point值。
//这两个调用效果一样,编译器也会隐式转换 pptr.Distance(q) (*pptr).Distance(q)
就像一些函数允许nil指针作为实参,方法的接受者也一样。
type ColoredPoint struct{ Point Color color.RGBA }
内嵌相当于把Point里面的东西都嵌入到ColoredPoint中,包括变量和方法,有点类似于继承的意思。因此我们可以定义ColoredPoint类型,使用Point方法。
//如果形参不一致,需要显式使用它
var p:=ColoredPoint{***}
var q:=ColoredPoint{***}
p.Distance(q.Point) //显示使用
通常调用方法都是使用p.Distance()形式,但是把这两个操作分开也是可以的。方法变量类似于函数变量,选择子p.Distance可以赋予一个方法变量,即把方法绑定到接收者上,函数只需要提供实参而不需要提供接受者就能够调用。
p:=Point{1,2} q:=Point{3,4} distanceFromP:=p.Distance //方法变量 distanceFromP(q) //不需要接收者就可以调用
与方法变量相关的是方法表达式,把原来方法的接受者替换成函数的第一个形参,可以像调用平常函数一样调用方法。
distance:=Point.Distance //方法表达式 distance(p,q)
如果需要用一个值来代表多个方法,方法变量可以帮助你调用这个值所对应的方法来处理不同的接受者。
封装也被称为数据隐藏,go语言只有一种方式控制命名可见性:首字母的大小写。而在go语言中封装的单元是包而不是类型,也就是说,结构体内的字段对于同一个包都是可见的。在go语言的getter器命名时候通常将Get省略,比如Point类型中的X的getter和setter分别为X()和SetX()。
原文:https://www.cnblogs.com/yrxing/p/14156099.html