1.1 函数声明
每个函数声明都包含一个名字、一个形参列表、一个可选的返回列表以及函数体:
func name(parameter-list) (result-list) {
body
}
形参列表指定了一组变量的参数名和参数类型,这些局部变量由调用者提供的实参传递而来,返回列表指定了函数返回值的类型。当函数返回一个未命名的返回值或者没有返回值时,返回列表的括号可以省略。
返回值也可以命名,这时每一个命名的返回值会声明一个局部变量,并根据变量类型初始化为零值。当函数存在返回列表时,必须显示以return结束。
函数的类型称为函数签名,当两个函数拥有相同的形参列表和返回列表时,认为两个函数的类型或签名是相同的。Go语言中没有默认参数值的概念,必须在调用中对形参一一赋值。
形参变量都是函数的局部变量,初始值由调用者提供的实参传递。函数形参与命名返回值都属于函数最外层作用域的局部变量。实参按值传递,函数接收到的是每个实参的副本,修改形参变量并不会影响调用者提供的实参。如果提供的实参是引用类型,例如指针、slice、map、函数或者通道,那么修改形参变量会间接修改实参变量。
Go语言实现了可变长度的栈,可达到1GB左右。
一个函数如果有命名的返回值,可以省略return语句的操作数,成为裸返回。
1.2 错误处理
与其他语言不同,Go通过使用普通的值而非异常来报告错误。当函数调用发生错误时,如果错误只有一种情况,习惯将错误值作为最后一个结果返回,通常错误被设置为布尔类型。当出现错误原因多样的情况,错误类型往往是内置的error接口类型。当函数返回一个非空错误时,它其他的结果都应忽略。
Go这样做的原因是异常会陷入带有错误消息的控制流去处理,经常会导致预期外的结果。错误以难以理解的栈跟踪信息报告给用户,这些信息大都是关于程序结构方面而不是简单明了的错误信息。Go使用简单的控制流来处理错误,简单明了。
1.3 函数变量
Go语言中函数是一等公民:像其他值一样,函数变量也有类型,可以赋给变量或者传递或者从其他函数中返回。
func square(n int) int {return n * n}
func negative(n int) int {return -n}
func product(m, n int) int {return m * n}
f := square
f(3) //9
f = product //编译错误,类型不匹配
函数类型的零值是nil(空值),调用一个空的函数变量将导致宕机。
var f func(int) int
f(3) //宕机,调用空函数
函数不可比较,仅能与nil比较。
1.4 匿名函数
命名函数只能在包级别的作用域声明,但我们能使用函数字面量在任何表达式指定函数变量,函数字面量在func关键字后没有名称,也称为匿名函数。
string.map(func(r rune) rune {return r+1}, "HAL-9000")
匿名函数能够获取到整个词法环境,里层的函数可以使用外层函数的变量:
func squuares() func() int {
var x int
return func() int {
x++
return x * x
}
}
这里函数squuares返回另外一个函数,每次调用squuares都会递增x并平方。里层匿名函数能够获取和更新外层变量,函数变量也称为闭包。
1.5变长函数
变长函数被调用的时候可以有可变的参数个数,在参数列表最后的类型名称之前使用省略号"..."表示声明一个变长函数,调用这个函数的时候可以传递该类型任意数量的参数。
func sum(vals ...int) int {
total := 0
for _, val := range vals {
total += val
}
return total
}
变长参数是一个slice,调用时候可以提供任何数量的参数。
interface{}类型意味着可以接受任何值。
1.6 延迟函数调用
延迟函数调用是在普调函数调用前加上关键字defer,函数和参数表达式会在语句执行时求值,但无论是正常情况还是不正常情况下,实际的调用都推迟到包含defer语句的函数结束后才执行,执行的时候以调用defer语句顺序的倒序进行。延迟函数在return语句之后执行,并且可以更新函数的结果变量。
1.7 宕机
Go语言运行时检测到运行时错误,例如数组越界或者空指针引用等,会发生宕机。程序发生宕机时,正常的程序执行会终止,goroutine中的延迟函数会立即执行,然后程序异常退出并留下一条日志消息。日志消息包括宕机值,每一个goroutine都会显示一个函数调用的栈跟踪消息。可以直接调用宕机函数来宕机。
尽管go语言中的宕机机制类似于其他语言的异常,但是使用场景却不相同,由于宕机会引起程序异常退出,因此只有在发生严重错误时才使用宕机。当宕机发生时,所有延迟函数倒序执行。
1.8 恢复
退出程序通常是正确处理宕机的方式,但是一定情况下宕机是可以恢复的。如果内置的recover函数在延迟函数内部调用,并且这个包含defer语句的函数发生宕机,recover会终止当前的宕机状态并且返回宕机的值。函数不会从宕机的位置继续运行而是正常返回。
func Parse(input string) (s *Syntax, err error) {
defer func() {
if p := recover(); p != nil {
......
}
}
}
2.1 方法
面向对象的编程思想在工业领域占据了主导位置,Go语言也不例外的支持这种思想。
2.1.1 方法声明
方法声明与普通函数的声明类似,只是在函数名前多一个参数,这个参数把这个方法绑定到这个参数对应的类型上。
type Point struct{X, Y float64}
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
参数p称为方法的接收者,用来描述主调方法就像对象发送消息。Go语言中,接收者不使用特殊名(this或者self),惯例使用类型名称的首字母。每一个类型有自己的命名空间,同类型的方法与字段不能同名。
Go与许多面向对象语言不同,方法可以绑定到任何类型上,同一个包下任何类型都可以声明方法,只要它的类型既不是指针类型也不是接口类型。
命名类型与指向它们的指针是唯一可以出现再接收者声明处的类型,不允许本身是指针的类型进行方法声明:
type P *int
func (P) f() {} //编译错误
如果接收者是Point类型变量,但方法要求一个Point类型接收者,编译器会对变量做取地址的隐式转换,不能对一个不能取地址的接收者调用Point方法:
Point{1, 2}.ScaleBy(2) //编译错误,无法获得Point字面量地址
实参接收者 | 形参接收者 | 可否调用方法 |
---|---|---|
T | T | Y |
*T | * T | Y |
T | *T | Y |
*T | T | Y |
2.2 结构体内嵌
type Point struct{X, Y float64}
type ColoredPoint struct {
Point
Color color.RGBA
}
内嵌结构体能够使我们通过类型为ColoredPoint类型的接收者调用内嵌Point的方法,即使在ColoredPoint没有声明过该方法的情况下。
匿名字段类型可以是指向命名类型的指针,这个时候,字段和方法间接来自于所指向的对象。当编译器处理选择子的时候,它会先查找直接声明的方法,之后再从内嵌字段的方法中查找,以此类推。当同一个查找级别中有同名方法时,编译器会报错。