go 自定义error怎么判断是否相等_go 语法常犯错误

不小心覆盖了变量

对从动态语言转过来的开发者来说,简短声明很好用,这可能会让人误会 := 是一个赋值操作符。

如果你在新的代码块中像下边这样误用了 :=,编译不会报错,但是变量不会按你的预期工作:

func main() {

x := 1

println(x) // 1

{

println(x) // 1

x := 2

println(x) // 2 // 新的 x 变量的作用域只在代码块内部

}

println(x) // 1

}

这是 Go 开发者常犯的错,而且不易被发现。

这是 Go 开发者常犯的错,而且不易被发现。

可使用 vet 工具来诊断这种变量覆盖,Go 默认不做覆盖检查,添加 -shadow 选项来启用:

> go tool vet -shadow main.go

main.go:9: declaration of "x" shadows declaration at main.go:5

注意 vet 不会报告全部被覆盖的变量,可以使用 go-nyet 来做进一步的检测:

> $GOPATH/bin/go-nyet main.go

main.go:10:3:Shadowing variable `x`

map 容量

在创建 map 类型的变量时可以指定容量,但不能像 slice 一样使用 cap() 来检测分配空间的大小:

// 错误示例

func main() {

m := make(map[string]int, 99)

println(cap(m)) // error: invalid argument m1 (type map[string]int) for cap

}

类型声明与方法

从一个现有的非 interface 类型创建新类型时,并不会继承原有的方法:

// 定义 Mutex 的自定义类型

type myMutex sync.Mutex

func main() {

var mtx myMutex

mtx.Lock()

mtx.UnLock()

}

mtx.Lock undefined (type myMutex has no field or method Lock)...

如果你需要使用原类型的方法,可将原类型以匿名字段的形式嵌到你定义的新 struct 中:

// 类型以字段形式直接嵌入

type myLocker struct {

sync.Mutex

}

func main() {

var locker myLocker

locker.Lock()

locker.Unlock()

}

range 遍历 slice 和 array 时混淆了返回值

与其他编程语言中的 for-in 、foreach 遍历语句不同,Go 中的 range 在遍历时会生成 2 个值,第一个是元素索引,第二个是元素的值:

// 错误示例

func main() {

x := []string{"a", "b", "c"}

for v := range x {

fmt.Println(v) // 1 2 3

}

}

// 正确示例

func main() {

x := []string{"a", "b", "c"}

for _, v := range x { // 使用 _ 丢弃索引

fmt.Println(v)

}

}

访问 map 中不存在的 key

和其他编程语言类似,如果访问了 map 中不存在的 key 则希望能返回 nil.

Go 则会返回元素对应数据类型的零值,比如 nil、'' 、false 和 0,取值操作总有值返回,故不能通过取出来的值来判断 key 是不是在 map 中。

检查 key 是否存在可以用 map 直接访问,检查返回的第二个参数即可:

// 错误的 key 检测方式

func main() {

x := map[string]string{"one": "2", "two": "", "three": "3"}

if v := x["two"]; v == "" {

fmt.Println("key two is no entry") // 键 two 存不存在都会返回的空字符串

}

}

// 正确示例

func main() {

x := map[string]string{"one": "2", "two": "", "three": "3"}

if _, ok := x["two"]; !ok {

fmt.Println("key two is no entry")

}

}

defer 函数的参数值

对 defer 延迟执行的函数,它的参数会在声明时候就会求出具体值,而不是在执行时才求值:

// 在 defer 函数中参数会提前求值

func main() {

var i = 1

defer fmt.Println("result: ", func() int { return i * 2 }())

i++

}

result: 2

defer 函数的执行时机

对 defer 延迟执行的函数,会在调用它的函数结束时执行,而不是在调用它的语句块结束时执行,注意区分开。

比如在一个长时间执行的函数里,内部 for 循环中使用 defer 来清理每次迭代产生的资源调用,就会出现问题.

package main

import (

"os"

"path/filepath"

"fmt"

)

// 命令行参数指定目录名

// 遍历读取目录下的文件

func main() {

if len(os.Args) != 2 {

os.Exit(1)

}

dir := os.Args[1]

start,err := os.Stat(dir)

if err != nil && !start.IsDir() {

os.Exit(2)

}

var targets []string

filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {

if err != nil{

return err

}

if !info.Mode().IsRegular() {

return nil

}

targets = append(targets,path)

return nil

})

for _,target := range targets{

func(){

f,err := os.Open(target)

if err != nil {

fmt.Println("bad target:", target, "error:", err)

return // 在匿名函数内使用 return 代替 break 即可

}

fmt.Println("ok target:", target)

defer f.Close()

}()

}

}

for 语句中的迭代变量与闭包函数

type field struct {

name string

}

func (p *field) print() {

fmt.Println(p.name)

}

// 错误示例

func main() {

data := []field{{"one"}, {"two"}, {"three"}}

for _, v := range data {

go v.print()

}

time.Sleep(3 * time.Second)

// 输出 three three three

}

// 正确示例

func main() {

data := []field{{"one"}, {"two"}, {"three"}}

for _, v := range data {

v := v

go v.print()

}

time.Sleep(3 * time.Second)

// 输出 one two three

}

// 正确示例

func main() {

data := []*field{{"one"}, {"two"}, {"three"}}

for _, v := range data { // 此时迭代值 v 是三个元素值的地址,每次 v 指向的值不同

go v.print()

}

time.Sleep(3 * time.Second)

// 输出 one two three

}

类型声明与方法

从一个现有的非 interface 类型创建新类型时,并不会继承原有的方法:

// 定义 Mutex 的自定义类型

type myMutex sync.Mutex

func main() {

var mtx myMutex

mtx.Lock()

mtx.UnLock()

}

> mtx.Lock undefined (type myMutex has no field or method Lock)...

如果你需要使用原类型的方法,可将原类型以匿名字段的形式嵌到你定义的新 struct 中:

// 类型以字段形式直接嵌入

type myLocker struct {

sync.Mutex

}

func main() {

var locker myLocker

locker.Lock()

locker.Unlock()

}

interface 类型声明也保留它的方法集:

type myLocker sync.Locker

func main() {

var locker myLocker

locker.Lock()

locker.Unlock()

}

使用指针作为方法的 receiver

只要值是可寻址的,就可以在值上直接调用指针方法。即是对一个方法,它的 receiver 是指针就足矣。

但不是所有值都是可寻址的,比如 map 类型的元素、通过 interface 引用的变量:

type data struct {

name string

}

type printer interface {

print()

}

func (p *data) print() {

fmt.Println("name: ", p.name)

}

func main() {

d1 := data{"one"}

d1.print() // d1 变量可寻址,可直接调用指针 receiver 的方法

var in printer = data{"two"}

in.print() // 类型不匹配

m := map[string]data{

"x": data{"three"},

}

m["x"].print() // m["x"] 是不可寻址的 // 变动频繁

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值