这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记。
导言
笔者之前勉强算是系统性地学习过C/C++/Java/Python,学过一点Kotlin和JavaScript,但这次又捡起了Go,所以可能会从一些奇怪的角度分析Go语言的一些特性。 参考的内容来自A Tour of Go、Golang 零值、空值与空结构和实效Go编程。
零值
- go会对所有基本变量赋预先规定的零值,特别的对于字符串是"",对于引用类型(string不是引用类型)则是nil。
- nil本身没有默认类型。
- go的类型系统里面有一些因为interface产生的奇怪内容。
初始化
- 与零值相对的是初始化,像切片、map和chan这些引用类型声明时会默认初始化,可以直接使用,但是用new函数声明时返回的是零值nil。
- 之前在社区闲扯的时候,我说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
- 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里面的错误机制感觉更像是一种最佳实践规范,实际上是用接口实现,与多返回值的特性相辅相成。