0%

go语言学习笔记

为了去看Biscuit内核,学习一下Go语言,看起来简易教程挺短的。

  • fmt.Println格式化输出,fmt.Sprint格式化字符串

    strings.Join([]string, " ")

    strings.Fields(string) []string,返回被空格隔开的若干不含空格子串

  • 名字开头大写表示被导出,才能在包外使用。

  • 函数参数类型在变量名之后。

    1
    func add(x, y int) int { return x + y }

    多个相同类型参数x int, y int-> x, y int

    函数可返回一个元组如a, b := swap("hello", "world")

    函数返回值可被命名,如返回值为(x, y int)只需在函数内分别给x,y赋值即可,return无参数

  • var用来声明一个变量列表,var c, python bool,可以出现在包或函数级别

    变量初始化var i, j int = 1, 2,可以自动类型推导,此时变量列表类型不必一致

    在函数内可以直接使用k := 3代替变量声明,但函数外每条语句必须以关键词开头,所以函数外只能用var声明变量

  • Go的基本类型有boolstringint(uint),int8,int16,int32,int64byte=uint8rune=int32float32,float64complex64,complex128

    int,uint,uintptr32位系统上为32位,在64位系统上为64

    Printf中,%T占位符表示变量类型字符串,%v表示变量值字符串

    零初始化:数值类型0,bool类型false,字符串空串

    类型转化T(v)将值v转换为类型T没有隐式类型转换,因此int+float32会报错

    在变量初始化与赋值中支持自动类型推导

    使用const声明常量,常量不支持赋值运算符

    Go会在编译期一定程度上支持整数溢出直接报错

  • Go只有for循环

    1
    2
    3
    4
    sum := 0
    for i := 0; i < 10; i++ {
    sum += i
    }

初始化语句若声明变量,则只在循环体中可见。

只有条件判断语句,可以省略前后两个分号实现while功能。

无限循环for{}

if在判断之前也可以加上一个赋值型变量声明语句如if i := 0; ... {},声明的变量在if-else块中均有效

switch运行第一个值等于表达式的case语句,switch后的变量之前可以插入变量声明,相比C语言,每个case后面会自动加break,最后有一个default,同时case后面的值不必为整数常量

switch后面不加比较变量,而在每个case后面接bool表达式,可以用来实现更为通用的if-else链情形。

deter语句会将函数推迟到外层函数返回后执行,推迟调用的函数参数立即求值,但外层函数返回后才会被调用;实现时将声明的函数压入defer栈,因此会按照声明顺序从后往前进行调用

  • *T表示指向T类型的指针,空指针nil&,*语义同C语言

    Go不支持指针运算

  • 结构体声明与输出

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    type Vertex struct {
    X int
    Y int
    }
    func main() {
    v := Vertex{1, 2}
    fmt.Println(v)
    fmt.Println(v.X)
    p := &v
    p.X = 10 // shortcut of (*p).X = 10
    fmt.Println(v)
    v := Vertex{X: 5} // Y = 0
    fmt.Println(v)
    }
  • 数组[n]T表示拥有nT类型值的数组。

    初始化primes := [6]int{2,3,5,7,11,13},数组长度必须为常数

    数组切片var s []int = primes[1: 4],左闭右开,切片是数组的引用而非复制,len(s)表示切片长度,cap(s)表示切片开头到源数组结尾长度,切片有零值nil

    cap(s)>len(s)时,可以对切片进行拓展,…

    使用make动态分配数组并返回这个数组的切片,如a:=make([]int,0,5)参数分别表示len(a),cap(a),如果只有一个参数则表示len(a)=cap(a)

    高维数组

    1
    2
    3
    4
    5
    6
    board := [][]string{
    []string{"_", "_", "_"},
    []string{"_", "_", "_"},
    []string{"_", "_", "_"},
    }
    board[1][2]...

可以使用func append(s []T, vs ...T) []T向切片末尾追加元素并返回新切片,如果cap不够,即源数组太小,则内存会被拓展。(一个能在中间插入的vector)

可以使用for i, v := range pow {...}遍历切片。下标或者值可以使用_来代替表示忽略。

  • 映射map[T1]T2T1类型的值映射到T2,初值为nil。要使用make(map[T1]T2)初始化并返回此映射

    可以使用类数组访问或者结构体初始化映射

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    type Vertex struct {
    Lat, Long float64
    }

    var m = map[string]Vertex{
    "Bell Labs": Vertex{
    40.68433, -74.39967,
    },
    "Google": Vertex{
    37.42202, -122.08408,
    },
    }

插入/修改元素m[key] = elem,获取元素elem = m[key],删除元素delete(m, key),通过双赋值检测某个键是否存在elem, ok = m[key]ok表示键key是否在映射m中,为true,false

  • 闭包…

  • 为结构体定义方法,在函数声明中加入接收者参数

    1
    2
    3
    func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
    }

接受者的类型定义与方法声明必须在同一个包内,不能为内建类型声明方法,但可以使用type给内建类型另一个名字

可以为指针接收者*T声明方法,如C语言一样,这样才能对访问接收者内存进行修改。

如果同时实现了接收者为T,*T的两个相同方法m,则T.m()将会被重定向(&T).m()调用接收者为*T的方法。如果接收者为*T的方法未实现,则*T.m()会被重定向到(*T).m()。二者不应该混用,而指针接收者版本由于无需复制、能够修改通常更好。

  • 接口,一组方法签名定义的集合。

    1
    2
    3
    4
    5
    6
    7
    8
    type Abser interface {
    Abs() float64
    }
    var a Abser
    f := MyFloat(-math.Sqrt2)
    v := Vertex{3, 4}
    a = f // 需要接收者MyFloat实现接口Abser内所有函数
    a = &v // 需要接收者*Vertex实现接口Abser内所有函数

我们无须显式声明某结构体实现了某接口,只需对应接收者实现接口内所有函数,因此没有implements关键字。

接口值在内部看成一个(value, type)的元组。即具体值,具体类型。

具体值为nil的底层方法依然会被调用,因此在实现*T接收者的方法时要注意判空指针。但具体值为nil的接口值自身不为nil

空接口interface{}可保存任何类型的值,被用来处理未知类型的值

类型断言t := i. (T)断言接口值i保存了具体类型T,并将类型为T的具体值赋予变量t。如果没有保存,就会运行时错误。因此,类比映射,最好使用t, ok := i. (T)

类型选择参考代码如下

1
2
3
4
5
6
7
8
9
10
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
fmt.Printf("I don't know about type %T!\n", v)
}
}

实现Stringer接口type Stringer interface{ String() string }

可以将自身转为字符串使用Println/Printf输出。

使用error值表示错误状态type error interface { Error() string }

通常函数会返回error值,我们要:

if err != nil { fmt.Printf("%v", err) } else {...}

  • Go程(goroutine)

    go f(x, y, z)启动一个新的Go程并执行f(x, y, z)

    信道使用ch := make(chan T)创建

    可以使用ch <- v将值v送入信道ch,可以使用v := <-ch从信道ch接收值并赋予v

    信道可以有缓冲区,当缓冲区满时,向信道发送数据的Go程被阻塞;当缓冲区空时,从信道接收数据的Go程被阻塞。

    发送者可以通过close(c)关闭信道,对于接收者而言,v, ok := <-c可以判断信道是否被关闭。接收者可以用for i := range c不断从信道接受数据直到信道被关闭,而发送者关闭信道也主要是为了接收者可以退出接收循环

    select语句使得一个Go程可以等待多个通信操作,会被阻塞到某个分支可以继续运行。