最近写了很多了Go代码,但是对于Go的深入内容知之甚少,为了达到知其然,并知其所以然的想法。开始了学习之旅,首选学习素材GO圣经。把自己觉得有用的都记下来,以便以后拿来温习。

  • Go 没有的特性
    • 它没有隐式的数值转换
    • 没有构造函数和析构函数
    • 没有运算符重载
    • 没有默认参数
    • 没有继承
    • 没有泛型
    • 没有异常
    • 没有宏
    • 没有函数修饰
    • 更没有线程局部存储
  • 声明的类型
    • var:变量
    • type:类型
    • const:常量
    • func:函数
  • 各种类型对应的零值
    • 数值对应的零值是0
    • 布尔值对应零值是false
    • 字符串对应零值是空字符串
    • 接口或引用类型(包括slice、指针、map、chan和函数)对应nil
    • 数组或者结构体对应内部每种类型对应的零值
  • 变量的声明周期
    • 变量的生命周期指的是在程序运行期间变量有效存在的时间间隔。对于在包一级声明的变量来说,它们的生命周期和整个程序的运行周期是一致的。而相比之下,局部变量的声明周期则是动态的:每次从创建一个新变量的声明语句开始,直到该变量不再被引用为止,然后变量的存储空间可能被回收。函数的参数变量和返回值变量都是局部变量。它们在函数每次被调用的时候创建。
    • 垃圾回收
      • 基本的实现思路是,从每个包级的变量和每个当前运行函数的每一个局部变量开始,通过指针或引用的访问路径遍历,是否可以找到该变量。如果不存在这样的访问路径,那么说明该变量是不可达的,也就是说它是否存在并不会影响程序后续的计算结果。因为一个变量的有效周期只取决于是否可达,因此一个循环迭代内部的局部变量的生命周期可能超出其局部作用域。同时,局部变量可能在函数返回之后依然存在。编译器会自动选择在栈上还是在堆上分配局部变量的存储空间,但可能令人惊讶的是,这个选择并不是由用var还是new声明变量的方式决定的。
var global *int
func f() {
    var x int
    x = 1
    global = &x
}
func g() {
    y := new(int)
    *y = 1
}

f函数里的x变量必须在堆上分配,因为它在函数退出后依然可以通过包一级的global变量找到,虽然它是在函数内部定义的;用Go语言的术语说,这个x局部变量从函数f中逃逸了。相反,当g函数返回时,变量y将是不可达的,也就是说可以马上被回收的。因此,y并没有从函数g中逃逸,编译器可以选择在栈上分配*y的存储空间(也可以选择在堆上分配,然后由Go语言的GC回收这个变量的内存空间),虽然这里用的是new方式。其实在任何时候,你并不需为了编写正确的代码而要考虑变量的逃逸行为,要记住的是,逃逸的变量需要额外分配内存,同时对性能的优化可能会产生细微的影响。

  • 赋值
    • 赋值语句是显式的赋值形式,但是程序中还有很多地方会发生隐式的赋值行为:函数调用会隐式地将调用参数的值赋值给函数的参数变量,一个返回语句会隐式地将返回操作的值赋值给结果变量,一个复合类型的字面量也会产生赋值行为。例如下面的语句:
      medals := []string{"gold", "silver", "bronze"}
      
  • 作用域
    • 不要将作用域和生命周期混为一谈。声明语句的作用域对应的是一个源代码的文本区域;它是一个编译时的属性。一个变量的生命周期是指程序运行时变量存在的有效时间段,在此时间区域内它可以被程序的其他部分引用;是一个运行时的概念。
  • 作用域类型
    • 词法块:花括弧所包含的一系列语句
    • 词法块可以深度嵌套,变量的生命周期在此法块中。