函数声明
如果一组形参或返回值有相同的类型,我们不必为每个形参都写出参数类型
go
func f(i, j, k int, s, t string) { /* ... */ }
func f(i int, j int, k int, s string, t string) { /* ... */ }
函数名以小写开头,则它只能在它所声明的包内使用;如果以大写开头,该函数就可以被其他包调用
递归
多返回值
许多标准库中的函数返回2个值:期望得到的返回值、函数出错时的错误信息
调用多返回值函数时,返回给调用者的是一组值,调用者必须显式的将这些值分配给变量
如果一个函数所有的返回值都有显式的变量名,那么该函数的return语句可以省略操作数,这称之为bare return
go
func CountWordsAndImages(url string) (words, images int, err error) {
resp, err := http.Get(url)
if err != nil {
return
}
doc, err := html.Parse(resp.Body)
resp.Body.Close()
if err != nil {
err = fmt.Errorf("parsing HTML: %s", err)
return
}
words, images = countWordsAndImages(doc)
return
}
func countWordsAndImages(n *html.Node) (words, images int) { /* ... */ }
如果没有仔细的审查代码,很难发现前2处return等价于 return 0,0,err(Go会将返回值 words和images在函数体的开始处,根据它们的类型,将其初始化为0),最后一处return等价于 return words, image, nil
错误
panic是来自被调用函数的信号,表示发生了某个已知的bug
对于那些将运行失败看作是预期结果的函数,它们会返回一个额外的返回值,通常是最后一个,来传递错误信息
如果导致失败的原因只有一个,额外的返回值可以是一个布尔值,通常被命名为ok
通常,导致失败的原因不止一种,因此,额外的返回值不再是简单的布尔类型,而是error类型(内置的error是接口类型)
错误处理策略
传播错误,这意味着函数中某个子程序的失败,会变成该函数的失败
如果错误的发生是偶然性的,或由不可预知的问题导致的,可重新尝试失败的操作。在重试时,限制重试的时间间隔或重试的次数,防止无限制的重试
如果错误发生后,程序无法继续运行,可输出错误信息并结束程序。需要注意的是,这种策略只应在main中执行。对库函数而言,应仅向上传播错误,除非该错误意味着程序内部包含不一致性,即遇到了bug,才能在库函数中结束程序
输出错误信息,不需要中断程序的运行。可以通过log包提供函数。或者标准错误流输出错误信息
可以直接忽略掉错误
在Go中,错误处理有一套独特的编码风格。检查某个子函数是否失败后,通常将处理失败的逻辑代码放在处理成功的代码之前。如果某个错误会导致函数返回,那么成功时的逻辑代码不应放在else语句块中,而应直接放在函数体中
首先是一系列的初始检查,防止错误发生,之后是函数的实际逻辑
文件结尾错误(EOF)
io包保证任何由文件结束引起的读取失败都返回同一个错误------io.EOF
可变参数
参数数量可变的函数称为可变参数函数。在声明可变参数函数时,需要在参数列表的最后一个参数类型之前加上省略符号"...",这表示该函数会接收任意数量的该类型参数
原始参数已经是切片类型,只需在最后一个参数后加上省略符
go
func sum(vals ...int) int {
total := 0
for _, val := range vals { // vals被看作是类型为[] int的切片
total += val
}
return total
}
fmt.Println(sum()) // "0"
fmt.Println(sum(3)) // "3"
fmt.Println(sum(1, 2, 3, 4)) // "10"
// 等价于
values := []int{1, 2, 3, 4}
fmt.Println(sum(values...)) // "10"
Deferred函数
defer语句经常被用于处理成对的操作,如打开、关闭、连接、断开连接、加锁、释放锁。通过defer机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放。释放资源的defer应该直接跟在请求资源的语句后
go
func title(url string) error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
ct := resp.Header.Get("Content-Type")
if ct != "text/html" && !strings.HasPrefix(ct,"text/html;") {
return fmt.Errorf("%s has type %s, not text/html",url, ct)
}
doc, err := html.Parse(resp.Body)
if err != nil {
return fmt.Errorf("parsing %s as HTML: %v", url,err)
}
// ...print doc's title element...
return nil
}
Panic异常
一般而言,当panic异常发生时,程序会中断运行,并立即执行在该goroutine中被延迟的函数(defer 机制)。随后,程序崩溃并输出日志信息
Recover捕获异常
如果在deferred函数中调用了内置函数recover,并且定义该defer语句的函数发生了panic异常,recover会使程序从panic中恢复,并返回panic value。导致panic异常的函数不会继续运行,但能正常返回。在未发生panic时调用recover,recover会返回nil
方法
面向对象编程(OOP):封装和组合
方法的声明和函数类似,他们的区别是:方法在定义的时候,会在 func 和方法名之间增加一个参数,这个参数就是接收者,这样我们定义的这个方法就和接收者绑定在了一起,称之为这个接收者的方法
scss
func (接收者名称 接收者类型) 函数名称 (形参列表) (返回值列表) {
逻辑语句;
}
Go语言中的函数可以和任何类型绑定, 但是一般用于和结构体绑定
使用类型的变量即可调用方法,类型变量和方法之前是一个 . 操作符,表示要调用这个类型变量的某个方法的意思
在调用指针接收者方法的时候,使用的也是一个值的变量,并不是一个指针,也可使用指针(&p).modify() // 指针接收者,修改有效
有两种类型的接收者:值接收者和指针接收者
go
type person struct {
name string
}
// func和方法名之间增加的参数(p person),这个就是接收者。即类型person有了一个String方法
func (p person) String() string{ // 值接受者
return "the person name is "+p.name
}
func (p *person) modify(){ // 指针接受者
p.name = "李四"
}
func main() {
p:=person{name:"张三"}
p.modify() // 在调用指针接收者方法的时候,使用的也是一个值的变量,并不是一个指针,也可使用指针
fmt.Println(p.String()) // 调用方法
}