golang笔记1


enum

关键字iota定义常量组中从0开始按行计数的自增枚举值。 在同一常量组中,可以提供多个iota,它们各自增长。

  A, B = iota, iota << 10 // 0, 0 << 10
  C, D  // 1, 1 << 10
  

如果iota自增被打断,要显式恢复

  A = iota  // 0
  B         // 1
  C = "c"   // c
  E = iota  // 4, 显式恢复。注意计数包含C, D两行。
  F         // 5
  

可通过自定义类型实现枚举限制。

  type Color int
  const (
    Black Color = iota
    Red
    Blue
  )
  func test(c Color) {}
  func main(){
    c := Black    
    test(c)

    x := 1
    test(x) // Error: cannot use x (type int) as type Color in function argument

    test(1) // 常量会被编译器自动转换
  }
  

指针

不能对指针进行加减操作 可以使用unsafe.Pointer和任意类型指针间进行转换。

    func main(){
        x := 0x12345678

        p := unsafe.Pointer(&x) // *int -> Pointer
        n := (*[4]byte)(p)

        for i:=0; i<len(n); i++{
            fmt.Printf("%X ", n[i])    
        }
    }
  

返回局部变量指针是安全的,编译器会根据需要将其分配在GC Heap上。

    func test() *int{
        x := 100
        return &x // 在堆上分配内存。但在内联时,也可直接分配在目标栈上
    }
  

将Pointer转化成uintptr,可变相实现指针运算

    func main(){
        d := struct{
            s string
            x int
        }{"abc", 100}    
        
        p := uintptr(unsafe.Pointer(&d)) // *struct -> Pointer -> uintptr
        p += unsafe.Offsetof(d.x)  // uintptr + offset

        p2 := unsafe.Pointer(p) // uintptr -> Pointer
        px := (*int)(p2) // Pointer -> *int
        *px = 200 // d.x = 200

        fmt.Printf("%#v\n", d)
    }
  

GC把uintptr当成普通整数对象,无法阻止”关联”对象被回收

Bit

go 没有~, 取反也用^

0110 & 1011 = 0010 AND 0110 | 1011 = 1111 OR 0110 ^ 1011 = 1101 XOR 0110 &^ 1011 = 0100 AND NOT 清除标志位, 比较常见

a := 0 a |= 1 « 2 // 0000100: bit2设置标志位 a |= 1 « 6 // 1000100: bit6设置标志位 a = a &^ (1 « 6) // 0000100: 清除bit6标志位

Array

go 数组是定长的复合类型。如果数组中出现...,则表示数组的长度可以根据初始化值的个数来计算:

  array := [...]int{1, 2, 3}
  

另外,也可以在初始化时指定数组下标:

  months := [...]string{1: "January", /*...*/, 12: "December"}
  array := [...]int{99:-1} /*array len is 100, only array[99]==-1*/
  

array整形数组长度为100, 第100个元素为-1, 其余都是int型的默认值0。

  1. 与c++一样,数组的长度也可以是编译期可以确定的常量表达式。
  2. go中如果有函数以数组作为参数,数组并不会像c/c++中一样隐式转换为指针,而是将形参数组完全拷贝作为函数实参。
  3. 如果函数以Slice作为参数,则Slice的效果与c/c++中的指针效果类似:传递的是Slice的引用,Slice元素并不会完全拷贝。
  4. 指针数组 [n]*T, 数组指针 *[n]T.
  5. 如果数组的元素类型是可以互相比较的,那么数组类型也是可以相互比较的。可以通过==比较运算符来比较两个数组:只有两个数组的所有元素都是相等时候数组才相等。
func change_array(a [3]int) {
	for i := 0; i < len(a); i++ {
		a[i] *= 10
	}

	fmt.Println("In change_array: ", a)
}

func change_slice(s []int) {
	for idx, _ := range s {
		s[idx] *= 10
	}

	fmt.Println("In change_slice: ", s)
}

func main() {
	var param [3]int = [3]int{1, 2, 3}
	change_array(param)
	fmt.Println("After change_array: ", param)

	var slice []int = []int{1, 2, 3}
	change_slice(slice)
	fmt.Println("After chage_slice: ", slice)
}
  

执行结果如下:

1
2
3
4
In change_array:  [10 20 30]  
After change_array:  [1 2 3]  
In change_slice:  [10 20 30]  
After chage_slice:  [10 20 30]  

nil []byte

  • 对nil的[]byte可以有效的进行append, len, copy, range.
package main

import (
    "fmt"
)

var dst []byte

dst = append(dst, []byte("hello world"))

fmt.Println(string(dst))

Slice

  • Slice定义与数组很相似,除了没有指定长度。
  • Slice三要素: 指针, 长度, 容量。
  • 与数组不同,Slice之间不可比较大小。因此我们不能使用==操作符来判断两个Slice是否包含全部相等元素。不过标准库提供了高度优化的bytes.Equal函数来判断两个字节型的Slice是否相等。Slice唯一可以与nil做比较。
  • 多个Slice之间可以共享底层数据,并且引用的数组部分区间可能重叠。例如,有如下数组定义:
  /* define month array */
  months := [...]string{1: "January", /*...*/, 12: "December"}
  /* define slices */
  Q2 := mounths[4:7]
  summer := mounths[6:9]
  

对象布局如下:

Slice不支持==运算符原因有二:

1
2
1. Slice的元素是间接引用的,一个slice甚至可以包含自身。虽然有很多办法处理这种情形,但是没有一个是简单有效的。
2. 因为slice的元素是间接引用的,一个固定值的slice在不同的时间可能包含不同的元素,因为底层数组的元素可能会被修改。 并且Go语言中map等哈希表之类的数据结构的key只做简单的浅拷贝,它要求在整个声明周期中相等的key必须对相同的元素。对于像指针或chan之类的引用类型, ==相等测试可以判断两个是否是引用相同的对象。一个针对slice的浅相等测试的==操作符可能是有一定用处的,也能临时解决map类型的key问题, 但是slice和数组不同的相等测试行为会让人困惑。因此,安全的做法是直接禁止slice之间的比较操作。

Map

  • Map中的元素并不是一个变量,因此不能对map的元素进行取址操作。

凡是可以比较的类型都可以作为Map的键

  _ = &ages["bob"] // compile error: cannot take address of map element
  

Struct

  • 如果一个结构体的成员都是可以比较的,那么结构体也是可以比较的。
  • 如果这个结构体可比较,则它可作为map的键。
  • 结构体支持匿名成员语法糖:Go语言有个特性让我们只声明一个成员对应的数据类型而不指定成员的名字,这类成员叫做匿名成员。 匿名成员的数据类型必须是命名的类型或者指向一个命名的类型的指针。
  type Cicle struct{
    Point // 匿名成员
    Redius int
  }

  type Wheel struct{
    Circle // 匿名成员
    Spokes int
  }

  // access the anonymous
  var w wheel
  w.X = 8 // equivalent to w.Circle.Point.X = 8
  w.Y = 8 // equivalent to w.Circle.Point.Y = 8
  w.Radius = 5 // equivalent to w.Circle.Radius = 5
  

但结构体字面值方式初始化结构体时,就不能省略匿名成员的类型了。 结构体字面值必须遵循形状类型声明时的结构,所以我们只能用下面两种语法:

  w1 = Wheel{Circle{Point{8,8}, 5}, 20}
  w2 = Wheel{
        Circle: Circle{
            Point: Point{X:8, Y:8},
            Radius: 5,
        },
        Spokes: 20,
  }
  

1. 匿名成员也有一个隐式的名字,因此不能同时包含两个类型相同的匿名成员,这会导致名字冲突。 2. 成员的名字是由其类型隐式决定的,所有匿名成员也有可见性的规则约束。在Wheel中,如果Point 和 Circle改成小写,我们仍然可以使用简短形式访问匿名成员嵌套的成员。但在包外部,因为circle和point都没有导出, 因此简短的匿名成员访问语法也是禁止的。但在包外部,因为circle和point未导出,因此简短的匿名成员访问语法 也是禁止的。

函数

  • 函数实参是通过值的方式传递,因此函数的形参是实参的拷贝。对形参的修改不会影响实参。但如果 实参是引用类型(指针, slice, map, function, channel),实参可能会由于函数的间接引用被修改。

  • GO中如果函数只有声明而没有定义(无结构体), 则声明定义了函数标识符,这表示这样的函数不是以GO 实现的。

  package math
  func Sin(x float64) float // implemented in assembly language.
  
  • 大部分编程语言使用固定大小的函数调用栈,常见的大小从64KB到8MB不等。固定大小栈会限制递归的深度,当你用递归 处理大量数据时,需要避免栈溢出; 此外,还会导致安全性问题。 Go语言使用可变栈,栈的大小需要增加(初始时很小)。这使我们使用递归时不必考虑溢出和安全问题。

  • 函数有多处bare return(函数所有返回值都显式指定变量名,return语句可以省略操作数),往往使代码难以 理解而且代码审查时往往容易难以发现错误,所以不宜过度使用bare return。

  • 不论包含defer语句的函数是通过return正常结束还是 由于panic导致的异常结束,defer语句都会被执行,

  • 与c/c++不同,Go语言中在函数中返回局部变量的地址也是安全的。

  var p = f()
  func f() *int{
    v := 1
    return &v
  }
  
  • 一个函数可以执行多条defer语句,他们执行的顺序与声明顺序相反。

eg: 使用defer可以轻松创建一个函数用来记录函数的进入,退出,持续时间。

  func doSomething(){
    defer trace("doSomething")()
    // ... lots of work ...
  }

  func trace(msg string)func(){
      start := time.Now()
      log.Printf("enter %s", msg)
      return func(){
          log.Printf("exit %s (%s)", msg, time.Since(start))
    }
  }
  

Method

除指针和interface外,GO支持为任意其它类型添加附加方法。

结构体匿名内嵌

在类型中匿名内嵌一个命名类型的指针,通过添加这层关系让我们可以共享通用的结构 并动态地改变对象之间的关系。

当编译器解析到一个选择器调用的方法时,它首先到这个类型的直接定义里去找; 然后找被内嵌字段们引入的方法,然后再去内嵌字段中的内嵌(如果有),这样一直 递归向下查找。如果选择器有二义性的话编译期间就会报错: 比如你在同一级里 有两个同名的方法。

    type T1 struct {
    }

    func (t1 T1) Hello() {
    }

    type T2 struct {
    }

    func (t2 T2) Hello() {
    }

    type TT struct {
        T1
        T2
    }

    func main() {
        t := TT{}

        t.Hello() // FIXME: error! ambiguous selector t.Hello

        t.T1.Hello() // ok: call t1
        t.T2.Hello() // ok: call t2
    }
  

a tick

    var cache = struct{
        sync.Mutex
        mapping map[string]string
    }{
        mapping: make(map[string]string)    
    }

    func Lookup(key string) string{
        cache.Lock()
        v := cache.mapping[key]
        cache.Unlock()
        return v
    }
  

方法集

每个类型都有与之关联的方法集:

  • 类型T方法集包含全部receiver T方法
  • 类型*T方法包含全部receiver T + *T方法
  • 如果类型S包含匿名字段T,则S方法集包含T方法
  • 如果类型S包含匿名字段*T,则S方法包含T + *T方法
  • 不管嵌入T或T, *S方法集总是包含T+T方法。

用实例value和pointer调用方法(含匿名字段)不受方法集约束。 编译器总是查找全部方法,并自动转换receiver实参

interface

匿名接口可用作变量类型或结构成员。

    type Tester struct{
        s interface{
            String() string    
        }    
    }

    type User struct{
        id int
        name string
    }

    func (self *User)String() string{
        return fmt.Sprintf("user %d, %s", self.id, self.name)    
    }

    func main(){
        t := Tester{&User{1, "Tom"}}
        fmt.Println(t.s.String())
    }
  

接口技巧

让编译器检查,以确保某个类型实现接口

    var _ fmt.Stringer = (*Data)(nil)
  

某些时候,让函数直接“实现”接口能省不少事

    type Tester interface{
        Do()    
    }
    
    type FuncDo func()
    func (self FuncDo) Do(){self()}

    func main(){
        var t Tester = FuncDo(func(){println("Hello, world")})
        t.Do()
    }

  

channel

  • 向closed channel发送数据引发panic错误,接收立即返回零值。 可使用ok-idiom模式判断channel是否已经关闭。
  • 对于nil channel, 无论发送或者接收都会被堵塞。
    var a, b chan int = make(chan int), make(chan int, 3)
    for{
        if d, ok := <-data; ok{
            fmt.Println(d)    
        }else{
            break    
        }
    }
  
  • 可将channel隐式转化为单向channel,只接收或者只发送。 但无法将单向channel转化成普通channel。
  c := make(chan int, 3)
  
  var send chan<- int = c // send only
  var recv <-chan int = c // recv only

  send <- 1
  // <-send // Error: receive from send-only type chan<- int
  
  <-recv
  // recv <- 2 // Error: send to receive-only type <-chan int

  // d := (chan int)(send)  // Error: cannot convert type chan<- int to type chan int
  // d := (chan int)(recv) // Error!
  

使用channel实现信号量(semaphore)

    func main(){
        wg := sync.WaitGroup{}
        wg.Add(3)

        sem := make(chan int, 1)

        for i:=0; i<3; i++{
            go func(id int){
                defer wg.Done()

                sem <- 1 // 向sem发送数据,堵塞或者成功

                for x:=0; x<3; x++{
                    fmt.Println(id, x)    
                }

                <-sem // 接收数据,使得其他堵塞goroutine可以发送数据
            }(i) 
        }

        wg.Wait()
    }
  

select

如果需要同时处理多个channel,可使用select语句。它随机选择一个可用channel做 收发操作,或执行default case.

在循环中使用select default case时要小心,避免形成洪水。