Go常见错误探讨1

2023年 9月 3日 102.3k 0

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

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论