专栏首页网管叨bi叨从PHPer到Gopher要经历的一些转变

从PHPer到Gopher要经历的一些转变

前言

最近在公司内部给近几个月入职的新人做了一个技术分享,因为团队发展几个业务线的研发都增加了不少新人,大部分人之前都是PHP开发,所以就跟大家一起交流了下从PHPerGopher这个过程中思维和习惯上要做的调整。文章是我这次分享的演讲稿修改整理而成的,在阅读的时候尽量先不看文章里几个例子的答案,先给出自己认为的答案再看后面的解释,这样效果会好一点,我在现场也是带着大家一起思考这些问题来慢慢推演结论的。

学习一门新编程语言时,我们总会下意识地用自己熟悉语言类比着去理解新语言,甚至用原来语言的思维套路写新语言的程序。比如PHP里数组的长度是可以动态增长的,Go里面的切片和它差不多也能自动增加长度。比如PHP里我们可以用引用参数让函数修改外部的变量的数据,那在Go我们也可以用指针类型的参数达到同样的目的,所以他们在使用上应该都差不多吧,只不过是换了种编程语言来表达。

大家在刚从PHP转到用Go语言写程序时一定要警惕这种想法,从零开始了解Go语言的基础,才能用Go语言写好程序。我们这次分享会探讨两个问题:

  • Go语言里有引用类型吗
  • Go函数的参数能够通过引用传递吗

我先不给出这两个问题的答案,咱们用例子推演出这两个问题的结果。

之后我们会说几个PHP程序员在刚开始用Go写程序时几个需要改变的编码习惯和要注意的地方。我们这次分享不涉及什么高深的技术,都是一些需要注意的细节,相信新同学们在今天的分享会后会更有信心用Go语言写好程序。

我们先从上面提到的切片和指针两个数据类型切入,探讨上面提到的两个问题。

重新认识Go里的引用类型

切片是引用类型吗

数组需要预先声明长度,有些不灵活,因此在Go代码中不经常见到它们。但是切片却无处不在。切片是一段数组的描述符,编译期间的切片是 slice 类型的,但是在运行时切片由如下的 SliceHeader 结构体表示,其中 Data 字段是指向底层数组的指针(可以理解成底层数组中存储切片索引0位置上的元素的内存地址),Len 表示切片的长度,而 Cap 表示切片的容量(最大长度)

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

切片与数组的联系可以用下面这张图表示

切片与数组的联系

很多地方提起切片都会说它是引用类型,但是在上面的SliceHeader结构体类型中我们看到切片的属性里只有Data是指向底层数组的指针,而长度和容量却不是,这在让我们在平时使用切片时如果稍不注意,尤其是带着在其他语言使用引用类型的思维定式来使用切片时程序不但不会按照预期的运行还会出现一些诡异的现象,我们通过三个例子来看一下。

func main() {
  var s []int
  for i := 1; i <= 3; i++ {
    s = append(s, i)
  }
  reverse(s)
  fmt.Println(s)
}

func reverse(s []int) {
  for i, j := 0, len(s) - 1; i < j; i++ {
    j = len(s) - (i + 1)
    s[i], s[j] = s[j], s[i]
  }
}

程序最终的输出结果是:

[3 2 1]

上面的代码段首先构建了一个切片s,一开始s的值是[1, 2, 3]。然后在reverse函数里对切片进行了反转。在main函数里打印s会发现在reverse函数外也能看到reverse对切片s的操作结果。这符合我们对引用类型的理解。

现在我们将反转函数的内部稍微修改一下,在反转切片前往里面先追加一个元素。

func reverse(s []int) {

  s = append(s, 999)

  for i, j := 0, len(s) - 1; i < j; i++ {
    j = len(s) - (i + 1)
    s[i], s[j] = s[j], s[i]
  }
}

这次程序的输出变成了:

[999 3 2]

1不见了,导致1不见的原因是当调用append时,将创建一个新切片。新切片具有新的 “长度” 属性,该属性不是指针,但Data属性仍指向同一个底层数组。因此,我们函数内的代码最终会反转切片所引用的底层数组(切片里边是不存储任何数据的),但是函数外原始切片的长度属性还是之前的长度值3,这就是造成了上面 1 被丢掉的原因。

还有比这个更诡异的情况,我们再把上面的反转函数进一步改造一下,多添加几个元素:

func reverse(s []int) {
  s = append(s, 999, 1000, 1001)
  for i, j := 0, len(s)-1; i < j; i++ {
    j = len(s) - (i + 1)
    s[i], s[j] = s[j], s[i]
  }
}

这次程序的输出变成了:

[1, 2, 3]

在反转函数内对切片的更改在函数外又看不见了,这隐隐约约让我们感觉,切片并不像其他语言的引用类型那样是按照地址传递的。

如前所述,当我们调用append时,会创建一个新的切片。在第二个例子中,反转函数里的新切片仍指向同一底层数组,因为数组有足够的容量来添加新元素,因此在函数内对底层数组的更改也能在函数外体现,但是这个例子中,在reverse函数里向切片添加了三个元素,而我们的切片没有足够的容量。于是系统分配了一个新数组,让切片指向该数组。这时函数内外的切片指向的不同的底层数组,所以在函数内对切片做的任何更改都不会再影响我们的初始切片。

上面两个例子的切片对应的底层数组的变化如下:

切片底层数组重新分配

从上面几个切片的例子来看切片好像不是什么引用类型,根据切片头结构里的Data指针推测,指针有可能也不是引用类型,指针参数也是通过值传递给函数内部的。

指针是引用类型吗

我们还是通过一个例子来验证上面的猜测。

本文分享自微信公众号 - 网管叨bi叨(kevin_tech),作者:KevinYan11

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-05-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Go 切片使用绕坑指南

    不知道大家有没有发现在一个函数内部对切片参数进行了排序后也会改变函数外部原来的切片中元素的顺序,但是在函数内向切片增加了元素后在函数外的原切片却没有新增元素,更...

    KevinYan
  • Go 语言之父详述切片与其他编程语言数组的不同

    切片是Go 语言核心的数据结构,然而刚接触 Go 的程序员经常在切片的工作方式和行为表现上被绊倒。比如,明明说切片是引用类型但在函数内对其做的更改有时候却保留不...

    KevinYan
  • 如何在Go中使用切片容量和长度

    Run it on the Go Playground → https://play.golang.org/p/7PgUqBdZ6Z

    KevinYan
  • Go 切片使用绕坑指南

    不知道大家有没有发现在一个函数内部对切片参数进行了排序后也会改变函数外部原来的切片中元素的顺序,但是在函数内向切片增加了元素后在函数外的原切片却没有新增元素,更...

    KevinYan
  • 手把手golang基础教程——数组与切片

    今天是golang专题的第五篇,这一篇我们将会了解golang中的数组和切片的使用。

    TechFlow-承志
  • 给可变参数函数传入切片 转

    有一个可以直接将切片传入可变参数函数的语法糖,你可以在在切片后加上 ... 后缀。如果这样做,切片将直接传入函数,不再创建新的切片

    双面人
  • 开发人员如何提高效率和速度——实践检验真理

    工作效率是各行各业都非常重视的一个问题,对于一个高新技术企业来说,更是重中之重;代码开发是一项细致的工作,不仅要求开发人员有过硬的技术,更要有认真的态度,在本文...

    用户1289394
  • xss渗透试验(1)

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/detai...

    血狼
  • 出不了门的日子,我选择在 GitHub 上快乐的打游戏

    2020 年的开年因为一些大家都知道的原因,有些不顺,但还是要捏捏自己的脸蛋儿,微笑的面对,毕竟日子还是要过下去...

    Rocky0429
  • 出不了门的日子,自闭的我选择在 GitHub 上快乐的打游戏

    2020 年的开年因为一些大家都知道的原因,有些不顺,但还是要捏捏自己的脸蛋儿,微笑的面对,毕竟日子还是要过下去...

    帅地

扫码关注云+社区

领取腾讯云代金券