Go常见错误探讨1
关键词:
range,多值返回,类型嵌入,接收器,slice与map的nil
range
range语法可以用于遍历slice,map等数据结构,当用于slice时,其使用方法有如下几种:
for _,val :=range slice;
for idx,val :=range slice;
for idx,_ :=range slice;
for idx :=range slice;
你可以把range返回的元素个数当成1,那么这个变量就代表了slice中的下标,也就是说,按照这种方式,循环打印idx,你得到的结果是0,1,2,…,len(slice)-1
你也可以把range返回的元素个数当成2,那么第一个变量为slice中的下标,第二个变量为具体的值;
假如你认为元素个数为2,那么你可以用_来忽略你用不到的值,毕竟在Go语言中,一个没被使用的变量是会让编译器报错的,下划线可以解决这个问题;
多值返回
Go语言是一门支持多值返回的语言,比如下面这个函数:
func doRiskyOperation() (Result, error) { … }
返回了一个类型为Result的变量,和一个error变量;
那如何接受这类函数的值呢?
最标准的就是
s1,s2 :=do()
假如我需要直接将返回的多个值直接打印,我可以:
fmt.Println(doRiskyOperation())
但是写成下面这样不被允许:
fmt.Println("Output: ", doRiskyOperation())
在此,我们拓展开来,看看 = 和 := 这两个符号带给开发者的陷阱:
type Transformation func(Node) (Node, error)
func applyTransformations(n Node, ts []Transformation) ([]Node, error) {
var steps []Node
for _, t := range ts {
n, err := t(n)
if err != nil {
return nil, err
}
steps = append(steps, n)
}
return steps, nil
}
这段代码的目的是为了将ts中每一个transformation的结果加入到steps数组并返回,编译也是能通过的,但实际是达不到我们的效果的,原因就出在这个 :=上,它使得左边两个变量都是新的变量,其中的n会覆盖掉这个函数传入的参数n,同时每次迭代之后这个n就会失效
那我们该如何解决这个问题呢?
我们只要将err这个变量单独声明即可
type Transformation func(Node) (Node, error)
func applyTransformations(n Node, ts []Transformation) ([]Node, error) {
var steps
for _, t := range ts {
var err error
n, err = t(n)
if err != nil {
return nil, err
}
steps = append(steps, n)
}
return steps, nil
}
类型嵌入
当一个结构体内嵌了一个类型时,它继承了这个类型的所有方法,这种继承与oop中的继承不同,是在外部结构体上定义这些成员,并将这些成员的调用或访问委托给内部对象的语法糖,以此达到避免显示调用内部成员的目的。
以下是一段简单的式样代码:
type Nameable interface {
Name() string
}
type Node struct{
name string
}
func (n Node) Name() string {
return n.name
}
type Wrapper struct {
Node
}
tim := Wrapper{Node{"Tim"}}
// The expressions below are equivalent
tim.Name() // prints "Tim"
tim.Node.Name() // prints "Tim"
// As are these
tim.name // prints "Tim"
tim.Node.name // prints "Tim"
在类型嵌入中,也要特别关注类型断言,比如下面这段代码:
func Print(n Nameable) {
wrapper, ok := n.(Wrapper)
if ok {
fmt.Printf("Wrapper: %s", n.Name())
} else {
fmt.Printf(n.Name())
}
}
Print(tim.Node) // prints "Tim"
Print(tim) // prints "Wrapper: Tim"
tim可以被断言为一个Wrapper,但是tim.Node不可以。
接收器
以下是值/指针接收器
func (n Node) Name() string {...} // Value Receiver
func (n *Node) Name() string {...} // Pointer Receiver
方法是定义在接收器的类型上的,具体而言,分为两种情况:
1.接收器为值类型
以上述代码为例,此时Name这个方法是被Node类型和*Node类型同时实现,这两种类型都可以调用这个Name方法;
2.接收器为指针类型
以上述代码为例,此时Name这个方法仅仅被*Node这个类型实现,Node类型没有这个方法;
但是,即使如此,Node类型也可以调用这个方法,因为在具体调用时Node会被自动转换为*Node;
最后注意一下,当接收器为指针类型时,可以改变接收器内部元素的值,反之不可以,毕竟,Go是按值传递的。
Nil
nil在go语言中,不光可以代表空指针,slice,map,interface等都可以
我们具体来看一下slice和map中nil有什么陷阱
先看下一段关于slice的代码:
var slice []int
fmt.Println(len(slice)) // prints 0
slice = append(slice, 42) // okay
fmt.Println(len(slice)) // prints 1
如你所见,这段代码申明了一个slice变量,此时为nil,随后用append方法给其增加一个元素,从结果来看符合我们的预期。
再来看下面一段关于map的代码:
var gpa map[string]float
fmt.Println(len(gpa)) // prints 0
m["Neal"] = 4.0 // nil panic!
这段代码申明了一个值为nil的map变量,随后给其增加了一个元素,但结果居然是nil panic;
为什么给slice的nil增加一个元素可以,给mao就不行呢?
实际上,直接对nil进行操作都是不可以的,nil在go中是immutable(不可变的),slice那段代码,本质上是是因为append创建了一个新的数组,不再是在nil上进行操作,然后将这个新数组的值返回给slice,使得其变为我们看到的结果。而map那段代码,并没有这样的过程,于是便会出现panic。
我们可以使用make来初始化。防止nil值
tmp :=make(map[string]int,1)
fmt.Println(len(tmp)) //0
tmp["Halo"]=1
fmt.Println(len(tmp)) //1
全文终。
注:
参考文章:Avoiding Pitfalls in Go