背景
这两年go语言比较火,尤其是在服务器并发方面表现很好。一直想学一下,但是拖到现在。最近正好有这个机会,把go语言算是入了个门吧。
概括
学了两周,总体感觉如下:
- 和C++在一些语法有点像,但少了很多东西,比如说分号,for循环以及if语句的括号(在go语言中是可有可无的)
- 没有了引用类型,都使用指针替代,指针获取变量也是采用点分的形式
- 强类型语言,类型有点强到吐血,比C++还强
- 不支持函数重载、函数默认参数
- 编译限制太多,有未被使用的变量或者导入了未使用的库,都会编译失败
- 简化了异常处理,只有
panic
和defer
- 缩进,默认的8格缩进真是挺难看的
- go语言的资源在天朝是被墙着的
一些坑
这里记录的一些坑,都是我在使用的过程中遇到过的已经解决的或者是没有完美解决的。
1.array类型与slice类型
使用一门语言的时候数据结构肯定是一个必修课题,其中的数组又是所有语言中最基础的数据结构。
对于以下这两个类型[]int
,[3]int
起初我以为是一样的,于是写了以下这样的代码:
1 | // package, imports 略 |
结果可想而知,编译错误,原来在go中数组有两种(准确的说是无穷多种,因为[2]int
和[3]int
也是不同的类型),分别是array和slice。可以从C++的角度去理解:
- Go中的
var array [3]int
相当于是C++中的int array[3]
,数组的长度是定长的,不能扩展,但能修改数组中的值。 - Go中的
var slice []int
相当于是C++中的std::vector<int> slice
,数组长度可伸缩,可修改,可索引。
值得一提的是slice的内存分配和C++的vector也很相似,它有长度和容量两个概念,容量总是大于长度的。对于slice作append操作时,如果还有剩余容量,则直接附加,否则重新开辟一块长度是2倍的内存(如果slice本身数据特别多,可能不到2倍),把原来数据复制进去,再作附加操作。
其实slice是一个包含了array指针的数据结构,还有长度,容量这两个属性。slice赋值给另外一个slice的时候,不同的仅仅是两个slice对象在内存中的位置,而它们指向的array是相同的,仔细体会一下下面这个例子就可以理解,注释中写得很明确。
1 | func main(){ |
2.安装外部依赖包
在编译go的时候总是会被它严格的编译限制弄得很头疼,比如我在调试的时候需要fmt
这个库来打印一些东西,有时会注释某个打印,此时又不得不注释import "fmt"
,等到再打印的时候,又要把那个注释释放出来,很麻烦。stackoverflow上的这个链接跟我是同样的烦恼。答案中推荐了goimports这样一个工具,编译的时候会自动去掉不需要的依赖,使用下来还觉得挺不错的,但安装的过程确实感觉稍微有点坑。
官网说安装需要执行这样的命令:
1 | go get golang.org/x/tools/cmd/goimports |
但是http://golang.org在中国是被墙了的,当然可以从中国的某些镜像下载,但是还需要手动改一些文件夹的名字,很麻烦。因为`go get`命令,支持从官网的库中下载,也支持从github下载,所以我决定从github下载。
但不管我采用以下的哪种,都无法下载:
1 | go get https://github.com/bradfitz/goimports.git |
一通乱试,终于成功了:
1 | go get github.com/bradfitz/goimports |
在看了GOPATH
目录的文件结构后我大概终于终于明白了这种个形式的意义,因为命令中的路径其实也表达了文件夹中的路径,不管是官网的命令还是最后我采用的命令,它会下载到对应的文件夹:
1 | 执行官网的命令,会下载到以下路径 |
3.如何实现泛型
Go语言是强类型语言,简直强到不能再强,不能像javascript或者python那样随心所欲地传值,又不能像C++中那样做函数重载。所以如何实现一个泛型的接口在go语言中是一个较为麻烦的事情。
好在Go语言并没有赶尽杀绝,他还有interface{}
这样一个类型的参数,可以接受所有类型的其它参数。只不过有这样参数的接口,如果需要对类型作判断并且执行相应的操作的话还需要reflect
库的帮助。这个库很强大,接口也有很多,有时间有精力的话推荐读一下它的源码。
interface{}的泛型能力还是很强的,他可以接受所有类型的参数,刚开始我还小瞧了它,写出了这样的代码:
1 | // cannot use a (type []int) as type []interface {} in argument to array_of_interface |
结果编译出错。其实参数不需要定义成slice,直接i interface{}
就够了。如果在接口中需要判断类型,我推荐一个库的源码encoding/json
,这个库就是go语言中对json数据的序列化反序列化操作,可想而知,它需要实现大量的泛型操作(也是采用了reflect
库),贴一点关键部分的代码:
1 | // newTypeEncoder constructs an encoderFunc for a type. |
4.其它
待补充:
nil之坑
pointer receiver VS value receiver
有用的链接
当然,官方文档是非常棒的,基本上所有的问题在官网文档或者github项目的wiki中都有多提到,如果提前能够把这里的一些注意事项以及best practise看完,会非常有帮助的。只是内容比较多,另外自己动手操作以后再看文档的效果会比在还没有足够了解的情况下看文档好很多。
下面这些链接是我在学习过程中个人觉得挺有帮助的一些问答或博客,也一起分享出来吧。
- https://segmentfault.com/q/1010000003505053
- http://stackoverflow.com/questions/14025833/range-over-interface-which-stores-a-slice
- http://stackoverflow.com/questions/19560334/how-to-disable-golang-unused-import-error
- http://stackoverflow.com/questions/24878737/what-is-the-difference-between-go-get-and-go-install
- http://stackoverflow.com/questions/23542989/pointers-vs-values-in-parameters-and-return-values/23551970#23551970
- http://stackoverflow.com/questions/25025409/delete-element-in-a-slice