代码改变世界

go 学习笔记

2017-12-25 16:13  LittleMan  阅读(461)  评论(0编辑  收藏  举报
  1. var 声明语句可以创建一个特定类型的变量,然后给变量附加一个名字。并且设置变量的初始值。变量声明的一般语法如下:
    ​var 变量名字 类型 = 表达式
    bool 对应的零值为false

  2. 在函数内部,有一种称为简单变量声明语句用于声明和初始化局部变量。用名字 := 表达式声明变量,变量的类型根据表达式来自动推导。

  3. 简短变量声明左边的变量并不是全部都是刚刚声明的。如果有一些已经在相同的词法域声明过了,那么简短变量声明语句对这些已经声明过的变量就只有赋值行为。【注】简短变量声明语句中必须至少要声明一个新的变量。

  4. 并不是每一个值都会有内存地址,但是对于每一个变量必然有对应的的内存地址。

  5. 任何类型的指针的零值都是nil

  6. 在go语言中,返回函数中局部变量的地址也是安全的,不过每次对函数调用将返回不同的结果

  7. 指针是实现标准库中flag包的关键技术,他使用命令行参数来设置对应变量的值。

  8. 另一个创建变量的方法是调用内建的new函数。表达式new(T)将创建一个T类型的匿名变量,初始化为T类型的零值,然后返回变量的地址,返回的指针类型为*T。每次调用new函数都是返回一个新的变量的地址。存在特殊情况,就是说类型的大小是0,例如struct{}和[0]int,可能有相同的地址。

  9. 译注:函数的有右小括弧也可以另起一行缩进,同时为了防止编译器在行尾自动插入分号而导致的编译错误,可以在末尾的参数变量后面显式插入逗号。像下面这样:

        for t := 0.0; t < cycles*2*math.Pi; t += res {
            x := math.Sin(t)
            y := math.Sin(t*freq + phase)
            img.SetColorIndex(
                size+int(x*size+0.5), size+int(y*size+0.5),
                blackIndex, // 最后插入的逗号不会导致编译错误,这是Go编译器的一个特性
            )               // 小括弧另起一行缩进,和大括弧的风格保存一致
         }
    
  10.   v, ok = m[key] //map查找
      v, ok = x.(T) //类型断言
     v, ok = <-ch 通道接收
    //  _ 空白字符
      medals := []string{"1", "2", "3"}
    
    
  11. 类型命名: type 类型名字 底层类型【注】类型声明的语句一般出现包一级,如果新建类型的名字的首字母大写,则在外部包也可以使用。

  12. 在go语言中,如果一个名字是大写字母开头,那么该名字是导出的

  13. 如果导入一个包,但是又没有使用使用该包,将被当做一个编译错误来处理。

  14. 包的初始化首先是解决包级变量的依赖顺序,然后按照包级变量声明出现的顺序依次初始化,每个包只能被初始化以一次。

  15. 字符串操作s[i:j]基于原始的s字符串的第i个字节到第j个字节生成一个新字符串。生成的新字符串将包含j-i个字节。

  16. 字符串的值是不可变的:一个字符串包含的字节序列永远不会被改变

  17. 因为字符串是只读的,因此逐步构建字符串会导致很多分配和复制。在这种情况下,使用bytes.Buffer类型将会更有效。

  18. strconv包提供了布尔型、整形数、浮点数和对应字符串的相互转换。

  19. 将一个整数转换为字符串,一种方法是用fmt.Spintf返回一个格式化的字符串;另一个方法使用strconv.Itoa()

  20. 如果将一个字符串解析为整数,可以使用strconv包的Atoi或ParseInt函数

  21. 常量表达式的值在编译期计算,而不是在运行期间。 多个常量const ()

  22. 如果是批量声明的常量,除了第一个外其他的常量右边的初始化表达式都可以省略,如果省略初始化表达式,则表示使用前面常量的初始化表达式写法,对应的常量了类型也是一样

    const (
      a = 1
      b
      c = 2
      d
    )
    
  23. 常量声明可以使用iota常量生成器初始化,它用于生成一组相似规则初始化的常量,但是不用每行都写一遍初始化表达式。在一个const声明语句中,在第一个声明的常量所在的行,iota将会被置为0,然后每一个有常量声明的行加一

    type Weekday int
    
    const (
        Sunday Weekday = iota
        Monday
        Tuesday
        Wednesday
        Thursday
        Friday
        Saturday
    )
    
  24. 在数组字面值中,如果在数组的长度位置出现的是…省略号,则表示数组的长度是根据初始化值的个数来计算。

  25. r := [...]int{99: -1}
    定义一个含有100个元素的数组r,最后一个元素被初始化为-1,其他元素都是用0初始化
    
    
  26. 一个零值slice等于nil。一个nil值的slice并没有底层数组。一个nil值的slice长度和容量都是0,但是也有非nil值的slice的长度和容量也是0的,例如[]int{}或make([]int, 3)[3:]。与任意类型的nil值一样,我们可以用[]int(nil)类型转换表达式来生成对应类型slice的nil值。【注】如果需要测试一个slice是否为空,应该使用len(s) == 0来判断,而不应该用s==nil来判断。

    slice创建

    make([]T, len)
    make([]T, len, cap) // same as make([]T, cap)[:len]
    

  27. map类型可以写为map[K]V,创建map的方法:

    1. Ages := make(map[string]int)

    2. 创建空的map的表达式是map[string]int{}

    3. 使用内置的delete函数可以删除元素:delete(ages, "Alice")

    4. 禁止对map元素取址的原因是map可能随着元素数据的增长而重新分配更大的内存空间,从而可能导致之前的地址无效

    5. 想要遍历map中的全部的key/value对的话,可以使用range风格的for循环实现

      for name, age := range ages {
        
      }
      
  28. Map的迭代顺序是不确定的,并且不同的哈希函数实现可能导致不同的遍历顺序。

  29. 如果结构体的成员是以大写字母开头的,那么该成员是可以到处的。

  30. 一个命名为S的结构体类型将不能再包含S类型的成员:因为一个聚合的值不能包含它自身。但是可以包含*S指针类型的成员

  31. 如果结构体没有任何成员的话就是空结构体,写作struct{}。它的大小是0,也不包含任何信息,但是有时候是有价值的。

  32. 当匿名函数需要被递归调用时,我们必须首先声明一个变量,再讲匿名函数复制给这个变量。如果不分为两个部分,函数字面变量无法与变量绑定。

  33. defer 语句的函数会在return语句更新返回值变量后再执行,又因为在函数中定义的匿名函数可以访问该函数包括返回值变量在内的所有变量,所以匿名函数采用defer机制,可以使其观察函数的返回值【注】return XXX 这条语句并不是一条原子指令。函数的执行过程是这样的:先给返回值赋值,然后使用defer表达式,最后才是返回到调用函数中。defer表达式可能会在设置函数返回值之后,在返回到使用函数之前,修改返回值。

    返回值 = xxx

    调用defer 函数

    空的return

    func double(x int) (result int) {
      defer func() {fmt.Printf("double(%d) = %d\n",x, result)}()
      return x+x
    }
    
  34. 在循环体中的defer语句需要注意,因为只有在函数执行完毕后,这些被延时的函数才会执行。一种解决办法将循环体中的defer语句转移至另外一个函数,在每次循环时,调用这个函数。

  35. package geometry
    
    import "math"
    
    type Point struct{ X, Y float64 }
    
    // traditional function
    func Distance(p, q Point) float64 {
        return math.Hypot(q.X-p.X, q.Y-p.Y)
    }
    
    // same thing, but as a method of the Point type
    func (p Point) Distance(q Point) float64 {
        return math.Hypot(q.X-p.X, q.Y-p.Y)
    }
    

    上面的p Point中的p叫做方法的接收器,在go语言中,并不会像其他语言那样用this或者self作为接收器,可以任意的选择接收器的名字

    p := Pont{1, 2}
    q := Point{4, 6}
    fmt.Println(Distance(p,q))
    fms.Pringln(p.Distance(q))
    

    p.Distance叫做选择器,因为他会选择合适的对应P这个对象的Distance方法来执行。

  36. 只有类型(Point)和指向他们的指针(*Point),才是可能会出现在接收器声明里的两种接收器。此外,为了避免歧义,在声明方法时,如果一个类型名本身是一个指针的话,是不允许出现在接收器中的。想用指针类型方法( *Point).ScaleBy,只要提供一个Point类型的指针即可

    r := &Point{1, 2}
    r.ScaleBy(2)
    fmt.Println(*r)
    

    如果接收器p是一个Point类型的变量,并且器方法需要一个Point指针作为接收器,可以使用p.ScaleBy(2),编译器会隐式的帮我们用&p去调用ScaleBy这个方法,这种简写方法只适用于变量,包括struct里的字段比如p.X,以及array和slice内的元素比如perim[0]

  37. 方法值和方法表达式

    选择一个方法,并且在同一个表达式里执行,比如常见的p.Distance()形式,实际上将其分成两部分来执行也是可能的:p.Distance叫做选择器,选择器返回一个方法值->一个将方法绑定到特定接收器变量的函数。这个函数可以不通过指定其接收器即可被调用。即调用时不需要指定接收器,只要传入函数的参数即可。

    p := Point{1, 2}
    q := Point{4, 6}
    
    distanceFromP := p.Distance;
    fmt.Println(SistnaceFromp(q))
    

    在一个包的API需要一个函数值,且调用方法希望操作时某一个绑定了对象的方法的话,方法值会非常实用.下面例子中的time.AfterFunc这个函数的功能是在指定的延迟时间之后来执行一个(译注:另外的)函数。且这个函数操作的是一个Rocket对象r

    type Rocket struct {/**/}
    func (r *Rocket) Launch() {/* ....*/}
    r := new(Rocket)
    time.AfterFunc(10 * time.Second, func() { r.Launch() })
    //直接用方法值传入的话
    time.AfterFunc(10*time.Second, r.Lanunch)
    

    和方法值相关的还有方法表达式,当调用一个方法时,与调用一个普通的函数相比,我们必须要用选择器(p.Distance)语法来指定方法的接收器。

    当T时一个类型时,方法表达式可能会写作T.f或者(*T).f,会返回一个函数"值",这种函数会将其第一个参数用作接收器,所以可以用通常(不写选择器)的方式来对其进行调用

    p := Point{1, 2}
    q := Point{4, 6}
    
    distanceFromP := Point.Distance
    fmt.Println(distance(p, q))
    fmt.Printf("%T\n", distance) // func(Point, Point) float64
    
    scale := (*Point).ScaleBy
    scale(&p, 2)
    fmt.Println(p)            // "{2 4}"
    fmt.Printf("%T\n", scale) // "func(*Point, float64)"
    
    // 这个Distance实际上是指定了Point对象为接收器的一个方法func (p Point) Distance
    //但通过Point.Distance得到的函数需要比实际的Distance方法多一个参数
    
  38. 对于一个命名过的具体类型T;它一些方法的接收者时类型T本身然而另一些则是一个T的指针。在T类型的参数是哪个滴啊勇一个T的方法时合法的,但要这个参数是一个变量;编译器隐式的获取了它的地址。但这仅仅是一个语法糖:T类型的值不拥有所有*T指针的方法,那这样它就肯呢个只实现更少的接口。

  39. interface{} 被称为空接口类型时不可或缺的。因为空接口类型对实现它的类型没有要求,所以我们可以将任意个值付给空接口类型。我们不能直接对空接口持有的值做操作,因为interface{}没有任何方法,可以通过类型断言来获取interface{}中值得方法。

  40. 接口值:概念上一个接口的值,街口值,由两个部分组成,一个具体的类型和那个类型的值。他们呢被称为接口的动态类型和动态值。类型是编译期的概念,因此类型不是一个值。

  41. 类型断言时一个使用在接口值上的操作。;语法上它看起来像x.(T)被称为断言类型,这里x表示一个接口的类型和T表示一个类型。一个类型断言检查它操作对象的动态类型是否和断言的类型匹配。

  42. 在go语言中,每一个并发的执行单元叫做一个goroutine,在语法上,go语句时一个普通的函数和方法调用前加上关键字go。go语句会使其语句最后那个的函数在一个新创建的gotoutine中运行。

  43. channels时goroutine之间的通信机制,一个channels是一个通信机制。每个channel都一个特殊的类型,也就是channel可以发送的数据类型.

    ch := make(chan int)
    

    一个channel有发送和接收两个主要操作,都是通信行为。一个发送语句将一个值从一个goroutine通过channel发送到另一个执行接收操作的goroutine。发送和接收两个操作都是用<-预算符。在发送语句中,<-运算符分割channel和要发送的值。在接收语句中,<-运算符写在channel对象之前

    ch <- x //发送
    x = <- ch //接收
    <-ch 、、
    

    go语言提供了单向的channel类型,分别用于只发送或者只接收的channel。类型chan<-int表示一个只发送int的channel,只能发送不能接收。相反,类型<-chan int 表示一个只接收int的channel,只能接收不能发送。

    • 带缓存的Channel内部持有一个元素队列。队列的最大容量是在调用make函数创建channel时通过第二参数指定的.ch = make(chan string, 3)
  44. 反射是由reflect包提供支持。它定义了两个重要的类型,Type和Value。一个Type表示一个go类型。它是一个接口。唯一能反映reflect.Type实现的是接口类型的描述信息,同样的实体标识了动态类型的接口值

    函数 reflect.TypeOf接受任意的interface{}类型,并返回对应动态类型的reflect.Type reflect.TypeOf返回的是一个动态类型的接口值,它总是会返回具体的类型。

    一个reflect.Value可以持有一个任意类型的值。函数reflect.ValueOf接受任意的interface{}类型,并返回对应动态类型的reflect.Value。和reflect.TypeOf类似,reflect.ValueOf返回的结果也是具体的类型,但是reflect.Value也可以持有一个接口值.

  45. 一个reflect.Value和interface{}都能保存任意的值。所不同的是,一个空的接口隐藏了值对应的表示方式和所有的公开的方法,因此只有我们知道具体的动态类型才能使用类型断言来访问内部的值,对于内部值并没有特别可做的事情。

  46. 所有通过reflect.ValueOf(x)返回的reflect.Value都是不可取地址。不过可以通过调用reflect.ValueOf(&x).Elem()来获取任意变量x对应的可取地址的Value.可以通过reflect.Value的CanAddr方法来判断是否可以被取地址。每次通过指针间接的获取reflect.Value都是可取地址的,即使开始的是一个不可取的地址Value。在反射机制中,所有关于是否支持去地址的规则都是类似的。

    要从变量对应的可取地址的reflect.Value来访问变量需要三个步骤

    1. 调用Addr()方法,它返回一个Value,里面保存了指向变量的指针。
    2. 在Value上调用interface方法,也就是返回一个 interface{},里面通用包含指向变量的指针。
    3. 如果已经知道变量类型,可以使用类型的断言机制将得到的interface{}类型的接口强制还为普通类型的指针,这样就可以通过这个普通指针的更新变量
    x := 2
    d := reflect.ValueOf(&x).Elem() // d refers to x
    px := d.Addr().Interface().(*int) // px = &x
    *px = 3
    

    或者,不要使用指针,而是通过调用可取地址的reflect.Value的reflect.Value.Set方法来更新对于的值

    d.Set(refkect.ValueOf(4))
    

    Set方法将在运行时执行和编译时类似的可赋值性约束的检查

  47. 数组slice并不会实际复制一份数据,它只是创建一个新的数据结构,包含了另外的一个指针,一个长度和一个容量数据。