go学习总结

188 阅读7分钟

标签(空格分隔): go


英文文档 中文文档-看云版 中文文档-go-zh.org版

更推荐看云版的中文文档,翻译的更准确。

[TOC]

建议看go本身的源码,里面关于文档格式、变量命名都有示范意义。

变量类型

Documents -> References -> Language Specification

常量

Go中的常量就是不变量。它们在编译时创建,即便它们可能是函数中定义的局部变量。 常量只能是数字、字符(符文)、字符串或布尔值。由于编译时的限制, 定义它们的表达式必须也是可被编译器求值的常量表达式。 链接

变量的初始化与常量类似,但其初始值也可以是在运行时才被计算的一般表达式。

变量声明

type T struct {
	S string
}

var t T           # 声明t是T类型的变量
var t = T{"bbb"}   # 声明t是T类型的变量,且赋予初始值 "bbb"
var t T = T{"bbb"} # 同上
t := T{"bbb"}     # 同上
var t T = {"bbb"} # 错误。也就是说变量值必须用类型包裹

多次声明

同一个变量名可以声明多次,重新声明与再次赋值。 _ 可以用来存放不用的返回值,或者就是为了引入一个包,而在代码中不使用包的东西。

同名新建

以相同的名字创建新的变量

# 在Go中这样做是合法且惯用的。你用相同的名字获得了该变量的一个新的版本, 以此来局部地刻意屏蔽循环变量,使它对每个Go程保持唯一。
req := req

关键字 type

定义一个结构体

make

make 只适用于映射、切片和信道且不返回指针。若要获得明确的指针, 请使用 new 分配内存。[]

struct

# 下面2种定义都可以
type Vertex struct {
	X int
	Y int
}

type Vertex struct {
	X, Y int
}

# var 用小括号可以声明一堆
var (
	v1 = Vertex{1, 2}  // has type Vertex
	v2 = Vertex{X: 1}  // Y:0 is implicit
	v3 = Vertex{}      // X:0 and Y:0
	p  = &Vertex{1, 2} // has type *Vertex
)

内嵌

interface 有内嵌,struct 也有内嵌能力。 都在effective go有介绍(挨着的)

# struct 在定义[内嵌](https://go-zh.org/doc/effective_go.html)的时候可以不用声明字段名直接用类型表示吗?
# 可以,避免了提供字段并且提供方法的繁琐,算是一种语法糖
# 而且如下所示,内嵌字段可以和普通字段并存
type Job struct {
	Command string
	*log.Logger
}

# 当内嵌一个类型时,该类型的方法会成为外部类型的方法, 但当它们被调用时,该方法的接收者是内部类型,而非外部的。在我们的例子中,当 bufio.ReadWriter 的 Read 方法被调用时, 它与之前写的转发方法具有同样的效果;接收者是 ReadWriter 的 reader 字段,而非 ReadWriter 本身。

中文文档有的地方翻译的有错误引导的倾向(所以不要单纯依赖中文文档)。如:

# 应该翻译为:内嵌与子类在某个重要方面也是不同的。way不应该翻译为手段,而应该翻译为方面。比如:they differ with each other in many ways(他们在许多方面都不一样)。
There's an important way in which embedding differs from subclassing. 
vs
还有种区分内嵌与子类的重要手段。

# 这个翻译没有把 alongside 体现出来:**并存**
This example shows an embedded field alongside a regular, named field.
vs
这个例子展示了一个内嵌字段和一个常规的命名字段。

switch

常规

  • 替代 if else
  • case 可以不用非得是常量字符串或数字,也可以是函数调用等
  • 不用写 break,如果一个条件符合了,就不往下走了
  • 可以不带参数,相当于 switch true
  • 并列的条件不能连写多个 case,而是一个 case 内用逗号分开

Type switches

参考下面的 Type assertions。

A type switch is a construct that permits several type assertions in series.

# 和普通的 Type assertions 不一样的是括号里面用了关键词 type
# 而且得到的返回值是 . 前面的i类型,这回 i 就专门不是 empty interface 了,而是想要测试类型的 interface 了
A type switch is like a regular switch statement, but the cases in a type switch specify types (not values), and those values are compared against the type of the value held by the given interface value.

switch v := i.(type) {
case T:
    // here v has type T
case S:
    // here v has type S
default:
    // no match; here v has the same type as i
}

The declaration in a type switch has the same syntax as a type assertion i.(T), but the specific type T is replaced with the keyword type.

for

for 仅带一个条件的话相当于 while,不带参数就相当于 while true

range

就是用于 for 循环。

遍历 slicemap

range 用于 for 循环,去遍历一个 slicemap。 返回顺序是 index, value 可以不要第二个变量,只得到 index

遍历 channel

for i := range c

The loop for i := range c receives values from the channel repeatedly until it is closed.

# sender一般不用关闭channel,除非要终止一个range
Channels aren't like files; you don't usually need to close them. Closing is only necessary when the receiver must be told there are no more values coming, such as to terminate a range loop.

select

随机选择一个可执行的case(未block的)去执行。 没有参数,后面就是大括号。

The select statement lets a goroutine wait on multiple communication operations.

A select blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready.

The default case in a select is run if no other case is ready.(但不会终止整个程序,只有return能终止)


# select 本身不会循环,还是需要 for 来循环
func fibonacci(c, quit chan int) {
	x, y := 0, 1
	for {
		select {
		case c <- x:
			fmt.Println("ljy1")
			x, y = y, x+y
		case <-quit:
			fmt.Println("quit")
			return
		}
	}
}

defer

周围的函数都return之后才执行 不过defer函数的参数是立即运行的,无需担心变量值在函数执行时被改变 一堆defer的话是被压进一个stack,后进先出

panic

对于一些隐式的运行时错误,如切片索引越界、类型断言错误等情形下,panic方法就会被调用,它将立刻中断当前函数的执行,并展开当前Goroutine的调用栈,依次执行之前注册的defer函数。

recover

调用recover方法会终止栈展开操作并返回之前传递给panic方法的那个参数。由于在栈展开过程中,只有defer型函数会被执行,因此recover的调用必须置于defer函数内才有效

init函数

每个源文件可以定义自己的不带参数的(niladic)init函数,来设置它所需的状态。(实际上每个文件可以有多个init函数。)init是在程序包中所有变量声明都被初始化,以及所有被导入的程序包中的变量初始化之后才被调用。

除了用于无法通过声明来表示的初始化以外,init函数的一个常用法是在真正执行之前进行验证或者修复程序状态的正确性。

pointer 指针

p := &i 表示 p 是变量 i 的指针 *p 表示变量 i,也就是说 * + 指针 表示原变量

slice

tour之外的详细文档

实效Go中的介绍:

在Go中,

数组是值。将一个数组赋予另一个数组会复制其所有元素。
特别地,若将某个数组传入某个函数,它将接收到该数组的一份副本而非指针。
数组的大小是其类型的一部分。类型 [10]int 和 [20]int 是不同的。
数组为值的属性很有用,但代价高昂;若你想要C那样的行为和效率,你可以传递一个指向该数组的指针。

# 但这并不是Go的习惯用法,切片才是。

[]bool{true, true, false} 表示了2重意思: 创建了一个数组 [3]bool{true, true, false} 创建了一个切片,指向上面的数组

slice 可以表示一段对源数组的引用(而不是必须新切一段数组出来),这点和 js 是不一样的。 slice 的 length 表示当前切片中元素数量,capacity 表示从当前切片的第一个元素开始往后数,源数组中元素的数量。

append

给 slice 增加新的元素。返回一个新的slice s = append(s, 2, 3, 4)

pointer method

method 就是有 receiver 的function receiver 分为普通receiver以及 pointer receiver

函数的参数定义为什么类型,执行的时候就得是什么类型(指针就是指针,原值就是原值)。 无论mehtond 定义的时候的 receiver 是源值还是指针,method 执行的时候,既可以用源值调用,也可以用指针调用。因为go都会做出解析

v.Scale(5) as (&v).Scale(5)          # v是源值,Scale 是 method with pointer receiver [链接](https://tour.golang.org/methods/6)
p.Abs() is interpreted as (*p).Abs() # p是指针,Abs 是 method with value receiver [链接](https://tour.golang.org/methods/7)

但是 value receiverpointer receiver 定义的method是不同的:

  • pointer receiver 类型的method 可以改变源值

推荐使用 pointer receiver 来定义method:

  • 方法可以修改源值
  • 执行方法的时候不用再复制一个副本

interface 接口

interface 是一种具有一组方法的类型,这些方法定义了 interface 的行为。 如果一个类型实现了一个 interface 中所有方法,我们说类型实现了该 interface,所以所有类型都实现了 empty interface,因为任何一种类型至少实现了 0 个方法。go 没有显式的关键字用来实现 interface,只需要实现 interface 包含的方法即可。 interface 的变量中存储的是实现了 interface 的类型的对象值链接

# empty interface 没有定义方法的interface 
type I interface{}

# nil interface 没有具体的值去承载
type I interface {
	M()
}
var i I
// i = &T{"hello"} # 比如没有这一句

内嵌

注意调用的方法的接收者不太一样?

type Reader interface {
	Read(p []byte) (n int, err error)
}

type Writer interface {
	Write(p []byte) (n int, err error)
}


// Only interfaces can be embedded within interfaces
// ReadWriter 接口结合了 Reader 和 Writer 接口。
type ReadWriter interface {
	Reader
	Writer
}

Type assertions

# 如果i承载的是T类型,就能返回值给t,否则会panic
# 一般用 empty interface,因为任何一种类型都实现了 empty interface
t := i.(T)

# 如果i承载的是T类型,就能返回值给t,同时ok是true;否则不会panic,而是给t一个类型T的zero value ,ok为false
t, ok := i.(T)

map

有什么用?

内存

官方文档 Documents -> References -> The Go Memory Model

channel

官方文档1 官方文档2

重要:一侧block了之后,另一侧 才 会运行 重要:一侧block了之后,另一侧 就 会运行

对于unbuffered channel:

ch := make(chan int)

By default, sends and receives block until the other side is ready. This allows goroutines to synchronize without explicit locks or condition variables.

send动作和receive动作在对方ready之前会block,也就是阻塞当前线程。

对于buffered channel:

ch := make(chan int, 100)

Sends to a buffered channel block only when the buffer is full. Receives block when the buffer is empty.

buffer满的时候,send动作才会block
buffer空的时候,receive动作才会block
# **因为一侧block了之后,另一侧才会运行**,所以不是同时又send又receive,所以一旦开始写,直到写满后block,receive端才会继续运行;一旦开始读,直到读空后block,receive端才会继续运行;可以试试(塞完一波取一波):

package main

import (
	"fmt"
)

func fibonacci(n int, c chan int) {
	x, y := 0, 1
	for i := 0; i < n+2; i++ {
		c <- x
		fmt.Println("ljy",x)
		x, y = y, x+y
	}
	close(c)
}

func main() {
	c := make(chan int, 10)
	go fibonacci(cap(c), c)
	// for range c其实就是receive操作
	for i := range c {
		fmt.Println(i)
	}
}
# 但是time.sleep 会主动block,这样就不用等塞满或取完去block,可以试试(塞一个就取一个):
package main

import (
	"fmt"
	"time"
)

func fibonacci(n int, c chan int) {
	x, y := 0, 1
	for i := 0; i < n+2; i++ {
		c <- x
		fmt.Println("lky",x)
		x, y = y, x+y
		time.Sleep(1000 * time.Millisecond)
	}
	close(c)
}

func main() {
	c := make(chan int, 10)
	go fibonacci(cap(c), c)
	for i := range c {
		fmt.Println(i)
	}
}

channel 接收者第二个参数是布尔值,便是 channel 是否关闭了

v, ok := <-ch

Channels of channels

goroutine

go 语句启动一个 goroutine 之后,并不会立即执行函数里面的语句,而是先执行外层函数后面的语句。 主线程退出的话,go 语句创建的并发线程也会销毁。

go module

下载回来的东西放到gopath/pkg/mod/cache/download

引用自己的包的话,从最外层名字开始往内写 引用外部的包的话,虽然go.mod里面有,还是要写全?不能直接写模块名,go mod 下载到一定的path下面了,还是有路径的。 只是不是从默认的GOPATH 找,而是从go mod指定的path下找了?

go env
go mod help init
go help importpath
# Go settings
export GOPATH=$(go env GOPATH)
export GO111MODULE=on
export GOPROXY=https://goproxy.io

export NODE_ENV=development
export PATH=$PATH:~/Library/Python/2.7/bin:$GOPATH/bin

protocol buffer

官方文档

# 编译器将数据编译成二进制信息
From that, the protocol buffer compiler creates a class that implements automatic encoding and parsing of the protocol buffer data with an efficient binary format. 

string name = 1; 后面的数字1表示存储到内存时字段 name 的代号,为了拓展以及向前兼容,一旦创建,最好不要再修改。另外,该数字1-15用的字节少,所以越是常用的字段,尽量用小数字。

安装 protocol buffer compiler

链接

  • 先下载安装包,将 bin 里面的二进制脚本 protoc复制到 $PATH 下,建议放到 $GOPATH/bin 下面
  • 在 go 项目中执行 go get -u github.com/golang/protobuf/protoc-gen-go 供脚本使用