记录一下在看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里了。
Comments
注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。