[关闭]
@delight 2016-01-02T07:57:51.000000Z 字数 4569 阅读 1611

Go语言学习

go


其实前段时间我已经试着学习Go了,但是由于没啥项目需求所以很快就忘了,这次借着要写heka插件的机会重新学习一下,巩固一下基础知识。

准备工作

  1. 安装GoLang,直接用官网的安装指南就行;
  2. 设置环境变量GOPATH,这个是用默认包的存放位置,用go get安装的包会存放在这个位置。在~/.zshrc~/.bashrc里面加入export GOPATH=~/.go,然后在PATH里面加入GOPATH/bin即可;
  3. 设置代理。go get命令下载必定被墙,使用git config --global http.proxy "xxxx:oooo"设置代理方可使用,也可以使用http_proxy=xxxx:oooo go get这个格式,或者在bashrc里面加个alias
  4. IDE,个人使用Sublime + GoSublime挺方便的;
  5. 官方教程,建议下载到本地运行,速度更快;
  6. 交互式命令行:gore(go get -u github.com/motemen/gore),本地调试使用;

基础部分

基本语法

关键字

  1. 打头的package xxx,类似java,import可以用括号打包;
  2. 类型在变量名后,这种奇特的声明方式虽然有篇blog来解释,但总而言之是扯淡的;
  3. 连续多个变量同类型可省略前面的只保留最后一个;
  4. 类似python的多值返回(但是python本质是一个tuple);
  5. 裸返回会返回当前所有的变量值,如果给返回值命名了,就不必在函数体中声明这些变量;
  6. var name int是典型的声明变量格式,自动推导类型的语法是name := 0(但是这个语法只能在函数体里面用,外面必须用var声明)。可以在一行给多个变量赋值(类似python的解包);
  7. 基本类型,和c++类似,包括bool, int, uint, byte(uint8),rune(int32), float32, float64, uintptr, complex64, complex128, string,注意么有double,类似其他GC语言,所有类型会被自动化为零值;
  8. Go没有隐式类型转换,所有类型之间必须显式转换。注意intstring之间不能互转,可以用strconv中的ItoaAtoi来完成;
  9. 常量使用const关键字声明,常量只能是基础类型,且不能用:=声明。常量的实际类型由上下文决定,数值常量本身是高精度的;
  10. 和C语言一样,单引号表示字符(byte),双引号表示字符串(话说这一套早就过时了啊。。。)

语句

  1. 循环只有for语句,且不需要括号(后面的都不需要),基本格式还是类似c的for i := 0; i < 10; ++i,这种,后面必须跟大括号,且大括号必须和for在同一行…
  2. 如果省略前后前后的分号,for就成了while;如果全部省略,裸的for代表死循环;
  3. if类似,不要括号,花括号必须;而且if也可以在分号前声明一个变量,作用域仅限于花括号以及后面跟着的else里面;
  4. switch语句,好吧,和上面也类似。有个有趣的地方是,默认自动终止,除非使用fallthrough,和C中的默认自动向下,除非手动break相反;switch也可以直接用空语句,条件比较复杂时使用可以让代码看起来更加整洁;
  5. defer语句,这是Go的特色语句了…defer是在函数返回后再执行,其本质时压栈,所以弹出顺序与defer的顺序相反;

指针

  1. 虽然Go是一门GC语言,但是仍然拥有指针。*T表示指向类型T的指针,取地址仍然使用&。不过与C不一样的是,不允许指针运算;
  2. 和C一样,拥有struct,而且蛋疼的是,也只能拥有字段(和C一样)。结构体通过指针访问字段也是使用.符号(没有了->符号);
  3. 使用{}进行结构体初始化,如
  1. type Point struct {
  2. X, Y float32
  3. }
  4. var (
  5. a = Point{X: 10}
  6. b = Point{1, 1}
  7. c = Point{}
  8. p = &Point{1, 2}
  9. )
  10. fmt.Println(p.X)

虽然感觉有点奇怪,不过和C++11后的初始化列表其实挺像的。


数组

  1. 声明方式: var a [10]int,这语法也是醉了。和C一样,数组不能动态扩张;
  2. 使用slice代替数组,声明方式: a = make([]int, 0, 5),第二个参数表示长度(len),第三个参数表示容量(cap)。类似python中的list,可以切片;注意,如果声明var []a那么a==nil是成立的;
  3. 可以通过append往slice中添加元素,类似C++中的vector可以自动扩展长度;
  4. range关键字(注意这货不是函数。。)用来对slice进行循环,格式是for i, v := range a;

字典

  1. map现在也是新兴语言的标配了,mapslice一样,必须通过make创建,语法是m := make(map[string]int),[]中的是键的类型,后面跟着的是值的类型。初始化语法神马的和struct类似;
  2. 删除元素使用delete关键字;检测存在使用双赋值:a, ok = m['test'],如果存在则ok为true,否则为false

函数

  1. 函数被提到第一公民的位置,和javascript里面的语法很像,当然,除了强类型声明很麻烦以外;
  2. 函数的闭包与js类似,内嵌函数引用的是各自的闭包(其实有点像C中的static局部变量);

方法

  1. 虽然Go里面没有类,但是可以声明struct关联的方法,虽然语法非常别扭…例如(接着上面的Point
  1. func (p1 *Point) distance(p2 *Point) int{
  2. //...
  3. }

方法接受者位置在func关键字和函数名之间,呃,其实和C++的外置方法声明还是有点像的…
2. 值的注意的是,不仅仅是struct,可以通过这种声明向本包内任意非内置类型注入方法,甚至可以通过type声明别称后向别称的内置类型进行注入;
3. 方法接受者可以是指针,也可以不是,当然只有指针才能改变元素的实际值;


结构体

  1. struct从语法上来讲和C基本是一样的;
  2. 可以在字段后面添加字符串,表示tag,在反射的时候用;

接口

  1. 虽然没有类,但是由接口。关键字interface声明一种接口:
  1. type Flyable struct{
  2. Fly();
  3. }

上面Flyable声明了一个接口,拥有Fly方法. 这样后面假设我给pig加上fly方法,那么变量var item Flyable就可以被赋值为item = &pig{}
这里值得注意的是,这里的接口实现本质是隐式的,或者可以说是duckable的,pythoner对此应该深有理解:)
2. Stringers是一个常见的接口,类似python中的__str__或者java中的toString,它只需要实现String方法;
3. Go里面没有异常,仍然使用错误。error是一个接口,只有一个方法Error() string,通常函数会返回一个error,放在第二个位置,如果其不为nil则说明出了错误;
4. 其他常见接口包括io.Reader,表示从数据流结尾读取;http.Handler表示处理HTTP请求的服务器;image.Image表明一个图像的接口;


并发

  1. goroutine是Go运行时的轻量级线程,在方法名前加go就在另一个线程中同步执行了;
  2. channel是有类型的管道,可以使用<-操作符对其发送或接受值,使用make(chan int, 100)创建一个intchannel,第二个参数表示缓冲区长度,也可以不带,表示完全无缓冲;
  3. close一个channel表示不再发送数据(只有发送者可以关闭),向已经closechannel发送数据会引起panic。使用range则表示从channel中源源不断的接受数据直到被关闭;
  4. select语句使得一个goroutine在多个通讯操作上等待,阻塞直到某个分支可行,例如:
  1. // var a, b chan int
  2. select{
  3. case x <- a:
  4. //...
  5. case <- b:
  6. //...
  7. default:
  8. //...
  9. }

当所有分支都不可行时,执行default语句;
5. sync.Mutex提供了互斥锁,包括LockUnlock两个方法,可以使用defer语句保证锁一定会被释放;


至此,基础部分结束。


进阶部分

环境搭建

  1. 前面导出了GOPATH环境变量,这个路径就是实际的工作空间。从结论来看,Go提倡将所有Go语言项目放入同一个工作路径,当然也可以使用虚拟环境,类似python。
  2. 如果使用过go get命令,那么GOPATH下会自动创建bin, pkgsrc三个文件夹,源码存放在src之下,import本地包时,就是从这一层开始的;
  3. go install会生成输出文件(可执行或者库),go build则仅编译;

使用技巧

  1. Go自带了一个工具go fmt用来对代码进行格式化;
  2. 注释的格式和C++一致。使用godoc生成文档,类似python的docstring,但是约定更加简单:对类型、变量、常量、函数或者包的注释,在其定义前编写普通的注释即可,不要插入空行。Godoc 将会把这些注释识别为对其后的内容的文档。
  3. 与顶级定义不相邻的注释,会被 godoc 的输出忽略,但有一个意外。以“BUG(who)”开头的顶级注释会被识别为已知的 bug,会被包含在包文档的“Bugs”部分。
  4. getter没有必要用Get开头,直接大写首字母就行,setter还可以留着Set
  5. Go习惯使用驼峰式写法,而不是下划线;
  6. Go其实是需要分号的,但是分号是自动插入的。这造成了一些非常奇怪的约定。例如左大括号必须放在一行末尾…
  7. new用来分配内存,并且填0,返回指向对象的指针,程序可以利用这些指针进行手动初始化;make则只能用来创建内置类型(slice, map和channel),返回的是对象本身,而不是指针;
  8. array是一种对象,和它的大小相关;array名并不是指针(和C不同);
  9. print语系和C中基本一致;
  10. interface {}相当于C中的void *可以被转化为任意类型,一种常见的反射方式是使用v.(type),比如str, ok = v.(string),返回的就是string类型;另外可以在switch语句里面用x.(type),然后再case里面判断类型;
  11. import后必须使用,否则会报错(傻逼设定。。),可以用import _ "fmt"的方法导入但不使用,或者用_赋值;
  12. 可以通过往struct里面塞匿名字段(另一个struct)来达到继承的目的,虽然看起来很奇怪就是了;同样,也可以往interface里面塞一个别的interface达到继承接口的目的;
  13. panicrecover是最后手段;
  14. 反射可以用reflect包完成。reflect.TypeOfreflect.ValueOf分别获得接口的实际类型和值;一般把接口类型称作Type,实际类型称为Kind。可以使用x.interface().(int)类似的语法将reflect.Value恢复成原来的值,使用printf时,可以直接将空接口里面的类型信息反射出来;
  15. 如果想要修改一个反射对象,反射对象本身必须是settable的,换句话说,我们应该试图取得对象的指针,而不是对象本身。通过Elem方法拿到指针指向的对象,然后再进行修改;
  16. TypeValue有很多方法进行值的转换;
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注