03月31, 2019

Thinking in A Tour of Go

记录一下在看A Tour of Go时遇到的一些问题和思考。


首先是几个C/C++选手初用Go需要注意的问题。

  • int uint uintptr是C的long,即32位机器则是4字节,64位机器则是8字节。
  • switch是强制按从上往下顺序来判断。因此也就不可能出现像C那样编译器实现二分查找做switch减少判断次数的情况。且Go的switch不需要break,因此当两个case有相同行为时,要把两个case写到一行,条件之间用逗号隔开。
  • 指针不需要解引用,这个学了Go的肯定都印象深刻,只是在写代码时需要多习惯习惯。
  • 主线程退出,整个程序直接就退出了,不论Goroutine有没有执行完。
  • Go的运算符优先级与C++不同。Go的移位运算符比加减优先级高,位运算符比比较运算符优先级高。而C++正好相反。
  • 允许对code block内定义的局部变量取地址后在code block外使用。编译器会自动分析出这样的变量,在内存分配时将其分配至堆而不是栈。
  • 字面量计算完成后才进行赋值。因此可以var x uint64 = 1<<64 - 1
  • 可变参数的行为与实现,请参考文档
  • string不可以修改,必须转换成byte或者rune切片后才能修改,而且改的也不是之前那个string,改后的字符串只能通过切片来使用。
  • 按位取非是^而不是~

下面是一些思考:

  • 所谓闭包,把它理解成C++的仿函数就可以了。
  • Go实际上通过interface实现类和抽象,通过interface或struct的内嵌(可以匿名)实现继承。但这种继承只是看起来像继承,即你可以直接使用继承的那个类型的方法,但实际上本质仍然是内嵌的变量。这种内嵌的struct可以加*,本质上也还是个指针。
  • 利用chan可实现线程同步(信号量),如果线程数量不确定(例如存在递归创建线程的情况),可以使用sync包中的WaitGroup。
  • Go通过panic、recover和defer实现异常的处理。C++是throw try catch。

https://tour.golang.org/methods/20

package main

import (
    "fmt"
)

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string {
    return fmt.Sprint("cannot Sqrt negative number: ", float64(e))
}

func Sqrt(x float64) (float64, error) {
    if x < 0 {
        return 0, ErrNegativeSqrt(x)
    }
    z := x
    for i := 0; i < 50; i++ {
        z -= (z*z - x) / (2 * z)
    }
    return z, nil
}

func main() {
    fmt.Println(Sqrt(2))
    fmt.Println(Sqrt(-2))
}

为什么必须强制转换e,否则就会陷入无限循环?

因为它会调用Error方法去转换成字符串,那如果我们手动实现一个String方法呢?

试了一下,还是不行。但是按通常的思考来看,如果有String的话应该是会先调用String的。实际上在Go里,是Error比String优先。(参考


https://tour.golang.org/methods/25

为什么这样的代码不能执行?

package main

import (
    "golang.org/x/tour/pic"
    "image"
    "image/color"
)

type Image struct{}

func (i *Image) ColorModel() color.Model {
    return color.RGBAModel
}
func (i *Image) Bounds() image.Rectangle {
    return image.Rect(0, 0, 255, 255)
}
func (i *Image) At(x, y int) color.Color {
    v := uint8((x + y) / 2)
    return color.RGBA{v, v, 255, 255}
}

func main() {
    m := Image{}
    pic.ShowImage(m)
}

错误信息如下:

./compile73.go:24:15: cannot use m (type Image) as type image.Image in argument to pic.ShowImage:
    Image does not implement image.Image (At method has pointer receiver)

我在实现方法的时候,习惯性地使用指针,因为这样实现的方法不仅可以正常用,还可以能够修改对象的值,而且可以防止对象的复制。但这里不能这么用。

这里需要注意,interface的方法与一般的类型的方法不同,它严格要求类型一致,这是规定。因此,要么i *Image*全去掉,要么在pic.ShowImage时传&m


其他:

不同类型的字面量:

a := [...]string   {Enone: "no error", Eio: "Eio", Einval: "invalid argument"}
s := []string      {Enone: "no error", Eio: "Eio", Einval: "invalid argument"}
m := map[int]string{Enone: "no error", Eio: "Eio", Einval: "invalid argument"}

分别是数组、切片和映射。


一些语法:

  • 可以用反引号`括起来字符串,引号和反斜杠还有换行在里面就不用转义了。就是个heredoc。
  • 枚举类型通过const与iota关键字来实现。
  • 可以为函数实现方法。
  • break 标号可以跳出多重循环,实际上也就是C/C++的goto 标号,没什么区别。

Go的一些特殊特性?

  • 库大多以代码形式下载使用,一起编译成一个可执行程序。
  • Go依赖可以用vendor,把所有依赖的代码都放在当前项目的vendor目录下,如果有相应的vendor,go get就不会把代码下到GOPATH里了。

本文链接:https://debug.fanzheng.org/post/thinking-in-a-tour-of-go.html

-- EOF --

Comments

评论加载中...

注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。