Go常见错误探讨1

Go常见错误探讨1

关键词:

range,多值返回,类型嵌入,接收器,slice与map的nil

range

range语法可以用于遍历slice,map等数据结构,当用于slice时,其使用方法有如下几种:

go 复制代码
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语言是一门支持多值返回的语言,比如下面这个函数:

go 复制代码
func doRiskyOperation() (Result, error) { ... }

返回了一个类型为Result的变量,和一个error变量;

那如何接受这类函数的值呢?

最标准的就是

go 复制代码
s1,s2 :=do()

假如我需要直接将返回的多个值直接打印,我可以:

go 复制代码
fmt.Println(doRiskyOperation())

但是写成下面这样不被允许:

go 复制代码
fmt.Println("Output: ", doRiskyOperation())

在此,我们拓展开来,看看 = 和 := 这两个符号带给开发者的陷阱:

go 复制代码
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这个变量单独声明即可

go 复制代码
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中的继承不同,是在外部结构体上定义这些成员,并将这些成员的调用或访问委托给内部对象的语法糖,以此达到避免显示调用内部成员的目的。

以下是一段简单的式样代码:

go 复制代码
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"

在类型嵌入中,也要特别关注类型断言,比如下面这段代码:

go 复制代码
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不可以。

接收器

以下是值/指针接收器

go 复制代码
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的代码:

go 复制代码
var slice []int
fmt.Println(len(slice)) 	// prints 0
slice = append(slice, 42) 	// okay
fmt.Println(len(slice)) 	// prints 1

如你所见,这段代码申明了一个slice变量,此时为nil,随后用append方法给其增加一个元素,从结果来看符合我们的预期。

再来看下面一段关于map的代码:

go 复制代码
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值

go 复制代码
tmp :=make(map[string]int,1)
fmt.Println(len(tmp))   //0
tmp["Halo"]=1
fmt.Println(len(tmp))   //1

全文终。

注:

参考文章:Avoiding Pitfalls in Go

相关推荐
Estar.Lee7 分钟前
查手机号归属地免费API接口教程
android·网络·后端·网络协议·tcp/ip·oneapi
2401_857610032 小时前
SpringBoot社团管理:安全与维护
spring boot·后端·安全
凌冰_2 小时前
IDEA2023 SpringBoot整合MyBatis(三)
spring boot·后端·mybatis
码农飞飞2 小时前
深入理解Rust的模式匹配
开发语言·后端·rust·模式匹配·解构·结构体和枚举
一个小坑货2 小时前
Rust 的简介
开发语言·后端·rust
monkey_meng3 小时前
【遵守孤儿规则的External trait pattern】
开发语言·后端·rust
Estar.Lee3 小时前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
新知图书4 小时前
Rust编程与项目实战-模块std::thread(之一)
开发语言·后端·rust
盛夏绽放4 小时前
Node.js 和 Socket.IO 实现实时通信
前端·后端·websocket·node.js
Ares-Wang4 小时前
Asp.net Core Hosted Service(托管服务) Timer (定时任务)
后端·asp.net