Go语言入门:函数及方法 | 青训营

函数声明

如果一组形参或返回值有相同的类型,我们不必为每个形参都写出参数类型

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()) // 调用方法
}
相关推荐
CallBack8 个月前
Typora+PicGo+阿里云OSS搭建个人图床,纵享丝滑!
前端·青训营笔记
Taonce1 年前
站在Android开发者的角度认识MQTT - 源码篇
android·青训营笔记
AB_IN1 年前
打开抖音会发生什么 | 青训营
青训营笔记
monster1231 年前
结营感受(go) | 青训营
青训营笔记
翼同学1 年前
实践记录:使用Bcrypt进行密码安全性保护和验证 | 青训营
青训营笔记
hu1hu_1 年前
Git 的正确使用姿势与最佳实践(1) | 青训营
青训营笔记
星曈1 年前
详解前端框架中的设计模式 | 青训营
青训营笔记
tuxiaobei1 年前
文件上传漏洞 Upload-lab 实践(中)| 青训营
青训营笔记
yibao1 年前
高质量编程与性能调优实战 | 青训营
青训营笔记
小金先生SG1 年前
阿里云对象存储OSS使用| 青训营
青训营笔记