A Tour of Go - Go语言入门笔记 | 青训营笔记

278 阅读6分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记。

导言

笔者之前勉强算是系统性地学习过C/C++/Java/Python,学过一点Kotlin和JavaScript,但这次又捡起了Go,所以可能会从一些奇怪的角度分析Go语言的一些特性。 参考的内容来自A Tour of GoGolang 零值、空值与空结构实效Go编程

零值

  1. go会对所有基本变量赋预先规定的零值,特别的对于字符串是"",对于引用类型(string不是引用类型)则是nil。
  2. nil本身没有默认类型。
  3. go的类型系统里面有一些因为interface产生的奇怪内容。

初始化

  1. 与零值相对的是初始化,像切片、map和chan这些引用类型声明时会默认初始化,可以直接使用,但是用new函数声明时返回的是零值nil。
  2. 之前在社区闲扯的时候,我说go看起来没有new运算符(我的意思其实应该是指关键字),声明数组和哈希表要用make函数,这倒是和C的malloc很像,反正看起来也没有类,都是struct。之后就有人就喷我说让我回去好好学习。go确实有一个内建函数new用来分配内存并返回指针,但是请注意,new不是一个关键字,请参考官网The Go Programming Language Specification中Keywords部分列出的关键字,目前只有下面25个是关键字。
break        default      func         interface    select
case         defer        go           map          struct
chan         else         goto         package      switch
const        fallthrough  if           range        type
continue     for          import       return       var
  1. new不是一个关键字又怎么样? 这意味着new可以用来命名函数和变量,比如说下面的代码解析的new函数就不是内建函数,new变量也可以正常赋值。至于如何声明一个func new(Type){}函数我还没搞懂,这里的Type类型是go内建的,不太清楚去哪里获取定义。再比如说你的某个包内只有一个类型,像学Java一样搞工厂模式或者单例模式,我觉得也可以声明一个包内的new函数,然后只导出一个GetInstance之类的函数。我只是想想,还没有尝试行不行得通。
import (
   "fmt"
)

func main() {
   new(0)
   new := 1
   fmt.Println(new)
}

func new(a int) {
   fmt.Println("hijacked")
}

1.go支持struct的指针用'.'运算符实现成员变量的访问,这和C++指针的"->"运算符不一样。看起来go的指针类型有点弱。

2.go有点像Kotlin,声明变量时不初始化需要加var。

3.go的数组切片非常费解,像[0:]、[:10]、[:]这些在用Python时就很熟悉了,但是数组切片居然还有cap(acity)属性,表示底层数组从切片起始索引到末尾的长度,如果容量cap大于切片长度,那么下面这种切片扩展是合法的i:=[]int{1,2,3,4,5,6} i=i[:0] i=i[:4],但是i:=[]int{1,2,3,4,5,6} i=i[:4] i=i[-2:]是不合法的,也就是说只能单向扩展,不理解这玩意有啥用。

5.声明函数的时候碰到一个想不通的事,[]int和[1]int类型不同,但是[]int和[:]int类型相同。

6.声明数组的时候还支持匿名struct,比如t:=[]struct{}{}。这和C还是Python的一切是值的理念差不多?

7.对于多返回值的赋值语句,似乎可以用:=重复命名同名但不同类型的变量。而且:=可以用在左侧部分变量声明过的情况,但是不能用于左侧变量全部声明过的情况。

1.go的常量用const声明可以隐式指明类型,那它到底是用C/C++那样用宏做替换呢还是像:=一样做了类型推断呢?

2.switch语句编译之后是按状态跳转还是就是if-else的语法糖?语法描述是从上至下顺序匹配case,尤其是支持switch {}和switch true {}这种写法。

3.switch的case支持浮点数,我试了一下,好像不是按误差abs(a-b)ε比较的,像是浮点数按位比较,比如1==1+10^(-16),因为1和10^(-16)转换为二进制后相差超过52位,和64位浮点数的精度有关,那么类似的就是 1!=1+2*10^(-16)。所以这和go对值是否相等的判断机制有关?比如浮点数和浮点数相等。

4.看到defer、panic和recover的时候真的蚌埠住了,我寻思这不是Java的finally、throws和try-catch之类的吗,但是recover必须在defer函数内,这是不是意味着go因为某些我没了解到的安全机制从而去掉了异常机制,但是把运行时异常用panic-recover这套东西处理掉了,而且看描述,panic-recover都在所谓的同一套goroutine里处理,也就是在协程之类的地方处理?不太熟悉这些。

1.go是没有自动类型转换的,但是为什么 type myfloat float64 test myfloat=0.0 float64(test)可以不用像C++一样写类型转换函数?

2.go支持指针类型使用'.'运算符访问成员变量和类似成员函数的receiver函数,但还是保留了按值传递和按引用传递的区别。go的receiver函数和函数多返回值、多重赋值让我感觉到处都是语法糖,总有种C套壳或者C框架的感觉。

3.但是我觉得有点矛盾的是go的函数形参如果声明为指针,就不能传入非指针,客观上避免了误用和可能的越界,但是语法糖为啥不做彻底一点允许混用呢?不过那就向Java靠拢了。而且实现接口的时候receiver函数也会严格区分指针和非指针版本。暂时没有搞懂go指针的使用场景,是不是和C++用法差不多。

4.go里面的接口让我感觉挺离谱的,var test interface{}就像是强化版的(void*)和Object,但是go里面的这种变量居然能保留变量的原始类型,fmt.Print这种函数形参都是interface{},现在再想想还是一阵瞳孔地震。 5.go里面的断言有点丑,一开始看到i.()还在想这是不是Java Object里的那种默认函数。 6.go里面的错误机制感觉更像是一种最佳实践规范,实际上是用接口实现,与多返回值的特性相辅相成。