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

相关推荐
zimoyin4 小时前
Kotlin 使用 Springboot 反射执行方法并自动传参
spring boot·后端·kotlin
SomeB1oody6 小时前
【Rust自学】18.1. 能用到模式(匹配)的地方
开发语言·后端·rust
LiuYuHani6 小时前
Spring Boot面试题
java·spring boot·后端
萧月霖6 小时前
Scala语言的安全开发
开发语言·后端·golang
电脑玩家粉色男孩6 小时前
八、Spring Boot 日志详解
java·spring boot·后端
ChinaRainbowSea7 小时前
八. Spring Boot2 整合连接 Redis(超详细剖析)
java·数据库·spring boot·redis·后端·nosql
叫我DPT8 小时前
Go 中 defer 的机制
开发语言·后端·golang
我们的五年8 小时前
【Linux网络编程】:守护进程,前台进程,后台进程
linux·服务器·后端·ubuntu
谢大旭9 小时前
ASP.NET Core自定义 MIME 类型配置
后端·c#
SomeB1oody10 小时前
【Rust自学】19.5. 高级类型
开发语言·后端·设计模式·rust