Golang笔记
一
0.
环境变量
-
GOROOT:
安装目录。包括自带的源码、标准库包。 -
GOPATH:
工作目录, 允许多个目录。包含第三方源码。
go install, go get, go run等命令会用到GOPATH环境变量。
当存在多个路径时, 优先采用第一个路径 \GOPATH之下主要包含三个目录:
bin: 存放可执行程序;
pkg: 存放编译好的中间文件, 主要是*.a文件
src: 存放go的源文件, 按包的不同进行组织 \ -
GOBIN:
go install编译存放路径, 可以为空.
为空时则遵循“约定优于配置”原则, 可执行文件放在各自GOPATH目录的bin文件夹中(前提: package main的main函数文件不能直接放到GOPATH的src下面)
开发命令相关
- 整体开发目录
go_project // go_project为GOPATH目录
-- bin
-- myApp1 // 编译生成
-- myApp2 // 编译生成
-- pkg
-- src
-- myApp1 // project1
-- models
-- controllers
-- others
-- main.go
-- myApp2 // project2
-- models
-- controllers
-- others
-- main.go
- import:
- 导入标准库搜索路径:
$GOROOT/src - 导入私人库搜索路径:
$GOPATH/src
import后接包的相对路径(相对$GOROOT/src和$GOPATH/src), 通常相对路径的文件夹名与包名相同。
-
go get
参数 [main.go所在路径]:可选。相对【$GOPATH/src】路径。缺省是.(src自己)。可指定src下面的子文件夹路径。
- git clone到【$GOPATH/src】 (缺省)
- 执行go install
-
go build
编译, 在当前目录下生成可执行文件, 文件名默认与当前目录名同名, win下为xxx.exe -
go install
参数 [main.go所在路径]:可选。相对【$GOPATH/src】路径。缺省是.(src自己)。可指定src下面的子文件夹路径。
- 编译源代码, 若是可执行文件 (package "main"且包含main方法), 则会编译生成可执行文件到【$GOBIN】目录下, $GOBIN为空时, 为【$GOPATH/bin】目录下;
- 可执行文件import引入的其他包, 就会被编译到【$GOPATH/pkg/$GOOS_$GOARCH】目录下;
- 若是一个普通的包,会被编译生成到pkg目录下, 生成文件的文件名以.a结尾
builtin
Constants
Variables
func append(slice []Type, elems ...Type) []Type
func cap(v Type) int
func close(c chan<- Type)
func complex(r, i FloatType) ComplexType
func copy(dst, src []Type) int
func delete(m map[Type]Type1, key Type)
func imag(c ComplexType) FloatType
func len(v Type) int
func make(t Type, size ...IntegerType) Type
func new(Type) *Type
func panic(v interface{})
func print(args ...Type)
func println(args ...Type)
func real(c ComplexType) FloatType
func recover() interface{}
type ComplexType
type FloatType
type IntegerType
type Type
type Type1
type bool
type byte
type complex128
type complex64
type error
type float32
type float64
type int
type int16
type int32
type int64
type int8
type rune
type string
type uint
type uint16
type uint32
type uint64
type uint8
type uintptr
1. 类型定义
package main
type Node struct {
X, Y int
}
var (
x int = 3
y string = "hi"
px *int = &x
v1 = Vertex{1, 2} // 类型为 Vertex
v2 = Vertex{X: 1} // Y:0 被省略
v3 = Vertex{} // X:0 和 Y:0
p = &Vertex{1, 2} // 类型为 *Vertex
a [2]string // string数组
)
func main() {
var c, python, java bool
// var c, python, java = true, false, "no!"
// c, python, java := true, false, "no!"
// 在函数中, :=简洁赋值语句在明确类型的地方, 可以用于替代 var 定义。
// 函数外的每个语句都必须以关键字开始(var、 func等), :=结构不能使用在函数外
}
func add(x, y int) int {
return x + y
}
2. slice
- 一个 slice 会指向一个序列的值,并且包含了长度信息。
- []T是一个元素类型为 T的 slice。
- len(s)返回 slice s 的长度。
slice的slice
- slice 可以重新切片,创建一个新的 slice 值指向相同的数组。
- s[lo:hi]表示从 lo到 hi-1的 slice 元素,含左端,不包含右端
- s[lo:hi][lo2:hi2]表示在切片一次的基础上再切片一次
make: 构造slice
- slice 由函数make创建。这会分配一个全是零值的数组并且返回一个 slice 指向这个数组:
- a := make([]int, 5) // len(a)=5
- 为了指定容量,可传递第三个参数到 make:
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:] // len(b)=4, cap(b)=4
- slice 的零值是 nil , 其长度和容量是 0。
append: 向slice添加元素
- func append(s []T, vs ...T) []T
- append的第一个参数 s是一个元素类型为 T的 slice ,其余类型为 T的值将会附加到该 slice 的末尾。
- append的结果是一个包含原 slice 所有元素加上新添加的元素的 slice。
- 如果s的底层数组太小,而不能容纳所有值时,会分配一个更大的数组。 返回的 slice 会指向这个新分配的数组。
range
- for循环的 range格式可以对 slice 或者 map 进行迭代循环。
- 当使用 for循环遍历一个 slice 时,每次迭代 range将返回两个值。 第一个是当前下标(序号),第二个是该下标所对应元素的一个拷贝。
map
- map 映射键到值。
- map 在使用之前必须用 make来创建;值为 nil的 map 是空的,并且不能对其赋值。
package main
import "fmt"
type Vertex struct {
Lat, Long float64
}
var m map[string]Vertex
func main() {
m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
fmt.Println(m["Bell Labs"])
}
或直接初始化
package main
import "fmt"
type Vertex struct {
Lat, Long float64
}
var m = map[string]Vertex{
"Bell Labs": {40.68433,-74.39967},
"Google": {37.42202, -122.08408},
}
func main() {
fmt.Println(m)
}
- 在 map m中插入或修改一个元素:
m[key] = elem - 获得元素:
elem = m[key] - 删除元素:
delete(m, key) - 通过双赋值检测某个键存在:
elem, ok = m[key] // elem为nil值时, ok = false
闭包
package main
import "fmt"
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
neg(-2*i),
)
}
}
package main
import "fmt"
// 返回一个“返回int的函数”
func fibonacci() func() int {
a,b := 1, 0
return func() int {
a, b = b, a+b
return a
}
}
func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}
类型断言
如果T是具体类型
类型断言检查x的动态类型是否等于具体类型T。如果检查成功,类型断言返回的结果是x的动态值,其类型是T,否则panic。
var w io.Writer
w = os.Stdout
f := w.(*os.File) // success: f == os.Stdout
c := w.(*bytes.Buffer) // panic: interface holds *os.File, not *bytes.Buffer
如果T是接口类型
类型断言检查x的动态类型是否满足T。如果检查成功,返回值是一个类型为T的接口值。换句话说,到接口类型的类型断言,改变了表达式的类型,改变了(通常是扩大了)可以访问的方法,且保护了接口值内部的动态类型和值。
var w io.Writer
w = os.Stdout
rw := w.(io.ReadWriter) // success: *os.File has both Read and Write
w = new(ByteCounter)
rw = w.(io.ReadWriter) // panic: *ByteCounter has no Read method
无论T是什么类型,如果x是nil接口值,则类型断言失败。
如果我们想知道类型断言是否失败,而不是失败时触发panic,可以使用返回两个值的版本
y, ok := x.(T)
Type Switches
switch x.(type){
case nil: // 如果x是nil
case int, uint:
case bool:
case string;
default: //没有匹配上
}
二
函数
带指针参数的函数必须接受一个指针
panic
当panic异常发生时,程序会中断运行,并立即执行在该goroutine中被延迟的函数(defer 机制)。随后,程序崩溃并输出日志信息。
recover
如果在deferred函数中调用了内置函数recover,并且定义该defer语句的函数发生了panic异常,recover会使程序从panic中恢复,并返回panic value。导致panic异常的函数不会继续运行,但能正常返回。在未发生panic时调用recover,recover会返回nil。
reflect
-
反射可以大大提高程序的灵活性,使得interface{}有更大的发挥余地
- 反射必须结合interface才玩得转
- 变量的type要是concrete type的(也就是interface变量)才有反射一说
-
反射可以将“接口类型变量”转换为“反射类型对象”
- 反射使用 TypeOf 和 ValueOf 函数从接口中获取目标对象信息
-
反射可以将“反射类型对象”转换为“接口类型变量
- reflect.value.Interface().(已知的类型)
- 遍历reflect.Type的Field获取其Field
-
反射可以修改反射类型对象,但是其值必须是“addressable”
- 想要利用反射修改对象状态,前提是 interface.data 是 settable,即 pointer-interface
-
通过反射可以“动态”调用方法
-
因为Golang本身不支持模板,因此在以往需要使用模板的场景下往往就需要使用反射(reflect)来实现
方法
不能对pointer、interface定义方法
方法的接受者可以是 *T 或者 T。
接收者为 *T 时,相当于传引用,传入可以为 *T 或 T(但不能是临时变量)。
接受者为 T 时,相当于传值,传入可以为 *T 或 T。
接口
一个类型如果拥有一个接口需要的所有方法,这个类型就实现了这个接口。
interface{}被称为空接口类型,我们可以将任意一个值赋给空接口类型。
接口值,由两个部分组成,一个具体的类型type(某个结构体或者基本类型)和那个类型的值value。
接口内的方法调用的时候,相当于value值为参数进行调用
nil接口:类型和值都是nil。
sort
package sort
type Interface interface {
Len() int
Less(i, j int) bool // i, j are indices of sequence elements
Swap(i, j int)
}
package sort
type reverse struct{ Interface } // that is, sort.Interface
func (r reverse) Less(i, j int) bool { return r.Interface.Less(j, i) }
func Reverse(data Interface) Interface { return reverse{data} }
常用
slice
make([][]int, 5)
[]int{0, 1, 2}
map
make(map[string][int])
ma["hh"] = 2
delete(ma, "hh")
make/append/len/cap/range
bulitin function
func append(slice []Type, elems ...Type) []Type
func copy(dst, src []Type) int
func make(t Type, size ...IntegerType) Type
func new(Type) *Type
不定参
package main
import "fmt"
func Sum(a ...int) int {
s:=0
for _,i:=range a{
s+=i
}
return s
}
func main() {
fmt.Println(Sum(1,2,3,4))
}
协程
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
信道
循环 for i := range c 会不断从信道接收值,直到它被关闭。
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 将和送入 c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // 从 c 中接收
fmt.Println(x, y, x+y)
}
// 等价二叉树
package main
import "golang.org/x/tour/tree"
import "fmt"
// Walk 步进 tree t 将所有的值从 tree 发送到 channel ch。
func dfs(t *tree.Tree, ch chan int) {
if t == nil {
return
}
//ch <- t.Value
dfs(t.Left, ch)
ch <- t.Value
dfs(t.Right, ch)
}
func Walk(t *tree.Tree, ch chan int) {
dfs(t, ch)
close(ch)
}
// Same 检测树 t1 和 t2 是否含有相同的值。
func Same(t1, t2 *tree.Tree) bool {
ch1, ch2 := make(chan int), make(chan int)
go Walk(t1, ch1)
go Walk(t2, ch2)
for i := range ch1 {
if i != <-ch2 {
return false
}
}
_, ok := <-ch2
return !ok
}
func main() {
ch := make(chan int)
go Walk(tree.New(1), ch)
for num := range ch {
fmt.Println(num)
}
fmt.Println(Same(tree.New(1), tree.New(1)))
fmt.Println(Same(tree.New(1), tree.New(2)))
}
互斥锁
package main
import (
"fmt"
"sync"
"time"
)
// SafeCounter 的并发使用是安全的。
type SafeCounter struct {
v map[string]int
mux sync.Mutex
}
// Inc 增加给定 key 的计数器的值。
func (c *SafeCounter) Inc(key string) {
c.mux.Lock()
// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
c.v[key]++
c.mux.Unlock()
}
// Value 返回给定 key 的计数器的当前值。
func (c *SafeCounter) Value(key string) int {
c.mux.Lock()
// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
defer c.mux.Unlock()
return c.v[key]
}
func main() {
c := SafeCounter{v: make(map[string]int)}
for i := 0; i < 1000; i++ {
go c.Inc("somekey")
}
time.Sleep(time.Second)
fmt.Println(c.Value("somekey"))
}
并行爬虫
package main
import (
"fmt"
)
type Fetcher interface {
// Fetch 返回 URL 的 body 内容,并且将在这个页面上找到的 URL 放到一个 slice 中。
Fetch(url string) (body string, urls []string, err error)
}
// Crawl 使用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。
func Crawl(url string, depth int, fetcher Fetcher) {
// TODO: 并行的抓取 URL。
// TODO: 不重复抓取页面。
// 下面并没有实现上面两种情况:
if depth <= 0 {
return
}
body, urls, err := fetcher.Fetch(url)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("found: %s %q\n", url, body)
for _, u := range urls {
Crawl(u, depth-1, fetcher)
}
return
}
func main() {
Crawl("https://golang.org/", 4, fetcher)
}
// fakeFetcher 是返回若干结果的 Fetcher。
type fakeFetcher map[string]*fakeResult
type fakeResult struct {
body string
urls []string
}
func (f fakeFetcher) Fetch(url string) (string, []string, error) {
if res, ok := f[url]; ok {
return res.body, res.urls, nil
}
return "", nil, fmt.Errorf("not found: %s", url)
}
// fetcher 是填充后的 fakeFetcher。
var fetcher = fakeFetcher{
"https://golang.org/": &fakeResult{
"The Go Programming Language",
[]string{
"https://golang.org/pkg/",
"https://golang.org/cmd/",
},
},
"https://golang.org/pkg/": &fakeResult{
"Packages",
[]string{
"https://golang.org/",
"https://golang.org/cmd/",
"https://golang.org/pkg/fmt/",
"https://golang.org/pkg/os/",
},
},
"https://golang.org/pkg/fmt/": &fakeResult{
"Package fmt",
[]string{
"https://golang.org/",
"https://golang.org/pkg/",
},
},
"https://golang.org/pkg/os/": &fakeResult{
"Package os",
[]string{
"https://golang.org/",
"https://golang.org/pkg/",
},
},
}