├── 05.modules ├── go.mod └── hello.go ├── 03.hello └── hello.go ├── 04.goland └── hello.go ├── doc ├── 01.golang安装部署.assets │ ├── image-20200307104441336.png │ ├── image-20200307104459564.png │ ├── image-20200307104510470.png │ └── image-20200307113322420.png ├── 05.goModules介绍.assets │ ├── image-20200308224453465.png │ ├── image-20200308224919808.png │ └── image-20200308230407947.png ├── 04.golang开发工具安装.assets │ ├── image-20200308213639991.png │ ├── image-20200308213639999.png │ ├── image-20200308213720888.png │ ├── image-20200308214137180.png │ ├── image-20200308214206249.png │ ├── image-20200308214256503.png │ ├── image-20200308214352004.png │ ├── image-20200308214422995.png │ ├── image-20200308214456521.png │ └── image-20200308214525960.png ├── 09.golang指针结构体接口.assets │ └── 765389-20180918115659254-712651853.png ├── ReadMe.md ├── 08.golang函数介绍.md ├── 05.goModules介绍.md ├── 10.golang的错误处理.md ├── 09.golang指针结构体接口.md ├── 06.golang基础语法.md ├── 07.golang常用数据结构.md ├── 01-03.golang安装部署.md ├── 11.golang协程.md ├── 12.golang常用函数.md └── 04.golang开发工具安装.md ├── .gitignore ├── 10.errors ├── 04.recover.go ├── 03.panic.go ├── 02.defer.go └── 01.errors.go ├── 11.goRoutines.go ├── 03.channelBuffering.go ├── 05.channelDirections.go ├── 04.channelSynchronization.go ├── 02.channels.go ├── 01.goroutines.go ├── 06.select.go ├── 08.nonBlocking.go └── 07.timeouts.go ├── 09.ptrAndStruct ├── 02structs.go ├── 03methods.go ├── 01pointers.go └── 04interfaces.go ├── 12.tools ├── 03.numberParsing.go ├── 01.stringFunctions.go ├── 06.writingFiles.go ├── 02.stringFormatting.go ├── 04.time.go └── 05.json.go ├── ReadMe.md ├── 08.func └── func.go ├── 06.basic └── basic.go ├── 07.data └── data.go └── LICENSE /05.modules/go.mod: -------------------------------------------------------------------------------- 1 | module hello 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/gogf/gf v1.15.3 7 | ) 8 | -------------------------------------------------------------------------------- /03.hello/hello.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println("hello world!") 7 | } 8 | -------------------------------------------------------------------------------- /04.goland/hello.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println("hello world!") 7 | } 8 | -------------------------------------------------------------------------------- /doc/01.golang安装部署.assets/image-20200307104441336.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goflyfox/gostudy/HEAD/doc/01.golang安装部署.assets/image-20200307104441336.png -------------------------------------------------------------------------------- /doc/01.golang安装部署.assets/image-20200307104459564.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goflyfox/gostudy/HEAD/doc/01.golang安装部署.assets/image-20200307104459564.png -------------------------------------------------------------------------------- /doc/01.golang安装部署.assets/image-20200307104510470.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goflyfox/gostudy/HEAD/doc/01.golang安装部署.assets/image-20200307104510470.png -------------------------------------------------------------------------------- /doc/01.golang安装部署.assets/image-20200307113322420.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goflyfox/gostudy/HEAD/doc/01.golang安装部署.assets/image-20200307113322420.png -------------------------------------------------------------------------------- /doc/05.goModules介绍.assets/image-20200308224453465.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goflyfox/gostudy/HEAD/doc/05.goModules介绍.assets/image-20200308224453465.png -------------------------------------------------------------------------------- /doc/05.goModules介绍.assets/image-20200308224919808.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goflyfox/gostudy/HEAD/doc/05.goModules介绍.assets/image-20200308224919808.png -------------------------------------------------------------------------------- /doc/05.goModules介绍.assets/image-20200308230407947.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goflyfox/gostudy/HEAD/doc/05.goModules介绍.assets/image-20200308230407947.png -------------------------------------------------------------------------------- /doc/04.golang开发工具安装.assets/image-20200308213639991.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goflyfox/gostudy/HEAD/doc/04.golang开发工具安装.assets/image-20200308213639991.png -------------------------------------------------------------------------------- /doc/04.golang开发工具安装.assets/image-20200308213639999.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goflyfox/gostudy/HEAD/doc/04.golang开发工具安装.assets/image-20200308213639999.png -------------------------------------------------------------------------------- /doc/04.golang开发工具安装.assets/image-20200308213720888.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goflyfox/gostudy/HEAD/doc/04.golang开发工具安装.assets/image-20200308213720888.png -------------------------------------------------------------------------------- /doc/04.golang开发工具安装.assets/image-20200308214137180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goflyfox/gostudy/HEAD/doc/04.golang开发工具安装.assets/image-20200308214137180.png -------------------------------------------------------------------------------- /doc/04.golang开发工具安装.assets/image-20200308214206249.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goflyfox/gostudy/HEAD/doc/04.golang开发工具安装.assets/image-20200308214206249.png -------------------------------------------------------------------------------- /doc/04.golang开发工具安装.assets/image-20200308214256503.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goflyfox/gostudy/HEAD/doc/04.golang开发工具安装.assets/image-20200308214256503.png -------------------------------------------------------------------------------- /doc/04.golang开发工具安装.assets/image-20200308214352004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goflyfox/gostudy/HEAD/doc/04.golang开发工具安装.assets/image-20200308214352004.png -------------------------------------------------------------------------------- /doc/04.golang开发工具安装.assets/image-20200308214422995.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goflyfox/gostudy/HEAD/doc/04.golang开发工具安装.assets/image-20200308214422995.png -------------------------------------------------------------------------------- /doc/04.golang开发工具安装.assets/image-20200308214456521.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goflyfox/gostudy/HEAD/doc/04.golang开发工具安装.assets/image-20200308214456521.png -------------------------------------------------------------------------------- /doc/04.golang开发工具安装.assets/image-20200308214525960.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goflyfox/gostudy/HEAD/doc/04.golang开发工具安装.assets/image-20200308214525960.png -------------------------------------------------------------------------------- /doc/09.golang指针结构体接口.assets/765389-20180918115659254-712651853.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goflyfox/gostudy/HEAD/doc/09.golang指针结构体接口.assets/765389-20180918115659254-712651853.png -------------------------------------------------------------------------------- /05.modules/hello.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gogf/gf" 6 | "github.com/gogf/gf/crypto/gmd5" 7 | ) 8 | 9 | func main() { 10 | fmt.Println("hello world!") 11 | fmt.Println(gf.VERSION) 12 | fmt.Println(gmd5.EncryptString("123456")) 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | pkg 13 | *.out 14 | .idea 15 | logs 16 | vendor -------------------------------------------------------------------------------- /10.errors/04.recover.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | // 这里我们对异常进行了捕获 7 | defer func() { 8 | if p := recover(); p != nil { 9 | err := fmt.Errorf("internal error: %v", p) 10 | if err != nil { 11 | fmt.Println(err) 12 | } 13 | } 14 | }() 15 | 16 | // 我们将在这个网站中使用 panic 来检查预期外的错误。这个是唯一一个为 panic 准备的例子。 17 | panic("一个异常") 18 | 19 | } 20 | -------------------------------------------------------------------------------- /10.errors/03.panic.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | // 我们将在这个网站中使用 panic 来检查预期外的错误。这个是唯一一个为 panic 准备的例子。 10 | panic("一个异常") 11 | 12 | // panic 的一个基本用法就是在一个函数返回了错误值但是我们并不知道(或者不想)处理时终止运行。 13 | // 这里是一个在创建一个新文件时返回异常错误时的panic 用法。 14 | fmt.Println("继续") 15 | _, err := os.Create("/tmp/file") 16 | if err != nil { 17 | panic(err) 18 | } 19 | // 运行程序将会引起 panic,输出一个错误消息和 Go 运行时栈信息,并且返回一个非零的状态码。 20 | } 21 | -------------------------------------------------------------------------------- /doc/ReadMe.md: -------------------------------------------------------------------------------- 1 | 文档目录 2 | 3 | * [01-03.golang安装部署.md](01-03.golang安装部署.md) 4 | * [04.golang开发工具安装.md](04.golang开发工具安装.md) 5 | * [05.goModules介绍.md](05.goModules介绍.md) 6 | * [06.golang基础语法.md](06.golang基础语法.md) 7 | * [07.golang常用数据结构.md](07.golang常用数据结构.md) 8 | * [08.golang函数介绍.md](08.golang函数介绍.md) 9 | * [09.golang指针结构体接口.md](09.golang指针结构体接口.md) 10 | * [10.golang的错误处理.md](10.golang的错误处理.md) 11 | * [11.golang协程.md](11.golang协程.md) 12 | * [12.golang常用函数.md](12.golang常用函数.md) -------------------------------------------------------------------------------- /11.goRoutines.go/03.channelBuffering.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // 默认通道是 无缓冲 的,这意味着只有在对应的接收(<- chan)通道准备好接收时,才允许进行发送(chan <-)。 6 | // 可缓存通道允许在没有对应接收方的情况下,缓存限定数量的值。 7 | func main() { 8 | // 这里我们 make 了一个通道,最多允许缓存 2 个值。 9 | messages := make(chan string, 2) 10 | // 因为这个通道是有缓冲区的,即使没有一个对应的并发接收方,我们仍然可以发送这些值。 11 | messages <- "buffered" 12 | messages <- "channel" 13 | // 然后我们可以像前面一样接收这两个值。 14 | fmt.Println(<-messages) 15 | fmt.Println(<-messages) 16 | } 17 | -------------------------------------------------------------------------------- /11.goRoutines.go/05.channelDirections.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // 当使用通道作为函数的参数时,你可以指定这个通道是不是只用来发送或者接收值。 6 | // 这个特性提升了程序的类型安全性。 7 | func ping(pings chan<- string, msg string) { 8 | // ping 函数定义了一个只允许发送数据的通道。 9 | // 尝试使用这个通道来接收数据将会得到一个编译时错误。 10 | pings <- msg 11 | } 12 | func pong(pings <-chan string, pongs chan<- string) { 13 | // pong 函数允许通道(pings)来接收数据,另一通道(pongs)来发送数据。 14 | msg := <-pings 15 | pongs <- msg 16 | } 17 | func main() { 18 | pings := make(chan string, 1) 19 | pongs := make(chan string, 1) 20 | ping(pings, "passed message") 21 | pong(pings, pongs) 22 | fmt.Println(<-pongs) 23 | } 24 | -------------------------------------------------------------------------------- /11.goRoutines.go/04.channelSynchronization.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | import "time" 5 | 6 | // 我们可以使用通道来同步 Go 协程间的执行状态。 7 | // 这里是一个使用阻塞的接受方式来等待一个 Go 协程的运行结束。 8 | func worker(done chan bool) { 9 | // 这是一个我们将要在 Go 协程中运行的函数。 10 | // done 通道将被用于通知其他 Go 协程这个函数已经工作完毕。 11 | fmt.Print("working...") 12 | time.Sleep(time.Second) 13 | fmt.Println("done") 14 | // 发送一个值来通知我们已经完工啦。 15 | done <- true 16 | } 17 | func main() { 18 | // 运行一个 worker Go协程,并给予用于通知的通道。 19 | done := make(chan bool, 1) 20 | go worker(done) 21 | // 程序将在接收到通道中 worker 发出的通知前一直阻塞。 22 | <-done 23 | // 如果你把 <- done 这行代码从程序中移除,程序甚至会在 worker还没开始运行时就结束了。 24 | } 25 | -------------------------------------------------------------------------------- /11.goRoutines.go/02.channels.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // 通道 是连接多个 Go 协程的管道。 8 | // 你可以从一个 Go 协程将值发送到通道,然后在别的 Go 协程中接收。 9 | func main() { 10 | // 使用 make(chan val-type) 创建一个新的通道。 11 | // 通道类型就是他们需要传递值的类型。 12 | messages := make(chan string) 13 | // 使用 channel <- 语法 发送 一个新的值到通道中。 14 | // 这里我们在一个新的 Go 协程中发送 "ping" 到上面创建的messages 通道中。 15 | go func() { 16 | messages <- "ping" 17 | }() 18 | // 使用 <-channel 语法从通道中 接收 一个值。 19 | // 这里将接收我们在上面发送的 "ping" 消息并打印出来。 20 | msg := <-messages 21 | fmt.Println(msg) 22 | // 我们运行程序时,通过通道,消息 "ping" 成功的从一个 Go 协程传到另一个中。 23 | // 默认发送和接收操作是阻塞的,直到发送方和接收方都准备完毕。 24 | // 这个特性允许我们,不使用任何其它的同步操作,来在程序结尾等待消息 "ping"。 25 | } 26 | -------------------------------------------------------------------------------- /09.ptrAndStruct/02structs.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // 这里的 person 结构体包含了 name 和 age 两个字段。 6 | type person struct { 7 | name string 8 | age int 9 | } 10 | 11 | func main() { 12 | // 使用这个语法创建了一个新的结构体元素。 13 | fmt.Println(person{"Bob", 20}) 14 | // 你可以在初始化一个结构体元素时指定字段名字。 15 | fmt.Println(person{name: "Alice", age: 30}) 16 | // 省略的字段将被初始化为零值。 17 | fmt.Println(person{name: "Fred"}) 18 | // & 前缀生成一个结构体指针。 19 | fmt.Println(&person{name: "Ann", age: 40}) 20 | // 使用点来访问结构体字段。 21 | s := person{name: "Sean", age: 50} 22 | fmt.Println(s.name) 23 | // 也可以对结构体指针使用. - 指针会被自动解引用。 24 | sp := &s 25 | fmt.Println(sp.age) 26 | // 结构体是可变的。 27 | sp.age = 51 28 | fmt.Println(sp.age) 29 | } 30 | -------------------------------------------------------------------------------- /09.ptrAndStruct/03methods.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type rectangle struct { 6 | width, height int 7 | } 8 | 9 | // 这里的 area 方法有一个接收器类型 rect。 10 | func (r *rectangle) area() int { 11 | return r.width * r.height 12 | } 13 | 14 | // 可以为值类型或者指针类型的接收器定义方法。这里是一个值类型接收器的例子。 15 | func (r rectangle) perim() int { 16 | return 2*r.width + 2*r.height 17 | } 18 | func main() { 19 | r := rectangle{width: 10, height: 5} 20 | // 这里我们调用上面为结构体定义的两个方法。 21 | fmt.Println("area: ", r.area()) 22 | fmt.Println("perim:", r.perim()) 23 | // Go 自动处理方法调用时的值和指针之间的转化。 24 | // 你可以使用指针来调用方法来避免在方法调用时产生一个拷贝,或者让方法能够改变接受的数据。 25 | rp := &r 26 | fmt.Println("area: ", rp.area()) 27 | fmt.Println("perim:", rp.perim()) 28 | } 29 | -------------------------------------------------------------------------------- /11.goRoutines.go/01.goroutines.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func f(from string) { 6 | for i := 0; i < 3; i++ { 7 | fmt.Println(from, ":", i) 8 | } 9 | } 10 | func main() { 11 | // 假设我们有一个函数叫做 f(s)。 12 | // 我们使用一般的方式调并同时运行。 13 | f("direct") 14 | // 使用 go f(s) 在一个 Go 协程中调用这个函数。 15 | // 这个新的 Go 协程将会并行的执行这个函数调用。 16 | go f("goroutine") 17 | // 你也可以为匿名函数启动一个 Go 协程。 18 | go func(msg string) { 19 | fmt.Println(msg) 20 | }("going") 21 | 22 | // 现在这两个 Go 协程在独立的 Go 协程中异步的运行,所以我们需要等它们执行结束。 23 | // 这里的 Scanln 代码需要我们在程序退出前按下任意键结束。 24 | var input string 25 | fmt.Scanln(&input) 26 | fmt.Println("done") 27 | // 当我们运行这个程序时,将首先看到阻塞式调用的输出,然后是两个 Go 协程的交替输出。 28 | // 这种交替的情况表示 Go 运行时是以异步的方式运行协程的。 29 | } 30 | -------------------------------------------------------------------------------- /10.errors/02.defer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | import "os" 5 | 6 | func main() { 7 | // 假设我们想要创建一个文件,向它进行写操作,然后在结束时关闭它。 8 | // 这里展示了如何通过 defer 来做到这一切。 9 | f := createFile("D:/defer.txt") // f := createFile("/tmp/defer.txt") 10 | // 在 closeFile 后得到一个文件对象,我们使用 defer通过 closeFile 来关闭这个文件。这会在封闭函数(main)结束时执行,就是 writeFile 结束后。 11 | defer closeFile(f) 12 | writeFile(f) 13 | } 14 | func createFile(p string) *os.File { 15 | fmt.Println("creating") 16 | f, err := os.Create(p) 17 | if err != nil { 18 | panic(err) 19 | } 20 | return f 21 | } 22 | func writeFile(f *os.File) { 23 | fmt.Println("writing") 24 | fmt.Fprintln(f, "data") 25 | } 26 | func closeFile(f *os.File) { 27 | fmt.Println("closing") 28 | f.Close() 29 | } 30 | -------------------------------------------------------------------------------- /09.ptrAndStruct/01pointers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // 我们将通过两个函数:val 和 ptr 来比较指针和值类型的不同。 6 | // val 有一个 int 型参数,所以使用值传递。 7 | // val 将从调用它的那个函数中得到一个 val1 形参的拷贝。 8 | func val(val1 int) { 9 | val1 = 0 10 | } 11 | 12 | // ptr 有一和上面不同的 *int 参数,意味着它用了一个 int指针。 13 | // 函数体内的 *iptr 接着解引用 这个指针,从它内存地址得到这个地址对应的当前值。 14 | // 对一个解引用的指针赋值将会改变这个指针引用的真实地址的值。 15 | func ptr(iptr *int) { 16 | *iptr = 0 17 | } 18 | 19 | func main() { 20 | test := 1 21 | fmt.Println("initial:", test) 22 | val(test) 23 | fmt.Println("val:", test) 24 | // 通过 &test 语法来取得 test 的内存地址,例如一个变量i 的指针。 25 | ptr(&test) 26 | fmt.Println("ptr:", test) 27 | // 指针也是可以被打印的。 28 | fmt.Println("pointer:", &test) 29 | // val 在 main 函数中不能改变 test 的值,但是zeroptr 可以,因为它有一个这个变量的内存地址的引用。 30 | fmt.Println("pointer:", *&test) 31 | } 32 | -------------------------------------------------------------------------------- /11.goRoutines.go/06.select.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "time" 4 | import "fmt" 5 | 6 | // Go 的通道选择器 让你可以同时等待多个通道操作。 7 | // Go 协程和通道以及选择器的结合是 Go 的一个强大特性。 8 | func main() { 9 | // 在我们的例子中,我们将从两个通道中选择。 10 | c1 := make(chan string) 11 | c2 := make(chan string) 12 | // 各个通道将在若干时间后接收一个值,这个用来模拟例如并行的 Go 协程中阻塞的 RPC 操作 13 | go func() { 14 | time.Sleep(time.Second * 1) 15 | c1 <- "one" 16 | }() 17 | go func() { 18 | time.Sleep(time.Second * 2) 19 | c2 <- "two" 20 | }() 21 | 22 | // 我们使用 select 关键字来同时等待这两个值,并打印各自接收到的值。 23 | for i := 0; i < 2; i++ { 24 | select { 25 | case msg1 := <-c1: 26 | fmt.Println("received", msg1) 27 | case msg2 := <-c2: 28 | fmt.Println("received", msg2) 29 | } 30 | } 31 | // 我们首先接收到值 "one",然后就是预料中的 "two"了。 32 | // 注意从第一次和第二次 Sleeps 并发执行,总共仅运行了两秒左右。 33 | } 34 | -------------------------------------------------------------------------------- /12.tools/03.numberParsing.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "strconv" 4 | import "fmt" 5 | 6 | // 从字符串中解析数字在很多程序中是一个基础常见的任务,在Go 中是这样处理的。 7 | func main() { 8 | // 内置的 strconv 包提供了数字解析功能。 9 | // 使用 ParseFloat 解析浮点数,这里的 64 表示表示解析的数的位数。 10 | f, _ := strconv.ParseFloat("1.234", 64) 11 | fmt.Println(f) 12 | // 在使用 ParseInt 解析整形数时,例子中的参数 0 表示自动推断字符串所表示的数字的进制。 13 | // 64 表示返回的整形数是以 64 位存储的。 14 | i, _ := strconv.ParseInt("123", 0, 64) 15 | fmt.Println(i) 16 | // ParseInt 会自动识别出十六进制数。 17 | d, _ := strconv.ParseInt("0x1c8", 0, 64) 18 | fmt.Println(d) 19 | // ParseUint 也是可用的。 20 | u, _ := strconv.ParseUint("789", 0, 64) 21 | fmt.Println(u) 22 | // Atoi 是一个基础的 10 进制整型数转换函数。 23 | k, _ := strconv.Atoi("135") 24 | fmt.Println(k) 25 | // 在输入错误时,解析函数会返回一个错误。 26 | _, e := strconv.Atoi("wat") 27 | fmt.Println(e) 28 | } 29 | -------------------------------------------------------------------------------- /11.goRoutines.go/08.nonBlocking.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // 常规的通过通道发送和接收数据是阻塞的。 8 | // 然而,我们可以使用带一个 default 子句的 select 来实现非阻塞 的发送、接收,甚至是非阻塞的多路 select。 9 | func main() { 10 | messages := make(chan string) 11 | signals := make(chan bool) 12 | // 这里是一个非阻塞接收的例子。 13 | // 如果在 messages 中存在,然后 select 将这个值带入 <-messages case中。 14 | // 如果不是,就直接到 default 分支中。 15 | select { 16 | case msg := <-messages: 17 | fmt.Println("received message", msg) 18 | default: 19 | fmt.Println("no message received") 20 | } 21 | 22 | // 一个非阻塞发送的实现方法和上面一样。 23 | msg := "hi" 24 | select { 25 | case messages <- msg: 26 | fmt.Println("sent message", msg) 27 | default: 28 | fmt.Println("no message sent") 29 | } 30 | 31 | // 我们可以在 default 前使用多个 case 子句来实现一个多路的非阻塞的选择器。 32 | // 这里我们试图在 messages和 signals 上同时使用非阻塞的接受操作。 33 | select { 34 | case msg := <-messages: 35 | fmt.Println("received message", msg) 36 | case sig := <-signals: 37 | fmt.Println("received signal", sig) 38 | default: 39 | fmt.Println("no activity") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /12.tools/01.stringFunctions.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import s "strings" 4 | import "fmt" 5 | 6 | // 我们给 fmt.Println 一个短名字的别名,我们随后将会经常用到。 7 | var p = fmt.Println 8 | 9 | // 标准库的 strings 包提供了很多有用的字符串相关的函数。 10 | // 这里是一些用来让你对这个包有个初步了解的例子。 11 | func main() { 12 | // 这是一些 strings 中的函数例子。 13 | // 注意他们都是包中的函数,不是字符串对象自身的方法,这意味着我们需要考虑在调用时传递字符作为第一个参数进行传递。 14 | p("Contains: ", s.Contains("test", "es")) 15 | p("Count: ", s.Count("test", "t")) 16 | p("HasPrefix: ", s.HasPrefix("test", "te")) 17 | p("HasSuffix: ", s.HasSuffix("test", "st")) 18 | p("Index: ", s.Index("test", "e")) 19 | p("Join: ", s.Join([]string{"a", "b"}, "-")) 20 | p("Repeat: ", s.Repeat("a", 5)) 21 | p("Replace: ", s.Replace("foo", "o", "0", -1)) 22 | p("Replace: ", s.Replace("foo", "o", "0", 1)) 23 | p("Split: ", s.Split("a-b-c-d-e", "-")) 24 | p("ToLower: ", s.ToLower("TEST")) 25 | p("ToUpper: ", s.ToUpper("test")) 26 | // 你可以在 strings包文档中找到更多的函数 27 | p() 28 | // 虽然不是 strings 的一部分,但是仍然值得一提的是获取字符串长度和通过索引获取一个字符的机制。 29 | p("Len: ", len("hello")) 30 | p("Char:", "hello"[1]) 31 | } 32 | -------------------------------------------------------------------------------- /11.goRoutines.go/07.timeouts.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "time" 4 | import "fmt" 5 | 6 | // 超时 对于一个连接外部资源,或者其它一些需要花费执行时间的操作的程序而言是很重要的。 7 | // 得益于通道和 select,在 Go中实现超时操作是简洁而优雅的。 8 | func main() { 9 | c1 := make(chan string, 1) 10 | // 在我们的例子中,假如我们执行一个外部调用,并在 2 秒后通过通道 c1 返回它的执行结果。 11 | go func() { 12 | time.Sleep(time.Second * 2) 13 | c1 <- "result 1" 14 | }() 15 | 16 | // 这里是使用 select 实现一个超时操作。res := <- c1 等待结果,<-Time.After 等待超时时间 1 秒后发送的值。 17 | // 由于 select 默认处理第一个已准备好的接收操作,如果这个操作超过了允许的 1 秒的话,将会执行超时 case。 18 | select { 19 | case res := <-c1: 20 | fmt.Println(res) 21 | case <-time.After(time.Second * 1): 22 | fmt.Println("timeout 1") 23 | } 24 | 25 | // 如果我允许一个长一点的超时时间 3 秒,将会成功的从 c2接收到值,并且打印出结果。 26 | c2 := make(chan string, 1) 27 | go func() { 28 | time.Sleep(time.Second * 2) 29 | c2 <- "result 2" 30 | }() 31 | 32 | select { 33 | case res := <-c2: 34 | fmt.Println(res) 35 | case <-time.After(time.Second * 3): 36 | fmt.Println("timeout 2") 37 | } 38 | // 运行这个程序,首先显示运行超时的操作,然后是成功接收的。 39 | // 使用这个 select 超时方式,需要使用通道传递结果。 40 | // 这对于一般情况是个好的方式,因为其他重要的 Go 特性是基于通道和select 的。 41 | } 42 | -------------------------------------------------------------------------------- /12.tools/06.writingFiles.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | ) 9 | 10 | // Go 写文件和我们前面看过的读操作有着相似的方式。 11 | // 读取文件需要经常进行错误检查,这个帮助方法可以精简下面的错误检查过程。 12 | func check(e error) { 13 | if e != nil { 14 | panic(e) 15 | } 16 | } 17 | func main() { 18 | d1 := []byte("hello\ngo\n") 19 | // 开始,这里是展示如写入一个字符串(或者只是一些字节)到一个文件。 20 | err := ioutil.WriteFile("D:/study/dat1", d1, 0644) 21 | check(err) 22 | // 对于更细粒度的写入,先打开一个文件。 23 | f, err := os.Create("D:/study/dat2") 24 | check(err) 25 | // 打开文件后,习惯立即使用 defer 调用文件的 Close操作。 26 | defer f.Close() 27 | // 你可以写入你想写入的字节切片 28 | d2 := []byte{115, 111, 109, 101, 10} 29 | n2, err := f.Write(d2) 30 | check(err) 31 | fmt.Printf("wrote %d bytes\n", n2) 32 | // WriteString 也是可用的。 33 | n3, err := f.WriteString("writes\n") 34 | fmt.Printf("wrote %d bytes\n", n3) 35 | // 调用 Sync 来将缓冲区的信息写入磁盘。 36 | f.Sync() 37 | // bufio 提供了和我们前面看到的带缓冲的读取器一样的带缓冲的写入器。 38 | w := bufio.NewWriter(f) 39 | n4, err := w.WriteString("buffered\n") 40 | fmt.Printf("wrote %d bytes\n", n4) 41 | // 使用 Flush 来确保所有缓存的操作已写入底层写入器。 42 | w.Flush() 43 | 44 | } 45 | -------------------------------------------------------------------------------- /09.ptrAndStruct/04interfaces.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | import "math" 5 | 6 | // 这里是一个几何体的基本接口。 7 | type geometry interface { 8 | area() float64 9 | perim() float64 10 | } 11 | 12 | // 在我们的例子中,我们将让 rect 和 circle 实现这个接口 13 | type rect struct { 14 | width, height float64 15 | } 16 | type circle struct { 17 | radius float64 18 | } 19 | 20 | // 要在 Go 中实现一个接口,我们只需要实现接口中的所有方法。 21 | // 这里我们让 rect 实现了 geometry 接口。 22 | func (r rect) area() float64 { 23 | return r.width * r.height 24 | } 25 | func (r rect) perim() float64 { 26 | return 2*r.width + 2*r.height 27 | } 28 | 29 | // circle 的实现。 30 | func (c circle) area() float64 { 31 | return math.Pi * c.radius * c.radius 32 | } 33 | func (c circle) perim() float64 { 34 | return 2 * math.Pi * c.radius 35 | } 36 | 37 | // 如果一个变量的是接口类型,那么我们可以调用这个被命名的接口中的方法。 38 | // 这里有一个一通用的 measure 函数,利用这个特性,它可以用在任何 geometry 上。 39 | func measure(g geometry) { 40 | fmt.Println(g) 41 | fmt.Println(g.area()) 42 | fmt.Println(g.perim()) 43 | } 44 | func main() { 45 | r := rect{width: 3, height: 4} 46 | c := circle{radius: 5} 47 | // 结构体类型 circle 和 rect 都实现了 geometry接口, 48 | // 所以我们可以使用它们的实例作为 measure 的参数。 49 | measure(r) 50 | measure(c) 51 | } 52 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | golang基础教程-快速入门go语言 2 | 3 | ## 教程内容 4 | 5 | golang语言介绍,安装部署,运行第一个程序及基础介绍,工具介绍,go module,基础语法,常用数据结构,函数介绍,指针,结构体,接口,错误处理,协程,通道,通道选择器及常用函数。 6 | 7 | ## 教程目录 8 | 9 | * [01-03.golang安装部署.md](doc/01-03.golang安装部署.md) 10 | * [04.golang开发工具安装.md](doc/04.golang开发工具安装.md) 11 | * [05.goModules介绍.md](doc/05.goModules介绍.md) 12 | * [06.golang基础语法.md](doc/06.golang基础语法.md) 13 | * [07.golang常用数据结构.md](doc/07.golang常用数据结构.md) 14 | * [08.golang函数介绍.md](doc/08.golang函数介绍.md) 15 | * [09.golang指针结构体接口.md](doc/09.golang指针结构体接口.md) 16 | * [10.golang的错误处理.md](doc/10.golang的错误处理.md) 17 | * [11.golang协程.md](doc/11.golang协程.md) 18 | * [12.golang常用函数.md](doc/12.golang常用函数.md) 19 | 20 | ## 代码地址 21 | 22 | * github:[https://github.com/goflyfox/gostudy](https://github.com/goflyfox/gostudy) 23 | * gitee:[https://gitee.com/goflyfox/gostudy](https://gitee.com/goflyfox/gostudy) 24 | 25 | ## 教程视频 26 | 27 | * 腾讯课堂教程地址:[golang基础教程-快速入门go语言](https://ke.qq.com/course/2585401?taid=9426843331949369&tuin=13b4f9bd) 28 | * bilibili教程地址:[golang基础教程-快速入门go语言](https://www.bilibili.com/video/av94410029) 29 | * 西瓜视频教程地址:[golang基础教程-快速入门go语言](https://www.ixigua.com/pseries/6809291194665796100/) 30 | 31 | ## 其他说明 32 | 33 | 1. QQ交流群:47919644 34 | 2. 对应教程编号前缀目录就是对应课程示例源码 35 | 3. 文件名这里不建议参考,go源码文件尽量都用英文 36 | 37 | -------------------------------------------------------------------------------- /10.errors/01.errors.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "errors" 4 | import "fmt" 5 | 6 | // 按照惯例,错误通常是最后一个返回值并且是 error 类型,一个内建的接口。 7 | func f1(arg int) (int, error) { 8 | // errors.New 构造一个使用给定的错误信息的基本error 值。 9 | if arg == 42 { 10 | return -1, errors.New("can't work with 42") 11 | } 12 | // 返回错误值为 nil 代表没有错误。 13 | return arg + 3, nil 14 | } 15 | 16 | // 通过实现 Error 方法来自定义 error 类型是可以的。 17 | // 这里使用自定义错误类型来表示上面的参数错误。 18 | type argError struct { 19 | arg int 20 | prob string 21 | } 22 | 23 | func (e *argError) Error() string { 24 | return fmt.Sprintf("%d - %s", e.arg, e.prob) 25 | } 26 | func f2(arg int) (int, error) { 27 | if arg == 42 { 28 | // 在这个例子中,我们使用 &argError 语法来建立一个新的结构体,并提供了 arg 和 prob 这个两个字段的值。 29 | return -1, &argError{arg, "can't work with it"} 30 | } 31 | return arg + 3, nil 32 | } 33 | func main() { 34 | // 下面的两个循环测试了各个返回错误的函数。 35 | // 注意在 if行内的错误检查代码,在 Go 中是一个普遍的用法。 36 | for _, i := range []int{7, 42} { 37 | if r, e := f1(i); e != nil { 38 | fmt.Println("f1 失败:", e) 39 | } else { 40 | fmt.Println("f1 工作:", r) 41 | } 42 | } 43 | for _, i := range []int{7, 42} { 44 | if r, e := f2(i); e != nil { 45 | fmt.Println("f2 失败:", e) 46 | } else { 47 | fmt.Println("f2 工作:", r) 48 | } 49 | } 50 | // 你如果想在程序中使用一个自定义错误类型中的数据,你需要通过类型断言来得到这个错误类型的实例。 51 | _, e := f2(42) 52 | if ae, ok := e.(*argError); ok { 53 | fmt.Println(ae.arg) 54 | fmt.Println(ae.prob) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /08.func/func.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | // hello world 7 | fmt.Println("hello world") 8 | 9 | // 1. 加法 10 | res := plus(1, 2) 11 | fmt.Println("1+2 =", res) 12 | res = plusPlus(1, 2, 3) 13 | fmt.Println("1+2+3 =", res) 14 | 15 | // 2. 多值返回 16 | // 这里我们通过多赋值 操作来使用这两个不同的返回值。 17 | a, b := vals() 18 | fmt.Println(a) 19 | fmt.Println(b) 20 | // 如果你仅仅想返回值的一部分的话,你可以使用空白定义符 _。 21 | _, c := vals() 22 | fmt.Println(c) 23 | 24 | // 3. 可变参数 25 | // 变参函数使用常规的调用方式,除了参数比较特殊。 26 | sum(1, 2) 27 | sum(1, 2, 3) 28 | // 如果你的 slice 已经有了多个值,想把它们作为变参使用,你要这样调用 func(slice...)。 29 | nums := []int{1, 2, 3, 4} 30 | sum(nums...) 31 | 32 | // 4. 闭包 33 | // 我们调用 intSeq 函数,将返回值(也是一个函数)赋给nextInt。 34 | // 这个函数的值包含了自己的值 i,这样在每次调用 nextInt 时都会更新 i 的值。 35 | nextInt := intSeq() 36 | // 通过多次调用 nextInt 来看看闭包的效果。 37 | fmt.Println(nextInt()) 38 | fmt.Println(nextInt()) 39 | fmt.Println(nextInt()) 40 | // 为了确认这个状态对于这个特定的函数是唯一的,我们重新创建并测试一下。 41 | newInts := intSeq() 42 | fmt.Println(newInts()) 43 | 44 | // 5. 递归 45 | fmt.Println(fact(7)) 46 | } 47 | 48 | // 函数 49 | // 这里是一个函数,接受两个 int 并且以 int 返回它们的和 50 | func plus(a int, b int) int { 51 | // Go 需要明确的返回值,例如,它不会自动返回最后一个表达式的值 52 | return a + b 53 | } 54 | 55 | // (int, int) 在这个函数中标志着这个函数返回 2 个 int。 56 | func plusPlus(a, b, c int) int { 57 | return a + b + c 58 | } 59 | 60 | // 多返回值函数 61 | func vals() (int, int) { 62 | return 3, 7 63 | } 64 | 65 | // 变参函数 66 | func sum(nums ...int) { 67 | fmt.Print(nums, " ") 68 | total := 0 69 | for _, num := range nums { 70 | total += num 71 | } 72 | fmt.Println(total) 73 | } 74 | 75 | // 闭包 76 | // 这个 intSeq 函数返回另一个在 intSeq 函数体内定义的匿名函数。 77 | // 这个返回的函数使用闭包的方式 隐藏 变量 i。 78 | func intSeq() func() int { 79 | i := 0 80 | return func() int { 81 | i += 1 82 | return i 83 | } 84 | } 85 | 86 | // 递归 87 | // face 函数在到达 face(0) 前一直调用自身。 88 | func fact(n int) int { 89 | if n == 0 { 90 | return 1 91 | } 92 | return n * fact(n-1) 93 | } 94 | -------------------------------------------------------------------------------- /12.tools/02.stringFormatting.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | import "os" 5 | 6 | type point struct { 7 | x, y int 8 | } 9 | 10 | func main() { 11 | // Go 为常规 Go 值的格式化设计提供了多种打印方式。例如,这里打印了 point 结构体的一个实例。 12 | p := point{1, 2} 13 | fmt.Printf("%v\n", p) 14 | // 如果值是一个结构体,%+v 的格式化输出内容将包括结构体的字段名。 15 | fmt.Printf("%+v\n", p) 16 | // %#v 形式则输出这个值的 Go 语法表示。例如,值的运行源代码片段。 17 | fmt.Printf("%#v\n", p) 18 | // 需要打印值的类型,使用 %T。 19 | fmt.Printf("%T\n", p) 20 | // 格式化布尔值是简单的。 21 | fmt.Printf("%t\n", true) 22 | // 格式化整形数有多种方式,使用 %d进行标准的十进制格式化。 23 | fmt.Printf("%d\n", 123) 24 | // 这个输出二进制表示形式。 25 | fmt.Printf("%b\n", 14) 26 | // 这个输出给定整数的对应字符。 27 | fmt.Printf("%c\n", 33) 28 | // %x 提供十六进制编码。 29 | fmt.Printf("%x\n", 456) 30 | // 对于浮点型同样有很多的格式化选项。使用 %f 进行最基本的十进制格式化。 31 | fmt.Printf("%f\n", 78.9) 32 | // %e 和 %E 将浮点型格式化为(稍微有一点不同的)科学技科学记数法表示形式。 33 | fmt.Printf("%e\n", 123400000.0) 34 | fmt.Printf("%E\n", 123400000.0) 35 | // 使用 %s 进行基本的字符串输出。 36 | fmt.Printf("%s\n", "\"string\"") 37 | // 像 Go 源代码中那样带有双引号的输出,使用 %q。 38 | fmt.Printf("%q\n", "\"string\"") 39 | // 和上面的整形数一样,%x 输出使用 base-16 编码的字符串,每个字节使用 2 个字符表示。 40 | fmt.Printf("%x\n", "hex this") 41 | // 要输出一个指针的值,使用 %p。 42 | fmt.Printf("%p\n", &p) 43 | // 当输出数字的时候,你将经常想要控制输出结果的宽度和精度,可以使用在 % 后面使用数字来控制输出宽度。 44 | // 默认结果使用右对齐并且通过空格来填充空白部分。 45 | fmt.Printf("|%6d|%6d|\n", 12, 345) 46 | // 你也可以指定浮点型的输出宽度,同时也可以通过 宽度.精度 的语法来指定输出的精度。 47 | fmt.Printf("|%6.2f|%6.2f|\n", 1.2, 3.45) 48 | // 要左对齐,使用 - 标志。 49 | fmt.Printf("|%-6.2f|%-6.2f|\n", 1.2, 3.45) 50 | // 你也许也想控制字符串输出时的宽度,特别是要确保他们在类表格输出时的对齐。 51 | // 这是基本的右对齐宽度表示。 52 | fmt.Printf("|%6s|%6s|\n", "foo", "b") 53 | // 要左对齐,和数字一样,使用 - 标志。 54 | fmt.Printf("|%-6s|%-6s|\n", "foo", "b") 55 | // 到目前为止,我们已经看过 Printf了,它通过 os.Stdout输出格式化的字符串。 56 | // Sprintf 则格式化并返回一个字符串而不带任何输出。 57 | s := fmt.Sprintf("a %s", "string") 58 | fmt.Println(s) 59 | // 你可以使用 Fprintf 来格式化并输出到 io.Writers而不是 os.Stdout。 60 | fmt.Fprintf(os.Stderr, "an %s\n", "error") 61 | } 62 | -------------------------------------------------------------------------------- /12.tools/04.time.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | import "time" 5 | 6 | func main() { 7 | p := fmt.Println 8 | // 得到当前时间。 9 | now := time.Now() 10 | p(now) 11 | // 通过提供年月日等信息,你可以构建一个 time。时间总是关联着位置信息,例如时区。 12 | then := time.Date( 13 | 2009, 11, 17, 20, 34, 58, 651387237, time.UTC) 14 | p(then) 15 | // 你可以提取出时间的各个组成部分。 16 | p(then.Year()) 17 | p(then.Month()) 18 | p(then.Day()) 19 | p(then.Hour()) 20 | p(then.Minute()) 21 | p(then.Second()) 22 | p(then.Nanosecond()) 23 | p(then.Location()) 24 | // 输出是星期一到日的 Weekday 也是支持的。 25 | p(then.Weekday()) 26 | // 这些方法来比较两个时间,分别测试一下是否是之前,之后或者是同一时刻,精确到秒。 27 | p(then.Before(now)) 28 | p(then.After(now)) 29 | p(then.Equal(now)) 30 | // 方法 Sub 返回一个 Duration 来表示两个时间点的间隔时间。 31 | diff := now.Sub(then) 32 | p(diff) 33 | // 我们计算出不同单位下的时间长度值。 34 | p(diff.Hours()) 35 | p(diff.Minutes()) 36 | p(diff.Seconds()) 37 | p(diff.Nanoseconds()) 38 | // 你可以使用 Add 将时间后移一个时间间隔,或者使用一个 - 来将时间前移一个时间间隔。 39 | p(then.Add(diff)) 40 | p(then.Add(-diff)) 41 | 42 | p("################") 43 | // 格式化 44 | // 这里是一个基本的按照 RFC3339 进行格式化的例子,使用对应模式常量。 45 | t := time.Now() 46 | p(t.Format(time.RFC3339)) 47 | // 时间解析使用同 Format 相同的形式值。 48 | t1, e := time.Parse( 49 | time.RFC3339, 50 | "2012-11-01T22:08:41+00:00") 51 | p(t1) 52 | // ormat 和 Parse 使用基于例子的形式来决定日期格式, 53 | // 一般你只要使用 time 包中提供的模式常量就行了,但是你也可以实现自定义模式。 54 | // 模式必须使用时间 Mon Jan 2 15:04:05 MST 2006来指定给定时间/字符串的格式化/解析方式。 55 | // 时间一定要按照如下所示:2006为年,15 为小时,Monday 代表星期几,等等。 56 | p(t.Format("3:04PM")) 57 | p(t.Format("Mon Jan _2 15:04:05 2006")) 58 | p(t.Format("2006-01-02T15:04:05.999999-07:00")) 59 | form := "3 04 PM" 60 | t2, e := time.Parse(form, "8 41 PM") 61 | p(t2) 62 | // 对于纯数字表示的时间,你也可以使用标准的格式化字符串来提出出时间值得组成。 63 | fmt.Printf("%d-%02d-%02dT%02d:%02d:%02d-00:00\n", 64 | t.Year(), t.Month(), t.Day(), 65 | t.Hour(), t.Minute(), t.Second()) 66 | // Parse 函数在输入的时间格式不正确是会返回一个错误。 67 | ansic := "Mon Jan _2 15:04:05 2006" 68 | _, e = time.Parse(ansic, "8:41PM") 69 | p(e) 70 | } 71 | -------------------------------------------------------------------------------- /12.tools/05.json.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "encoding/json" 4 | import "fmt" 5 | import "os" 6 | 7 | // 下面我们将使用这两个结构体来演示自定义类型的编码和解码。 8 | type Response1 struct { 9 | Page int 10 | Fruits []string 11 | } 12 | type Response2 struct { 13 | Page int `json:"page"` 14 | Fruits []string `json:"fruits"` 15 | } 16 | 17 | func main() { 18 | // 首先我们来看一下基本数据类型到 JSON 字符串的编码过程。这里是一些原子值的例子。 19 | bolB, _ := json.Marshal(true) 20 | fmt.Println(string(bolB)) 21 | intB, _ := json.Marshal(1) 22 | fmt.Println(string(intB)) 23 | fltB, _ := json.Marshal(2.34) 24 | fmt.Println(string(fltB)) 25 | strB, _ := json.Marshal("gopher") 26 | fmt.Println(string(strB)) 27 | // 这里是一些切片和 map 编码成 JSON 数组和对象的例子。 28 | slcD := []string{"apple", "peach", "pear"} 29 | slcB, _ := json.Marshal(slcD) 30 | fmt.Println(string(slcB)) 31 | mapD := map[string]int{"apple": 5, "lettuce": 7} 32 | mapB, _ := json.Marshal(mapD) 33 | fmt.Println(string(mapB)) 34 | // JSON 包可以自动的编码你的自定义类型。 35 | // 编码仅输出可导出的字段,并且默认使用他们的名字作为 JSON 数据的键。 36 | res1D := &Response1{ 37 | Page: 1, 38 | Fruits: []string{"apple", "peach", "pear"}} 39 | res1B, _ := json.Marshal(res1D) 40 | fmt.Println(string(res1B)) 41 | // 你可以给结构字段声明标签来自定义编码的 JSON 数据键名称。 42 | // 在上面 Response2 的定义可以作为这个标签这个的一个例子。 43 | res2D := &Response2{ 44 | Page: 1, 45 | Fruits: []string{"apple", "peach", "pear"}} 46 | res2B, _ := json.Marshal(res2D) 47 | fmt.Println(string(res2B)) 48 | // 现在来看看解码 JSON 数据为 Go 值的过程。 49 | // 这里是一个普通数据结构的解码例子。 50 | byt := []byte(`{"num":6.13,"strs":["a","b"]}`) 51 | // 我们需要提供一个 JSON 包可以存放解码数据的变量。 52 | // 这里的 map[string]interface{} 将保存一个 string 为键,值为任意值的map。 53 | var dat map[string]interface{} 54 | // 这里就是实际的解码和相关的错误检查。 55 | if err := json.Unmarshal(byt, &dat); err != nil { 56 | panic(err) 57 | } 58 | fmt.Println(dat) 59 | // 为了使用解码 map 中的值,我们需要将他们进行适当的类型转换。 60 | // 例如这里我们将 num 的值转换成 float64类型。 61 | num := dat["num"].(float64) 62 | fmt.Println(num) 63 | // 访问嵌套的值需要一系列的转化。 64 | strs := dat["strs"].([]interface{}) 65 | str1 := strs[0].(string) 66 | fmt.Println(str1) 67 | // 我们也可以解码 JSON 值到自定义类型。 68 | // 这个功能的好处就是可以为我们的程序带来额外的类型安全加强,并且消除在访问数据时的类型断言。 69 | str := `{"page": 1, "fruits": ["apple", "peach"]}` 70 | res := Response2{} 71 | json.Unmarshal([]byte(str), &res) 72 | fmt.Println(res) 73 | fmt.Println(res.Fruits[0]) 74 | // 在上面的例子中,我们经常使用 byte 和 string 作为使用标准输出时数据和 JSON 表示之间的中间值。 75 | // 我们也可以和os.Stdout 一样,直接将 JSON 编码直接输出至 os.Writer流中,或者作为 HTTP 响应体。 76 | enc := json.NewEncoder(os.Stdout) 77 | d := map[string]int{"apple": 5, "lettuce": 7} 78 | enc.Encode(d) 79 | } 80 | -------------------------------------------------------------------------------- /doc/08.golang函数介绍.md: -------------------------------------------------------------------------------- 1 | # golang函数介绍 2 | 3 | 函数是基本的代码块,用于执行一个任务。 4 | 5 | Go 语言最少有个 main() 函数。 6 | 7 | 你可以通过函数来划分不同功能,逻辑上每个函数执行的是指定的任务。 8 | 9 | 函数声明告诉了编译器函数的名称,返回类型,和参数。 10 | 11 | Go 语言标准库提供了多种可动用的内置的函数。例如,len() 函数可以接受不同类型参数并返回该类型的长度。如果我们传入的是字符串则返回字符串的长度,如果传入的是数组,则返回数组中包含的元素个数。 12 | 13 | ## 函数定义 14 | 15 | Go 语言函数定义格式如下: 16 | 17 | ``` 18 | func function_name( [parameter list] ) [return_types] { 19 | 函数体 20 | } 21 | ``` 22 | 23 | 函数定义解析: 24 | 25 | - func:函数由 func 开始声明 26 | - function_name:函数名称,函数名和参数列表一起构成了函数签名。 27 | - parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。 28 | - return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。 29 | - 函数体:函数定义的代码集合。 30 | 31 | ## 示例 32 | 33 | ```go 34 | package main 35 | 36 | import "fmt" 37 | 38 | func main() { 39 | // hello world 40 | fmt.Println("hello world") 41 | 42 | // 1. 加法 43 | res := plus(1, 2) 44 | fmt.Println("1+2 =", res) 45 | res = plusPlus(1, 2, 3) 46 | fmt.Println("1+2+3 =", res) 47 | 48 | // 2. 多值返回 49 | // 这里我们通过多赋值 操作来使用这两个不同的返回值。 50 | a, b := vals() 51 | fmt.Println(a) 52 | fmt.Println(b) 53 | // 如果你仅仅想返回值的一部分的话,你可以使用空白定义符 _。 54 | _, c := vals() 55 | fmt.Println(c) 56 | 57 | // 3. 可变参数 58 | // 变参函数使用常规的调用方式,除了参数比较特殊。 59 | sum(1, 2) 60 | sum(1, 2, 3) 61 | // 如果你的 slice 已经有了多个值,想把它们作为变参使用,你要这样调用 func(slice...)。 62 | nums := []int{1, 2, 3, 4} 63 | sum(nums...) 64 | 65 | // 4. 闭包 66 | // 我们调用 intSeq 函数,将返回值(也是一个函数)赋给nextInt。 67 | // 这个函数的值包含了自己的值 i,这样在每次调用 nextInt 时都会更新 i 的值。 68 | nextInt := intSeq() 69 | // 通过多次调用 nextInt 来看看闭包的效果。 70 | fmt.Println(nextInt()) 71 | fmt.Println(nextInt()) 72 | fmt.Println(nextInt()) 73 | // 为了确认这个状态对于这个特定的函数是唯一的,我们重新创建并测试一下。 74 | newInts := intSeq() 75 | fmt.Println(newInts()) 76 | 77 | // 5. 递归 78 | fmt.Println(fact(7)) 79 | } 80 | 81 | // 函数 82 | // 这里是一个函数,接受两个 int 并且以 int 返回它们的和 83 | func plus(a int, b int) int { 84 | // Go 需要明确的返回值,例如,它不会自动返回最后一个表达式的值 85 | return a + b 86 | } 87 | 88 | // (int, int) 在这个函数中标志着这个函数返回 2 个 int。 89 | func plusPlus(a, b, c int) int { 90 | return a + b + c 91 | } 92 | 93 | // 多返回值函数 94 | func vals() (int, int) { 95 | return 3, 7 96 | } 97 | 98 | // 变参函数 99 | func sum(nums ...int) { 100 | fmt.Print(nums, " ") 101 | total := 0 102 | for _, num := range nums { 103 | total += num 104 | } 105 | fmt.Println(total) 106 | } 107 | 108 | // 闭包 109 | // 这个 intSeq 函数返回另一个在 intSeq 函数体内定义的匿名函数。 110 | // 这个返回的函数使用闭包的方式 隐藏 变量 i。 111 | func intSeq() func() int { 112 | i := 0 113 | return func() int { 114 | i += 1 115 | return i 116 | } 117 | } 118 | 119 | // 递归 120 | // face 函数在到达 face(0) 前一直调用自身。 121 | func fact(n int) int { 122 | if n == 0 { 123 | return 1 124 | } 125 | return n * fact(n-1) 126 | } 127 | 128 | ``` 129 | 130 | -------------------------------------------------------------------------------- /doc/05.goModules介绍.md: -------------------------------------------------------------------------------- 1 | # 一、Go Modules 2 | 3 | ## 1. 介绍 4 | 5 | Go modules是官方提供的go包管理工具,用于解决go包管理和依赖问题;从1.11开始引入,到现在1.14已经比较完善,1.16已经全面推荐使用,并且默认为开启;Go Modules类似于JS的NPM,Java的maven和gradle。 6 | 7 | - GO111MODULE=off: 不使用 modules 功能。 8 | - GO111MODULE=on: 使用 modules 功能,不会去 GOPATH 下面查找依赖包。 9 | - GO111MODULE=auto: Golang 自己检测是不是使用 modules 功能。 10 | - 计划在 Go 1.17 中放弃对 `GOPATH`将忽略 `GO111MODULE`,需要关注默认下载目录如何设置,此特性静观1.17版本发布; 11 | 12 | ## 2. 关于go.mod 13 | 14 | `go.mod`是Go项目的依赖描述文件: 15 | 16 | ```go 17 | module hello 18 | 19 | go 1.14 20 | 21 | require github.com/gogf/gf v1.15.3 22 | ``` 23 | 24 | 1. module是配置项目名称 25 | 26 | 2. go配置的是使用的golang版本 27 | 28 | 3. require配置引用第三方依赖包路径和版本,latest表示最新版本; 29 | 30 | 配置完编译成功后,生成`go.sum`依赖分析结果,里面会有当前所有的依赖详细信息; 31 | 32 | ## 3. go modules指令 33 | 34 | 1. go get 35 | 36 | ```go 37 | go get -u (没有参数)下载、更新当前包下 直接和间接的依赖的最新版本,并不会更新整个项目。 38 | go get -u ./...下载、更新当前项目根目录下直接或间接的依赖的最新版本,但是会排除测试包的依赖;例如将v1.2.1更新为v2.0.1 39 | go get -u=patch ./...下载、更新当前项目根目录下直接或间接的依赖的大版本的最新小版本,例如,将v1.2.1更新为v1.2.5 40 | go get -u -t ./... 和go get -u ./...相似,但是会更新测试包的依赖 41 | go get -d 只执行下载动作,而不执行安装动作;不再支持go get -m ,使用go get -d替代。 42 | ``` 43 | 44 | 2. go list 45 | 46 | ```go 47 | go list -m all 列出当前项目build时需要使用直接或间接依赖的版本。 48 | go list -u -m all 不仅会列出当前使用模块的版本,还会列出当前使用模块的最新小版本和最新版本。 49 | ``` 50 | 51 | 3. go build ./... 构建当前项目 52 | 53 | 4. go test ./... 执行当前项目下的测试 54 | 55 | 5. go mod 56 | 57 | ```go 58 | go mod tidy 删除不必要的依赖,添加OS, architecture, and build tags组合所需要的依赖。 59 | go mod vendor 可选步骤,用于建立vendor文件夹,用于vendor机制的包管理 60 | go mod init 将go项目初始化成module-mode,使用go modules进行依赖管理。 61 | go mod verify 校验go.sum记录的依赖信息是否正确 62 | ``` 63 | 64 | ## 4. go modules需要注意的地方 65 | 66 | - 在项目根目录下生成`go.mod` 67 | - 项目中的包引用使用`import "[module名称]/[包所在文件在项目中的相对路径]"` 68 | 69 | # 二、Goland配置 70 | 71 | 首先我们需要开启go modules功能,然后配置代理;不配置代理会访问国外地址,会很慢;建议使用以下三个地址: 72 | 73 | - `https://goproxy.io` 74 | - `https://goproxy.cn` 75 | - `https://mirrors.aliyun.com/goproxy/` 76 | 77 | ![image-20200308224453465](05.goModules介绍.assets/image-20200308224453465.png) 78 | 79 | 项目结构: 80 | 81 | ![image-20200308230407947](05.goModules介绍.assets/image-20200308230407947.png) 82 | 83 | 使用上节课我们的hello world程序,创建`go.mod`,内容如下 84 | 85 | ```go 86 | module hello 87 | 88 | go 1.14 89 | 90 | require ( 91 | github.com/gogf/gf v1.15.3 92 | ) 93 | ``` 94 | 95 | 代码内容如下: 96 | 97 | ```go 98 | package main 99 | 100 | import ( 101 | "fmt" 102 | "github.com/gogf/gf" 103 | "github.com/gogf/gf/crypto/gmd5" 104 | ) 105 | 106 | func main(){ 107 | fmt.Println("hello world!") 108 | fmt.Println(gf.VERSION) 109 | fmt.Println(gmd5.EncryptString("123456")) 110 | } 111 | ``` 112 | 113 | 第一次我们需要下载依赖包,可以选择go.mod文件右键选择`Go Mod Tidy` 114 | 115 | 或者点击没下载的包,`alt+enter`键,选择`Sync packages of hello` 116 | 117 | ![image-20200308224919808](05.goModules介绍.assets/image-20200308224919808.png) 118 | 119 | 然后运行程序看到运行结果 120 | 121 | ```go 122 | hello world! 123 | v1.15.3 124 | e10adc3949ba59abbe56e057f20f883e 125 | 126 | Process finished with exit code 0 127 | ``` 128 | 129 | -------------------------------------------------------------------------------- /06.basic/basic.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | // hello world 11 | /** 12 | hello world 13 | */ 14 | fmt.Println("hello world") 15 | 16 | fmt.Println("##################### values") 17 | values() 18 | 19 | fmt.Println("##################### variables") 20 | variables() 21 | 22 | fmt.Println("##################### constants") 23 | constants() 24 | 25 | fmt.Println("##################### forFunc") 26 | forFunc() 27 | 28 | fmt.Println("##################### ifElse") 29 | ifElse() 30 | 31 | fmt.Println("##################### switchFunc") 32 | switchFunc() 33 | } 34 | 35 | // 值 36 | func values() { 37 | // 字符串拼接用 + 38 | fmt.Println("hello " + "world " + "!") 39 | // 整数和浮点数 40 | fmt.Println("1+2 =", 1+2) 41 | fmt.Println("11-1 =", 11-1) 42 | fmt.Println("99*99 =", 99*99) 43 | fmt.Println("8.0/3.0 =", 8.0/3.0) 44 | // 布尔型 45 | fmt.Println(true && false) 46 | fmt.Println(true || false) 47 | fmt.Println(!true) 48 | } 49 | 50 | // 变量 51 | func variables() { 52 | // var 声明 1 个或者多个变量。 53 | var a string = "hello" 54 | fmt.Println(a) 55 | var b, c int = 3, 5 56 | fmt.Println(b, c) 57 | 58 | // 会自动推断已经初始化的变量类型。 59 | var d = true 60 | fmt.Println(d) 61 | 62 | // 声明变量且 初始化为0 63 | var e int 64 | fmt.Println(e) 65 | 66 | // := 简写会自动推断类型,只能用在初始化 67 | f := "short" 68 | fmt.Println(f) 69 | } 70 | 71 | // 常量 72 | // 全局常量 73 | const con = "const" 74 | 75 | func constants() { 76 | fmt.Println(con) 77 | 78 | // const 语句可以出现在任何 var 语句可以出现的地方 79 | const num = 500 * 500 * 500 80 | // 常数表达式可以执行任意精度的运算 81 | const num2 = 4e21 / num 82 | fmt.Println(num2) 83 | // 数值型常量是没有确定的类型的,直到它们被给定了一个类型,比如说一次显示的类型转化。 84 | fmt.Println(int64(num2)) 85 | 86 | // 当上下文需要时,一个数可以被给定一个类型,比如变量赋值或者函数调用。 87 | // 举个例子,这里的 math.Sin函数需要一个 float64 的参数。 88 | fmt.Println(math.Sin(num)) 89 | } 90 | 91 | // For循环 92 | func forFunc() { 93 | // 最常用的方式,带单个循环条件。 94 | i := 1 95 | for i <= 4 { 96 | fmt.Println(i) 97 | i = i + 1 98 | } 99 | 100 | // 经典的初始化/条件/后续形式 for 循环。 101 | for j := 6; j <= 8; j++ { 102 | fmt.Println(j) 103 | } 104 | 105 | // 不带条件的 for 循环将一直执行,直到在循环体内使用了 break 或者 return 来跳出循环。 106 | for { 107 | fmt.Println("for...") 108 | break 109 | } 110 | 111 | for n := 0; n <= 7; n++ { 112 | if n%2 == 0 { 113 | continue 114 | } 115 | fmt.Println(n) 116 | } 117 | } 118 | 119 | // if/else 120 | func ifElse() { 121 | if 9%2 == 0 { 122 | fmt.Println("9 is even") 123 | } else { 124 | fmt.Println("9 is odd") 125 | } 126 | 127 | // 你可以不要 else 只用 if 语句。 128 | if 12%4 == 0 { 129 | fmt.Println("12 is divisible by 4") 130 | } 131 | 132 | // 在条件语句之前可以有一个语句; 133 | // 任何在这里声明的变量都可以在所有的条件分支中使用。 134 | if num := 7; num < 0 { 135 | fmt.Println(num, "正数") 136 | } else if num < 10 { 137 | fmt.Println(num, "小于10") 138 | } else { 139 | fmt.Println(num, "其他") 140 | } 141 | 142 | // 注意,在 Go 中,你可以不适用圆括号,但是花括号是需要的。 143 | // Go 里没有三目运算符, 144 | // 所以即使你只需要基本的条件判断,你仍需要使用完整的 if 语句。 145 | } 146 | 147 | // 分支结构 148 | func switchFunc() { 149 | i := 2 150 | switch i { 151 | case 1: 152 | fmt.Println("1") 153 | case 2: 154 | fmt.Println("2") 155 | case 3: 156 | fmt.Println("3") 157 | } 158 | 159 | // 在一个 case 语句中,你可以使用逗号来分隔多个表达式。 160 | // 在这个例子中,我们很好的使用了可选的default 分支。 161 | switch time.Now().Weekday() { 162 | case time.Saturday, time.Sunday: 163 | fmt.Println("星期天") 164 | default: 165 | fmt.Println("工作日") 166 | } 167 | 168 | // 不带表达式的 switch 是实现 if/else 逻辑的另一种方式。 169 | // 这里展示了 case 表达式是如何使用非常量的。 170 | t := time.Now() 171 | switch { 172 | case t.Hour() < 12: 173 | fmt.Println("12点前") 174 | default: 175 | fmt.Println("12点后,包含12点") 176 | } 177 | 178 | // 这里是一个函数变量 179 | whatAmI := func(i interface{}) { 180 | switch t := i.(type) { 181 | case bool: 182 | fmt.Println("bool") 183 | case int: 184 | fmt.Println("int") 185 | default: 186 | fmt.Printf("什么类型 %T\n", t) 187 | } 188 | } 189 | whatAmI(true) 190 | whatAmI(1) 191 | whatAmI("嘿") 192 | } 193 | -------------------------------------------------------------------------------- /doc/10.golang的错误处理.md: -------------------------------------------------------------------------------- 1 | # Go 错误处理 2 | 3 | ## 一、错误 4 | 5 | 在Go中有一部分函数总是能成功的运行。比如strings.Contains和strconv.FormatBool函数;对于大部分函数而言,永远无法确保能否成功运行。 6 | 7 | Go 语言通过内置的错误接口提供了非常简单的错误处理机制。 8 | 9 | error类型是一个接口类型,这是它的定义: 10 | 11 | ```go 12 | type error interface { 13 | Error() string 14 | } 15 | ``` 16 | 17 | 我们可以在编码中通过实现 error 接口类型来生成错误信息。 18 | 19 | 函数通常在最后的返回值中返回错误信息。使用errors.New 可返回一个错误信息: 20 | 21 | ```go 22 | func Sqrt(f float64) (float64, error) { 23 | if f < 0 { 24 | return 0, errors.New("math: square root of negative number") 25 | } 26 | // 实现 27 | } 28 | ``` 29 | 30 | 这里有一个错误处理的例子: 31 | 32 | ```go 33 | package main 34 | 35 | import "errors" 36 | import "fmt" 37 | 38 | // 按照惯例,错误通常是最后一个返回值并且是 error 类型,一个内建的接口。 39 | func f1(arg int) (int, error) { 40 | // errors.New 构造一个使用给定的错误信息的基本error 值。 41 | if arg == 42 { 42 | return -1, errors.New("can't work with 42") 43 | } 44 | // 返回错误值为 nil 代表没有错误。 45 | return arg + 3, nil 46 | } 47 | 48 | // 通过实现 Error 方法来自定义 error 类型是可以的。 49 | // 这里使用自定义错误类型来表示上面的参数错误。 50 | type argError struct { 51 | arg int 52 | prob string 53 | } 54 | 55 | func (e *argError) Error() string { 56 | return fmt.Sprintf("%d - %s", e.arg, e.prob) 57 | } 58 | func f2(arg int) (int, error) { 59 | if arg == 42 { 60 | // 在这个例子中,我们使用 &argError 语法来建立一个新的结构体,并提供了 arg 和 prob 这个两个字段的值。 61 | return -1, &argError{arg, "can't work with it"} 62 | } 63 | return arg + 3, nil 64 | } 65 | func main() { 66 | // 下面的两个循环测试了各个返回错误的函数。 67 | // 注意在 if行内的错误检查代码,在 Go 中是一个普遍的用法。 68 | for _, i := range []int{7, 42} { 69 | if r, e := f1(i); e != nil { 70 | fmt.Println("f1 失败:", e) 71 | } else { 72 | fmt.Println("f1 工作:", r) 73 | } 74 | } 75 | for _, i := range []int{7, 42} { 76 | if r, e := f2(i); e != nil { 77 | fmt.Println("f2 失败:", e) 78 | } else { 79 | fmt.Println("f2 工作:", r) 80 | } 81 | } 82 | // 你如果想在程序中使用一个自定义错误类型中的数据,你需要通过类型断言来得到这个错误类型的实例。 83 | _, e := f2(42) 84 | if ae, ok := e.(*argError); ok { 85 | fmt.Println(ae.arg) 86 | fmt.Println(ae.prob) 87 | } 88 | } 89 | ``` 90 | 91 | ## 二、Deferred函数 92 | 93 | defer语句经常被用于处理成对的操作,如打开、关闭、连接、断开连接、加锁、释放锁。通过defer机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放。释放资源的defer应该直接跟在请求资源的语句后。 94 | 95 | 示例: 96 | 97 | ```go 98 | package main 99 | 100 | import "fmt" 101 | import "os" 102 | 103 | func main() { 104 | // 假设我们想要创建一个文件,向它进行写操作,然后在结束时关闭它。 105 | // 这里展示了如何通过 defer 来做到这一切。 106 | f := createFile("D:/defer.txt") // f := createFile("/tmp/defer.txt") 107 | // 在 closeFile 后得到一个文件对象,我们使用 defer通过 closeFile 来关闭这个文件。这会在封闭函数(main)结束时执行,就是 writeFile 结束后。 108 | defer closeFile(f) 109 | writeFile(f) 110 | } 111 | func createFile(p string) *os.File { 112 | fmt.Println("creating") 113 | f, err := os.Create(p) 114 | if err != nil { 115 | panic(err) 116 | } 117 | return f 118 | } 119 | func writeFile(f *os.File) { 120 | fmt.Println("writing") 121 | fmt.Fprintln(f, "data") 122 | } 123 | func closeFile(f *os.File) { 124 | fmt.Println("closing") 125 | f.Close() 126 | } 127 | ``` 128 | 129 | 130 | 131 | ## 三、异常 132 | 133 | Go的类型系统会在编译时捕获很多错误,但有些错误只能在运行时检查,如数组访问越界、空指针引用等。这些运行时错误会引起painc异常。 134 | 135 | 示例如下: 136 | 137 | ```go 138 | package main 139 | 140 | import ( 141 | "fmt" 142 | "os" 143 | ) 144 | 145 | func main() { 146 | // 我们将在这个网站中使用 panic 来检查预期外的错误。这个是唯一一个为 panic 准备的例子。 147 | panic("一个异常") 148 | 149 | // panic 的一个基本用法就是在一个函数返回了错误值但是我们并不知道(或者不想)处理时终止运行。 150 | // 这里是一个在创建一个新文件时返回异常错误时的panic 用法。 151 | fmt.Println("继续") 152 | _, err := os.Create("/tmp/file") 153 | if err != nil { 154 | panic(err) 155 | } 156 | // 运行程序将会引起 panic,输出一个错误消息和 Go 运行时栈信息,并且返回一个非零的状态码。 157 | } 158 | ``` 159 | 160 | ## 四、捕获异常 161 | 162 | 通常来说,不应该对panic异常做任何处理,但有时,也许我们可以从异常中恢复,至少我们可以在程序崩溃前,做一些操作。举个例子,当web服务器遇到不可预料的严重问题时,在崩溃前应该将所有的连接关闭;如果不做任何处理,会使得客户端一直处于等待状态。如果web服务器还在开发阶段,服务器甚至可以将异常信息反馈到客户端,帮助调试。 163 | 164 | 如果在deferred函数中调用了内置函数recover,并且定义该defer语句的函数发生了panic异常,recover会使程序从panic中恢复,并返回panic value。导致panic异常的函数不会继续运行,但能正常返回。在未发生panic时调用recover,recover会返回nil。 165 | 166 | 示例: 167 | 168 | ```go 169 | package main 170 | 171 | import "fmt" 172 | 173 | func main() { 174 | // 这里我们对异常进行了捕获 175 | defer func() { 176 | if p := recover(); p != nil { 177 | err := fmt.Errorf("internal error: %v", p) 178 | if err != nil { 179 | fmt.Println(err) 180 | } 181 | } 182 | }() 183 | 184 | // 我们将在这个网站中使用 panic 来检查预期外的错误。这个是唯一一个为 panic 准备的例子。 185 | panic("一个异常") 186 | 187 | } 188 | ``` 189 | 190 | -------------------------------------------------------------------------------- /07.data/data.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | // hello world 7 | fmt.Println("hello world") 8 | 9 | fmt.Println("##################### arrays") 10 | arrays() 11 | 12 | fmt.Println("##################### slice") 13 | slice() 14 | 15 | fmt.Println("##################### mapFunc") 16 | mapFunc() 17 | 18 | fmt.Println("##################### rangeFunc") 19 | rangeFunc() 20 | } 21 | 22 | // 数组 23 | func arrays() { 24 | // 这里我们创建了一个数组 test1 来存放刚好 5 个 int。 25 | // 元素的类型和长度都是数组类型的一部分。 26 | // 数组默认是零值的,对于 int 数组来说也就是 0。 27 | var test1 [6]int 28 | fmt.Println("内容:", test1) 29 | // 我们可以使用 array[index] = value 语法来设置数组指定位置的值,或者用 array[index] 得到值。 30 | test1[4] = 100 31 | fmt.Println("设置:", test1) 32 | fmt.Println("获取:", test1[4]) 33 | // 使用内置函数 len 返回数组的长度 34 | fmt.Println("长度:", len(test1)) 35 | 36 | // 使用这个语法在一行内初始化一个数组 37 | test2 := [6]int{1, 2, 3, 4, 5, 6} 38 | fmt.Println("数据:", test2) 39 | 40 | // 数组的存储类型是单一的,但是你可以组合这些数据来构造多维的数据结构。 41 | var twoTest [3][4]int 42 | for i := 0; i < 3; i++ { 43 | for j := 0; j < 4; j++ { 44 | twoTest[i][j] = i + j 45 | } 46 | } 47 | // 注意,在使用 fmt.Println 来打印数组的时候,会使用[v1 v2 v3 ...] 的格式显示 48 | fmt.Println("二维: ", twoTest) 49 | } 50 | 51 | // 切片 52 | func slice() { 53 | // Slice 是 Go 中一个关键的数据类型,是一个比数组更加强大的序列接口 54 | 55 | // 不像数组,slice 的类型仅由它所包含的元素决定(不像数组中还需要元素的个数)。 56 | // 要创建一个长度非零的空slice,需要使用内建的方法 make。 57 | // 这里我们创建了一个长度为3的 string 类型 slice(初始化为零值)。 58 | test1 := make([]string, 3) 59 | fmt.Println("数据:", test1) 60 | // 我们可以和数组一样设置和得到值 61 | test1[0] = "A" 62 | test1[1] = "C" 63 | test1[2] = "B" 64 | fmt.Println("数据:", test1) 65 | fmt.Println("获取:", test1[2]) 66 | // 如你所料,len 返回 slice 的长度 67 | fmt.Println("长度:", len(test1)) 68 | 69 | // 作为基本操作的补充,slice 支持比数组更多的操作。 70 | // 其中一个是内建的 append,它返回一个包含了一个或者多个新值的 slice。 71 | // 注意我们接受返回由 append返回的新的 slice 值。 72 | test1 = append(test1, "D") 73 | test1 = append(test1, "E", "F") 74 | fmt.Println("追加:", test1) 75 | 76 | // Slice 也可以被 copy。这里我们创建一个空的和 test1 有相同长度的 slice test2,并且将 test1 复制给 test2。 77 | test2 := make([]string, len(test1)) 78 | copy(test2, test1) 79 | fmt.Println("拷贝:", test2) 80 | // Slice 支持通过 slice[low:high] 语法进行“切片”操作。例如,这里得到一个包含元素 test1[2], test1[3],test1[4] 的 slice。 81 | l := test1[2:5] 82 | fmt.Println("切片1:", l) 83 | // 这个 slice 从 test1[0] 到(但是不包含)test1[5]。 84 | l = test1[:5] 85 | fmt.Println("切片2:", l) 86 | // 这个 slice 从(包含)test1[2] 到 slice 的后一个值。 87 | l = test1[2:] 88 | fmt.Println("切片3:", l) 89 | // 我们可以在一行代码中声明并初始化一个 slice 变量。 90 | t := []string{"g", "h", "i"} 91 | fmt.Println("数据:", t) 92 | 93 | // Slice 可以组成多维数据结构。内部的 slice 长度可以不同,这和多位数组不同。 94 | twoTest := make([][]int, 3) 95 | for i := 0; i < 3; i++ { 96 | innerLen := i + 1 97 | twoTest[i] = make([]int, innerLen) 98 | for j := 0; j < innerLen; j++ { 99 | twoTest[i][j] = i + j 100 | } 101 | } 102 | // 注意,slice 和数组不同,虽然它们通过 fmt.Println 输出差不多。 103 | fmt.Println("二维: ", twoTest) 104 | } 105 | 106 | // 键值对 key/value 107 | func mapFunc() { 108 | // 要创建一个空 map,需要使用内建的 make:make(map[key-type]val-type). 109 | map1 := make(map[string]int) 110 | // 使用典型的 make[key] = val 语法来设置键值对。 111 | map1["k1"] = 7 112 | map1["k2"] = 13 113 | // 使用例如 Println 来打印一个 map 将会输出所有的键值对。 114 | fmt.Println("数据:", map1) 115 | // 使用 name[key] 来获取一个键的值 116 | v1 := map1["k1"] 117 | fmt.Println("值: ", v1) 118 | // 当对一个 map 调用内建的 len 时,返回的是键值对数目 119 | fmt.Println("长度:", len(map1)) 120 | // 内建的 delete 可以从一个 map 中移除键值对 121 | delete(map1, "k2") 122 | fmt.Println("数据:", map1) 123 | // 当从一个 map 中取值时,可选的第二返回值指示这个键是在这个 map 中。 124 | // 这可以用来消除键不存在和键有零值,像 0 或者 "" 而产生的歧义。 125 | _, prs := map1["k2"] 126 | fmt.Println("是否存在:", prs) 127 | // 你也可以通过这个语法在同一行申明和初始化一个新的map。 128 | map2 := map[string]int{"F": 1, "B": 2} 129 | // 注意一个 map 在使用 fmt.Println 打印的时候,是以 map[k:v k:v]的格式输出的。 130 | fmt.Println("数据:", map2) 131 | } 132 | 133 | // Range 遍历 134 | func rangeFunc() { 135 | // 这里我们使用 range 来统计一个 slice 的元素个数。数组也可以采用这种方法。 136 | array1 := []int{2, 3, 4} 137 | sum := 0 138 | for _, num := range array1 { 139 | sum += num 140 | } 141 | fmt.Println("求和:", sum) 142 | 143 | // range 在数组和 slice 中都同样提供每个项的索引和值。 144 | // 上面我们不需要索引,所以我们使用 空值定义符_ 来忽略它。 145 | // 有时候我们实际上是需要这个索引的。 146 | for i, num := range array1 { 147 | if num == 3 { 148 | fmt.Println("索引:", i) 149 | } 150 | } 151 | 152 | // range 在 map 中迭代键值对。 153 | map1 := map[string]string{"A": "苹果", "B": "香蕉"} 154 | for k, v := range map1 { 155 | fmt.Printf("%s -> %s\n", k, v) 156 | } 157 | for k := range map1 { 158 | fmt.Println("键:", k) 159 | } 160 | 161 | // range 在字符串中迭代 unicode 编码。 162 | // 第一个返回值是rune 的起始字节位置,然后第二个是 rune 自己。 163 | for i, c := range "abA" { 164 | fmt.Println(i, c) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /doc/09.golang指针结构体接口.md: -------------------------------------------------------------------------------- 1 | # golang的指针\结构体\接口 2 | 3 | ## **一、什么是指针** 4 | 5 | C语言里,变量存放在内存中,而**内存其实就是一组有序字节组成的数组**,每个字节有唯一的内存地址。CPU 通过内存寻址对存储在内存中的某个指定数据对象的地址进行定位。这里,数据对象是指存储在内存中的一个指定数据类型的数值或字符串,它们都有一个自己的地址,而指针便是保存这个地址的变量。也就是说:**指针是一种保存变量地址的变量**。 6 | 7 | ![img](09.golang指针结构体接口.assets/765389-20180918115659254-712651853.png) 8 | 9 | ## 二、Go 指针 10 | 11 | 指针如何定义: 12 | 13 | ```go 14 | var ip *int /* 指向整型*/ 15 | var fp *float32 /* 指向浮点型 */ 16 | ``` 17 | 18 | 指针使用流程: 19 | 20 | - 定义指针变量。 21 | - 为指针变量赋值。 22 | - 访问指针变量中指向地址的值。 23 | 24 | 示例: 25 | 26 | ```go 27 | package main 28 | 29 | import "fmt" 30 | 31 | // 我们将通过两个函数:val 和 ptr 来比较指针和值类型的不同。 32 | // val 有一个 int 型参数,所以使用值传递。 33 | // val 将从调用它的那个函数中得到一个 val1 形参的拷贝。 34 | func val(val1 int) { 35 | val1 = 0 36 | } 37 | 38 | // ptr 有一和上面不同的 *int 参数,意味着它用了一个 int指针。 39 | // 函数体内的 *iptr 接着解引用 这个指针,从它内存地址得到这个地址对应的当前值。 40 | // 对一个解引用的指针赋值将会改变这个指针引用的真实地址的值。 41 | func ptr(iptr *int) { 42 | *iptr = 0 43 | } 44 | 45 | func main() { 46 | test := 1 47 | fmt.Println("initial:", test) 48 | val(test) 49 | fmt.Println("val:", test) 50 | // 通过 &test 语法来取得 test 的内存地址,例如一个变量i 的指针。 51 | ptr(&test) 52 | fmt.Println("ptr:", test) 53 | // 指针也是可以被打印的。 54 | fmt.Println("pointer:", &test) 55 | // val 在 main 函数中不能改变 test 的值,但是zeroptr 可以,因为它有一个这个变量的内存地址的引用。 56 | fmt.Println("pointer:", *&test) 57 | } 58 | ``` 59 | 60 | ## 三、Go 空指针 61 | 62 | 当一个指针被定义后没有分配到任何变量时,它的值为 nil。 63 | 64 | nil 指针也称为空指针。 65 | 66 | nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。 67 | 68 | ## 四、什么结构体 69 | 70 | Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。 71 | 72 | 结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。 73 | 74 | 结构体表示一项记录,比如保存图书馆的书籍记录,每本书有以下属性: 75 | 76 | - Title :标题 77 | - Author : 作者 78 | - Subject:学科 79 | - ID:书籍ID 80 | 81 | ## 五、定义结构体 82 | 83 | 结构体定义需要使用 type 和 struct 语句。struct 语句定义一个新的数据类型,结构体中有一个或多个成员。type 语句设定了结构体的名称。结构体的格式如下: 84 | 85 | ``` 86 | type struct_variable_type struct { 87 | member definition 88 | member definition 89 | ... 90 | member definition 91 | } 92 | ``` 93 | 94 | 下面我们看下示例: 95 | 96 | ```go 97 | package main 98 | 99 | import "fmt" 100 | 101 | // 这里的 person 结构体包含了 name 和 age 两个字段。 102 | type person struct { 103 | name string 104 | age int 105 | } 106 | 107 | func main() { 108 | // 使用这个语法创建了一个新的结构体元素。 109 | fmt.Println(person{"Bob", 20}) 110 | // 你可以在初始化一个结构体元素时指定字段名字。 111 | fmt.Println(person{name: "Alice", age: 30}) 112 | // 省略的字段将被初始化为零值。 113 | fmt.Println(person{name: "Fred"}) 114 | // & 前缀生成一个结构体指针。 115 | fmt.Println(&person{name: "Ann", age: 40}) 116 | // 使用点来访问结构体字段。 117 | s := person{name: "Sean", age: 50} 118 | fmt.Println(s.name) 119 | // 也可以对结构体指针使用. - 指针会被自动解引用。 120 | sp := &s 121 | fmt.Println(sp.age) 122 | // 结构体是可变的。 123 | sp.age = 51 124 | fmt.Println(sp.age) 125 | } 126 | ``` 127 | 128 | ## 六、结构体方法 129 | 130 | 结构体即为对象,对象的行为可以称之为方法;比如人可以走,手、脚为人的属性,走位人的方法;我们看下面形状的例子: 131 | 132 | ```go 133 | package main 134 | 135 | import "fmt" 136 | 137 | type rectangle struct { 138 | width, height int 139 | } 140 | 141 | // 这里的 area 方法有一个接收器类型 rect。 142 | func (r *rectangle) area() int { 143 | return r.width * r.height 144 | } 145 | 146 | // 可以为值类型或者指针类型的接收器定义方法。这里是一个值类型接收器的例子。 147 | func (r rectangle) perim() int { 148 | return 2*r.width + 2*r.height 149 | } 150 | func main() { 151 | r := rectangle{width: 10, height: 5} 152 | // 这里我们调用上面为结构体定义的两个方法。 153 | fmt.Println("area: ", r.area()) 154 | fmt.Println("perim:", r.perim()) 155 | // Go 自动处理方法调用时的值和指针之间的转化。 156 | // 你可以使用指针来调用方法来避免在方法调用时产生一个拷贝,或者让方法能够改变接受的数据。 157 | rp := &r 158 | fmt.Println("area: ", rp.area()) 159 | fmt.Println("perim:", rp.perim()) 160 | } 161 | ``` 162 | 163 | ## 七、接口定义 164 | 165 | Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。 166 | 167 | 接口其实就是物体抽象的定义,实际用才会有体会,示例 168 | 169 | ```go 170 | package main 171 | 172 | import "fmt" 173 | import "math" 174 | 175 | // 这里是一个几何体的基本接口。 176 | type geometry interface { 177 | area() float64 178 | perim() float64 179 | } 180 | 181 | // 在我们的例子中,我们将让 rect 和 circle 实现这个接口 182 | type rect struct { 183 | width, height float64 184 | } 185 | type circle struct { 186 | radius float64 187 | } 188 | 189 | // 要在 Go 中实现一个接口,我们只需要实现接口中的所有方法。 190 | // 这里我们让 rect 实现了 geometry 接口。 191 | func (r rect) area() float64 { 192 | return r.width * r.height 193 | } 194 | func (r rect) perim() float64 { 195 | return 2*r.width + 2*r.height 196 | } 197 | 198 | // circle 的实现。 199 | func (c circle) area() float64 { 200 | return math.Pi * c.radius * c.radius 201 | } 202 | func (c circle) perim() float64 { 203 | return 2 * math.Pi * c.radius 204 | } 205 | 206 | // 如果一个变量的是接口类型,那么我们可以调用这个被命名的接口中的方法。 207 | // 这里有一个一通用的 measure 函数,利用这个特性,它可以用在任何 geometry 上。 208 | func measure(g geometry) { 209 | fmt.Println(g) 210 | fmt.Println(g.area()) 211 | fmt.Println(g.perim()) 212 | } 213 | func main() { 214 | r := rect{width: 3, height: 4} 215 | c := circle{radius: 5} 216 | // 结构体类型 circle 和 rect 都实现了 geometry接口, 217 | // 所以我们可以使用它们的实例作为 measure 的参数。 218 | measure(r) 219 | measure(c) 220 | } 221 | ``` 222 | 223 | -------------------------------------------------------------------------------- /doc/06.golang基础语法.md: -------------------------------------------------------------------------------- 1 | # golang基础语法 2 | 3 | Go 程序可以由多个标记组成,可以是关键字,标识符,常量,字符串,符号。 4 | 5 | Go 语言的基础组成有以下几个部分: 6 | 7 | - 包声明 8 | - 引入包 9 | - 函数 10 | - 变量 11 | - 语句 & 表达式 12 | - 注释 13 | 14 | ## 一、基本语法介绍 15 | 16 | ### 行分隔符 17 | 18 | 在 Go 程序中,一行代表一个语句结束。不像其它语言一样以分号 ; 结尾,因为这些工作都将由 Go 编译器自动完成。 19 | 20 | ### 注释 21 | 22 | 单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾。 23 | 24 | ### 字符串连接 25 | 26 | Go 语言的字符串可以通过 **+** 实现: 27 | 28 | ### 关键字 29 | 30 | 下面列举了 Go 代码中会使用到的 25 个关键字或保留字: 31 | 32 | | break | default | func | interface | select | 33 | | -------- | ----------- | ------ | --------- | ------ | 34 | | case | defer | go | map | struct | 35 | | chan | else | goto | package | switch | 36 | | const | fallthrough | if | range | type | 37 | | continue | for | import | return | var | 38 | 39 | 程序一般由关键字、常量、变量、运算符、类型和函数组成。 40 | 41 | 程序中可能会使用到这些分隔符:括号 (),中括号 [] 和大括号 {}。 42 | 43 | 程序中可能会使用到这些标点符号:.、,、;、: 和 …。 44 | 45 | ### 二、示例代码 46 | 47 | ```go 48 | package main 49 | 50 | import ( 51 | "fmt" 52 | "math" 53 | "time" 54 | ) 55 | 56 | func main() { 57 | // hello world 58 | /** 59 | hello world 60 | */ 61 | fmt.Println("hello world") 62 | 63 | fmt.Println("##################### values") 64 | values() 65 | 66 | fmt.Println("##################### variables") 67 | variables() 68 | 69 | fmt.Println("##################### constants") 70 | constants() 71 | 72 | fmt.Println("##################### forFunc") 73 | forFunc() 74 | 75 | fmt.Println("##################### ifElse") 76 | ifElse() 77 | 78 | fmt.Println("##################### switchFunc") 79 | switchFunc() 80 | } 81 | 82 | // 值 83 | func values() { 84 | // 字符串拼接用 + 85 | fmt.Println("hello " + "world " + "!") 86 | // 整数和浮点数 87 | fmt.Println("1+2 =", 1+2) 88 | fmt.Println("11-1 =", 11-1) 89 | fmt.Println("99*99 =", 99*99) 90 | fmt.Println("8.0/3.0 =", 8.0/3.0) 91 | // 布尔型 92 | fmt.Println(true && false) 93 | fmt.Println(true || false) 94 | fmt.Println(!true) 95 | } 96 | 97 | // 变量 98 | func variables() { 99 | // var 声明 1 个或者多个变量。 100 | var a string = "hello" 101 | fmt.Println(a) 102 | var b, c int = 3, 5 103 | fmt.Println(b, c) 104 | 105 | // 会自动推断已经初始化的变量类型。 106 | var d = true 107 | fmt.Println(d) 108 | 109 | // 声明变量且 初始化为0 110 | var e int 111 | fmt.Println(e) 112 | 113 | // := 简写会自动推断类型,只能用在初始化 114 | f := "short" 115 | fmt.Println(f) 116 | } 117 | 118 | // 常量 119 | // 全局常量 120 | const con = "const" 121 | 122 | func constants() { 123 | fmt.Println(con) 124 | 125 | // const 语句可以出现在任何 var 语句可以出现的地方 126 | const num = 500 * 500 * 500 127 | // 常数表达式可以执行任意精度的运算 128 | const num2 = 4e21 / num 129 | fmt.Println(num2) 130 | // 数值型常量是没有确定的类型的,直到它们被给定了一个类型,比如说一次显示的类型转化。 131 | fmt.Println(int64(num2)) 132 | 133 | // 当上下文需要时,一个数可以被给定一个类型,比如变量赋值或者函数调用。 134 | // 举个例子,这里的 math.Sin函数需要一个 float64 的参数。 135 | fmt.Println(math.Sin(num)) 136 | } 137 | 138 | // For循环 139 | func forFunc() { 140 | // 最常用的方式,带单个循环条件。 141 | i := 1 142 | for i <= 4 { 143 | fmt.Println(i) 144 | i = i + 1 145 | } 146 | 147 | // 经典的初始化/条件/后续形式 for 循环。 148 | for j := 6; j <= 8; j++ { 149 | fmt.Println(j) 150 | } 151 | 152 | // 不带条件的 for 循环将一直执行,直到在循环体内使用了 break 或者 return 来跳出循环。 153 | for { 154 | fmt.Println("for...") 155 | break 156 | } 157 | 158 | for n := 0; n <= 7; n++ { 159 | if n%2 == 0 { 160 | continue 161 | } 162 | fmt.Println(n) 163 | } 164 | } 165 | 166 | // if/else 167 | func ifElse() { 168 | if 9%2 == 0 { 169 | fmt.Println("9 is even") 170 | } else { 171 | fmt.Println("9 is odd") 172 | } 173 | 174 | // 你可以不要 else 只用 if 语句。 175 | if 12%4 == 0 { 176 | fmt.Println("12 is divisible by 4") 177 | } 178 | 179 | // 在条件语句之前可以有一个语句; 180 | // 任何在这里声明的变量都可以在所有的条件分支中使用。 181 | if num := 7; num < 0 { 182 | fmt.Println(num, "正数") 183 | } else if num < 10 { 184 | fmt.Println(num, "小于10") 185 | } else { 186 | fmt.Println(num, "其他") 187 | } 188 | 189 | // 注意,在 Go 中,你可以不适用圆括号,但是花括号是需要的。 190 | // Go 里没有三目运算符, 191 | // 所以即使你只需要基本的条件判断,你仍需要使用完整的 if 语句。 192 | } 193 | 194 | // 分支结构 195 | func switchFunc() { 196 | i := 2 197 | switch i { 198 | case 1: 199 | fmt.Println("1") 200 | case 2: 201 | fmt.Println("2") 202 | case 3: 203 | fmt.Println("3") 204 | } 205 | 206 | // 在一个 case 语句中,你可以使用逗号来分隔多个表达式。 207 | // 在这个例子中,我们很好的使用了可选的default 分支。 208 | switch time.Now().Weekday() { 209 | case time.Saturday, time.Sunday: 210 | fmt.Println("星期天") 211 | default: 212 | fmt.Println("工作日") 213 | } 214 | 215 | // 不带表达式的 switch 是实现 if/else 逻辑的另一种方式。 216 | // 这里展示了 case 表达式是如何使用非常量的。 217 | t := time.Now() 218 | switch { 219 | case t.Hour() < 12: 220 | fmt.Println("12点前") 221 | default: 222 | fmt.Println("12点后,包含12点") 223 | } 224 | 225 | // 这里是一个函数变量 226 | whatAmI := func(i interface{}) { 227 | switch t := i.(type) { 228 | case bool: 229 | fmt.Println("bool") 230 | case int: 231 | fmt.Println("int") 232 | default: 233 | fmt.Printf("什么类型 %T\n", t) 234 | } 235 | } 236 | whatAmI(true) 237 | whatAmI(1) 238 | whatAmI("嘿") 239 | } 240 | ``` 241 | 242 | -------------------------------------------------------------------------------- /doc/07.golang常用数据结构.md: -------------------------------------------------------------------------------- 1 | # golang常用数据类型 2 | 3 | ## 一、常用数据类型介绍 4 | 5 | Go 语言提供了数组,切片Slice,集合Map以及循环遍历Range; 6 | 7 | ### 数组 8 | 9 | 数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整型、字符串或者自定义类型。 10 | 11 | ### 切片(Slice) 12 | 13 | Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。 14 | 15 | ### 集合(Map) 16 | 17 | Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。 18 | 19 | ### 循环遍历(Range) 20 | 21 | Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。 22 | 23 | ## 二、示例 24 | 25 | ```go 26 | package main 27 | 28 | import "fmt" 29 | 30 | func main() { 31 | // hello world 32 | fmt.Println("hello world") 33 | 34 | fmt.Println("##################### arrays") 35 | arrays() 36 | 37 | fmt.Println("##################### slice") 38 | slice() 39 | 40 | fmt.Println("##################### mapFunc") 41 | mapFunc() 42 | 43 | fmt.Println("##################### rangeFunc") 44 | rangeFunc() 45 | } 46 | 47 | // 数组 48 | func arrays() { 49 | // 这里我们创建了一个数组 test1 来存放刚好 5 个 int。 50 | // 元素的类型和长度都是数组类型的一部分。 51 | // 数组默认是零值的,对于 int 数组来说也就是 0。 52 | var test1 [6]int 53 | fmt.Println("内容:", test1) 54 | // 我们可以使用 array[index] = value 语法来设置数组指定位置的值,或者用 array[index] 得到值。 55 | test1[4] = 100 56 | fmt.Println("设置:", test1) 57 | fmt.Println("获取:", test1[4]) 58 | // 使用内置函数 len 返回数组的长度 59 | fmt.Println("长度:", len(test1)) 60 | 61 | // 使用这个语法在一行内初始化一个数组 62 | test2 := [6]int{1, 2, 3, 4, 5, 6} 63 | fmt.Println("数据:", test2) 64 | 65 | // 数组的存储类型是单一的,但是你可以组合这些数据来构造多维的数据结构。 66 | var twoTest [3][4]int 67 | for i := 0; i < 3; i++ { 68 | for j := 0; j < 4; j++ { 69 | twoTest[i][j] = i + j 70 | } 71 | } 72 | // 注意,在使用 fmt.Println 来打印数组的时候,会使用[v1 v2 v3 ...] 的格式显示 73 | fmt.Println("二维: ", twoTest) 74 | } 75 | 76 | // 切片 77 | func slice() { 78 | // Slice 是 Go 中一个关键的数据类型,是一个比数组更加强大的序列接口 79 | 80 | // 不像数组,slice 的类型仅由它所包含的元素决定(不像数组中还需要元素的个数)。 81 | // 要创建一个长度非零的空slice,需要使用内建的方法 make。 82 | // 这里我们创建了一个长度为3的 string 类型 slice(初始化为零值)。 83 | test1 := make([]string, 3) 84 | fmt.Println("数据:", test1) 85 | // 我们可以和数组一样设置和得到值 86 | test1[0] = "A" 87 | test1[1] = "C" 88 | test1[2] = "B" 89 | fmt.Println("数据:", test1) 90 | fmt.Println("获取:", test1[2]) 91 | // 如你所料,len 返回 slice 的长度 92 | fmt.Println("长度:", len(test1)) 93 | 94 | // 作为基本操作的补充,slice 支持比数组更多的操作。 95 | // 其中一个是内建的 append,它返回一个包含了一个或者多个新值的 slice。 96 | // 注意我们接受返回由 append返回的新的 slice 值。 97 | test1 = append(test1, "D") 98 | test1 = append(test1, "E", "F") 99 | fmt.Println("追加:", test1) 100 | 101 | // Slice 也可以被 copy。这里我们创建一个空的和 test1 有相同长度的 slice test2,并且将 test1 复制给 test2。 102 | test2 := make([]string, len(test1)) 103 | copy(test2, test1) 104 | fmt.Println("拷贝:", test2) 105 | // Slice 支持通过 slice[low:high] 语法进行“切片”操作。例如,这里得到一个包含元素 test1[2], test1[3],test1[4] 的 slice。 106 | l := test1[2:5] 107 | fmt.Println("切片1:", l) 108 | // 这个 slice 从 test1[0] 到(但是不包含)test1[5]。 109 | l = test1[:5] 110 | fmt.Println("切片2:", l) 111 | // 这个 slice 从(包含)test1[2] 到 slice 的后一个值。 112 | l = test1[2:] 113 | fmt.Println("切片3:", l) 114 | // 我们可以在一行代码中声明并初始化一个 slice 变量。 115 | t := []string{"g", "h", "i"} 116 | fmt.Println("数据:", t) 117 | 118 | // Slice 可以组成多维数据结构。内部的 slice 长度可以不同,这和多位数组不同。 119 | twoTest := make([][]int, 3) 120 | for i := 0; i < 3; i++ { 121 | innerLen := i + 1 122 | twoTest[i] = make([]int, innerLen) 123 | for j := 0; j < innerLen; j++ { 124 | twoTest[i][j] = i + j 125 | } 126 | } 127 | // 注意,slice 和数组不同,虽然它们通过 fmt.Println 输出差不多。 128 | fmt.Println("二维: ", twoTest) 129 | } 130 | 131 | // 键值对 key/value 132 | func mapFunc() { 133 | // 要创建一个空 map,需要使用内建的 make:make(map[key-type]val-type). 134 | map1 := make(map[string]int) 135 | // 使用典型的 make[key] = val 语法来设置键值对。 136 | map1["k1"] = 7 137 | map1["k2"] = 13 138 | // 使用例如 Println 来打印一个 map 将会输出所有的键值对。 139 | fmt.Println("数据:", map1) 140 | // 使用 name[key] 来获取一个键的值 141 | v1 := map1["k1"] 142 | fmt.Println("值: ", v1) 143 | // 当对一个 map 调用内建的 len 时,返回的是键值对数目 144 | fmt.Println("长度:", len(map1)) 145 | // 内建的 delete 可以从一个 map 中移除键值对 146 | delete(map1, "k2") 147 | fmt.Println("数据:", map1) 148 | // 当从一个 map 中取值时,可选的第二返回值指示这个键是在这个 map 中。 149 | // 这可以用来消除键不存在和键有零值,像 0 或者 "" 而产生的歧义。 150 | _, prs := map1["k2"] 151 | fmt.Println("是否存在:", prs) 152 | // 你也可以通过这个语法在同一行申明和初始化一个新的map。 153 | map2 := map[string]int{"F": 1, "B": 2} 154 | // 注意一个 map 在使用 fmt.Println 打印的时候,是以 map[k:v k:v]的格式输出的。 155 | fmt.Println("数据:", map2) 156 | } 157 | 158 | // Range 遍历 159 | func rangeFunc() { 160 | // 这里我们使用 range 来统计一个 slice 的元素个数。数组也可以采用这种方法。 161 | array1 := []int{2, 3, 4} 162 | sum := 0 163 | for _, num := range array1 { 164 | sum += num 165 | } 166 | fmt.Println("求和:", sum) 167 | 168 | // range 在数组和 slice 中都同样提供每个项的索引和值。 169 | // 上面我们不需要索引,所以我们使用 空值定义符_ 来忽略它。 170 | // 有时候我们实际上是需要这个索引的。 171 | for i, num := range array1 { 172 | if num == 3 { 173 | fmt.Println("索引:", i) 174 | } 175 | } 176 | 177 | // range 在 map 中迭代键值对。 178 | map1 := map[string]string{"A": "苹果", "B": "香蕉"} 179 | for k, v := range map1 { 180 | fmt.Printf("%s -> %s\n", k, v) 181 | } 182 | for k := range map1 { 183 | fmt.Println("键:", k) 184 | } 185 | 186 | // range 在字符串中迭代 unicode 编码。 187 | // 第一个返回值是rune 的起始字节位置,然后第二个是 rune 自己。 188 | for i, c := range "abA" { 189 | fmt.Println(i, c) 190 | } 191 | } 192 | ``` 193 | 194 | -------------------------------------------------------------------------------- /doc/01-03.golang安装部署.md: -------------------------------------------------------------------------------- 1 | # 一、golang介绍 2 | 3 | ## 1. 语言介绍 4 | 5 | Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。 6 | 7 | Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,**并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本**。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。 8 | 9 | 10 | 11 | Go 语言是谷歌为充分利用现代硬件性能又兼顾开发效率而设计的一种全新语言。 12 | 13 | **Go 是一种跨平台(Mac OS、Windows、Linux 等)静态编译型语言**。拥有媲美 C 语言的强大性能,支持静态类型安全,在普通计算机上能几秒内快速编译一个大项目,开发效率跟动态语言相差无几。 14 | 15 | Go 语言在国内拥有非常活跃的社区、不仅大公司的 Go 项目越来越多,中小公司也都在考虑 Go 的应用。当前 Go 语言主要应用于后端服务的开发,未来随着 Go 项目的完善,在系统、游戏、UI界面、AI、物联网等领域,都将被广泛使用。 16 | 17 | 当然 Go 语言有优点也有一些缺点,完美的东西毕竟太少,如果看好 Go 的未来发展,那么不如抛开成见先来体验一番。 18 | 19 | ## 2. 特性说明 20 | 21 | **跨平台即最终可以执行到Windows,Linux,Unix等操作系统;** 22 | 23 | **静态语言**:1)编译工具代码感知更友好;2)商业系统大型开发更有保障;3)静态语言相对封闭,第三方开发包侵害性小; 24 | 25 | **动态语言**:1)代码编写更灵活;2)相对代码更简洁; 26 | 27 | **编译型和非编译型语言** 28 | 29 | image-20200307113322420 30 | 31 | 32 | 33 | Java 和 C# 比较特殊,源代码需要先转换成一种中间文件(字节码文件),然后再将中间文件拿到虚拟机中执行; 34 | 35 | 36 | 37 | 38 | ## 3. 核心开发团队 39 | 40 | ### Ken Thompson(肯·汤普森) 41 | 42 | image-20200307104441336 43 | 44 | 大名鼎鼎、如雷贯耳,Unix操作系统的发明人之一(排在第一号),C语言前身B语言的设计者,UTF-8编码设计者之一,图灵奖得主。老爷子今年快76岁了(1943年生)。早年一直在贝尔实验室做研究,60多岁的时候被谷歌尊养起来。2007年,老爷子和Rob Pike、Robert Griesemer一起设计了做出的Go语言。老爷子目前基本不参与Go的设计和开发。 45 | 46 | 在2011年的一次采访中,老爷子幽默地谈到设计Go语言的初衷是他们很不喜欢C++,因为C++中充满了大量的垃圾特性。 47 | 48 | ### Rob Pike(罗布·派克) 49 | 50 | image-20200307104459564 51 | 52 | 早年在贝尔实验室和Ken Thompson结对编程的小弟,早已成长为业内的领军人物。UTF-8两个发明人之一。Go设计团队第一任老大。如今也退休并被谷歌尊养起来了。Rob Pike仍旧活跃在各个Go论坛组中,适当地发表自己的意见。 53 | 54 | 顺便说一句,Go语言的地鼠吉祥物是由Rob Pike的媳妇Renee French设计的。 55 | 56 | 顺便另说一句,Rob Pike曾获得1980年奥运会射箭银牌。 57 | 58 | ### Robert Griesemer(罗伯特·格瑞史莫) 59 | 60 | image-20200307104510470 61 | 62 | Go语言三名最初的设计者之一,比较年轻。曾参与V8 JavaScript引擎和Java HotSpot虚拟机的研发。目前主要维护Go白皮书和代码解析器等。 63 | 64 | ## 4. 开发的优秀项目 65 | 66 | 语言的目标是用于项目开发,并能打造出很多优秀的产品。那么,Golang有哪些好像优秀的项目呢?不搜不知道,一搜吓一跳!列举一下我收集到的golang开发的优秀项目,如下: 67 | 68 | - docker,golang头号优秀项目,通过虚拟化技术实现的操作系统与应用的隔离,也称为容器; 69 | 70 | - kubernetes,是来自 Google 云平台的开源容器集群管理系统。简称k8s,k8s和docker是当前容器化技术的重要基础设施; 71 | 72 | - etcd,一种可靠的分布式KV存储系统,有点类似于zookeeper,可用于快速的云配置; 73 | 74 | - codis,由国人开发提供的一套优秀的redis分布式解决方案; 75 | 76 | - tidb,国内PingCAP 团队开发的一个分布式SQL 数据库,国内很多互联网公司在使用; 77 | 78 | - influxdb,时序型DB,着力于高性能查询与存储时序型数据,常用于系统监控与金融领域; 79 | 80 | ## 5. 大厂都在用 81 | 82 | 1. 腾讯蓝鲸 83 | 2. 百度APP 84 | 3. 知乎python用go重构 85 | 4. 字节跳动:抖音 86 | 5. 七牛云 87 | 88 | ## 6 学习方法 89 | 90 | - 多写多写再多写......... 91 | - 实践:自己设计项目,工作中使用 92 | 93 | # 二、安装部署 94 | 95 | go官网: https://golang.google.cn/dl/ ,请选择自己对应的系统 96 | 97 | 中文社区:https://studygolang.com/dl 98 | 99 | ## 1. win环境 100 | 101 | 1. 下载go.{version}.windows-amd64.msi或者go.{version}.windows-amd64.zip包,此次使用go.{version}.windows-amd64.zip包 102 | 2. 解压压缩文件(这里使用的是D:\Project,后面都基于这个目录) 103 | 3. 配置环境变量GOPATH和GOROOT 104 | 105 | ```bash 106 | # 打开cmd设置 107 | set GOPATH=D:\Project\GOPATH 108 | set GOROOT=D:\Project\GO 109 | set PATH=%PATH%;%GOROOT%\bin 110 | ``` 111 | 112 | 当然应该将这些环境变量配置到系统环境变量中 113 | 114 | 4. 此时打开cmd窗口,运行`go version`即可展示安装golang版本 115 | 116 | ```bash 117 | > go version 118 | go version go1.13.5 windows/amd64 119 | ``` 120 | 121 | ## 2. linux环境 122 | 123 | 1. 下载linux版本对应安装包,这里使用 go{version}.linux-amd64.tar.gz 124 | 2. 进入linux对应目录,解压文件 125 | 126 | ```bash 127 | tar -zxvf go{version}.linux-amd64.tar.gz 128 | ``` 129 | 130 | 3. 设置环境变量GOPATH和GOROOT 131 | 132 | ``` 133 | # 临时修改 134 | export GOPATH=D:\Project\GOPATH 135 | export GOROOT=D:\Project\GO 136 | export PATH=%PATH%:%GOROOT%\bin 137 | ``` 138 | 139 | 修改全局环境变量 140 | 141 | ```bash 142 | # 编辑全局环境变量文件 143 | vi /etc/profile 144 | # 追加环境变量都最后 145 | export GOPATH=D:\Project\GOPATH 146 | export GOROOT=D:\Project\GO 147 | export PATH=%PATH%:%GOROOT%\bin 148 | # 然后保存文件,并使文件生效 149 | source /etc/profile 150 | ``` 151 | 152 | 4. 运行`go version`查看版本信息 153 | 154 | ```bash 155 | # go version 156 | go version go1.13.5 windows/amd64 157 | ``` 158 | 159 | # 三、运行第一个程序 160 | 161 | ## 1. 运行和编译 162 | 163 | 当然还是hello word示例。创建文件hello.go,使用文本编辑器编辑,一定要注意文件编码为UTF-8 164 | 165 | ```go 166 | package main 167 | 168 | import "fmt" 169 | 170 | func main() { 171 | fmt.Println("Hello World !") 172 | } 173 | ``` 174 | 175 | 保存文件后,运行 176 | 177 | ```bash 178 | >go run hello.go 179 | Hello World ! 180 | ``` 181 | 182 | go编译运行 183 | 184 | ```bash 185 | >go build hello.go 186 | 187 | >hello.exe 188 | hello world! 189 | ``` 190 | 191 | ## 2. 交叉编译 192 | 193 | 交叉编译linux文件 194 | 195 | ```bash 196 | set CGO_ENABLED=0 197 | set GOOS=linux 198 | set GOARCH=amd64 199 | go build hello.go 200 | ``` 201 | 202 | 交叉编译参数 203 | 204 | ```bash 205 | $GOOS $GOARCH 206 | android arm 207 | darwin 386 208 | darwin amd64 209 | darwin arm 210 | darwin arm64 211 | dragonfly amd64 212 | freebsd 386 213 | freebsd amd64 214 | freebsd arm 215 | linux 386 216 | linux amd64 217 | linux arm 218 | linux arm64 219 | linux ppc64 220 | linux ppc64le 221 | linux mips 222 | linux mipsle 223 | linux mips64 224 | linux mips64le 225 | netbsd 386 226 | netbsd amd64 227 | netbsd arm 228 | openbsd 386 229 | openbsd amd64 230 | openbsd arm 231 | plan9 386 232 | plan9 amd64 233 | solaris amd64 234 | windows 386 235 | windows amd64 236 | ``` 237 | 238 | -------------------------------------------------------------------------------- /doc/11.golang协程.md: -------------------------------------------------------------------------------- 1 | ## go并发编程 2 | 3 | ## 一、协程(Goroutines) 4 | 5 | 在Go语言中,每一个并发的执行单元叫作一个goroutine。,我们只需要通过 go 关键字来开启 goroutine 即可。goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。 6 | 7 | goroutine 语法格式: 8 | 9 | ``` 10 | go 函数名( 参数列表 ) 11 | ``` 12 | 13 | 示例: 14 | 15 | ```go 16 | package main 17 | 18 | import "fmt" 19 | 20 | func f(from string) { 21 | for i := 0; i < 3; i++ { 22 | fmt.Println(from, ":", i) 23 | } 24 | } 25 | func main() { 26 | // 假设我们有一个函数叫做 f(s)。 27 | // 我们使用一般的方式调并同时运行。 28 | f("direct") 29 | // 使用 go f(s) 在一个 Go 协程中调用这个函数。 30 | // 这个新的 Go 协程将会并行的执行这个函数调用。 31 | go f("goroutine") 32 | // 你也可以为匿名函数启动一个 Go 协程。 33 | go func(msg string) { 34 | fmt.Println(msg) 35 | }("going") 36 | 37 | // 现在这两个 Go 协程在独立的 Go 协程中异步的运行,所以我们需要等它们执行结束。 38 | // 这里的 Scanln 代码需要我们在程序退出前按下任意键结束。 39 | var input string 40 | fmt.Scanln(&input) 41 | fmt.Println("done") 42 | // 当我们运行这个程序时,将首先看到阻塞式调用的输出,然后是两个 Go 协程的交替输出。 43 | // 这种交替的情况表示 Go 运行时是以异步的方式运行协程的。 44 | } 45 | ``` 46 | 47 | ## 二、通道(channel) 48 | 49 | 如果说goroutine是Go语言程序的并发体的话,那么channels则是它们之间的通信机制。通道(channel)是用来传递数据的一个数据结构。 50 | 51 | 通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 `<-` 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。 52 | 53 | ``` 54 | ch <- v // 把 v 发送到通道 ch 55 | v := <-ch // 从 ch 接收数据 56 | // 并把值赋给 v 57 | ``` 58 | 59 | 声明一个通道很简单,我们使用chan关键字即可,通道在使用前必须先创建: 60 | 61 | ``` 62 | ch := make(chan int) 63 | ``` 64 | 65 | 示例: 66 | 67 | ```go 68 | package main 69 | 70 | import ( 71 | "fmt" 72 | ) 73 | 74 | // 通道 是连接多个 Go 协程的管道。 75 | // 你可以从一个 Go 协程将值发送到通道,然后在别的 Go 协程中接收。 76 | func main() { 77 | // 使用 make(chan val-type) 创建一个新的通道。 78 | // 通道类型就是他们需要传递值的类型。 79 | messages := make(chan string) 80 | // 使用 channel <- 语法 发送 一个新的值到通道中。 81 | // 这里我们在一个新的 Go 协程中发送 "ping" 到上面创建的messages 通道中。 82 | go func() { 83 | messages <- "ping" 84 | }() 85 | // 使用 <-channel 语法从通道中 接收 一个值。 86 | // 这里将接收我们在上面发送的 "ping" 消息并打印出来。 87 | msg := <-messages 88 | fmt.Println(msg) 89 | // 我们运行程序时,通过通道,消息 "ping" 成功的从一个 Go 协程传到另一个中。 90 | // 默认发送和接收操作是阻塞的,直到发送方和接收方都准备完毕。 91 | // 这个特性允许我们,不使用任何其它的同步操作,来在程序结尾等待消息 "ping"。 92 | } 93 | ``` 94 | 95 | ## 三、通道缓冲区 96 | 97 | 通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小: 98 | 99 | ``` 100 | ch := make(chan int, 100) 101 | ``` 102 | 103 | 带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。 104 | 105 | 不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。 106 | 107 | **注意**:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。 108 | 109 | 示例: 110 | 111 | ```go 112 | package main 113 | 114 | import "fmt" 115 | 116 | // 默认通道是 无缓冲 的,这意味着只有在对应的接收(<- chan)通道准备好接收时,才允许进行发送(chan <-)。 117 | // 可缓存通道允许在没有对应接收方的情况下,缓存限定数量的值。 118 | func main() { 119 | // 这里我们 make 了一个通道,最多允许缓存 2 个值。 120 | messages := make(chan string, 2) 121 | // 因为这个通道是有缓冲区的,即使没有一个对应的并发接收方,我们仍然可以发送这些值。 122 | messages <- "buffered" 123 | messages <- "channel" 124 | // 然后我们可以像前面一样接收这两个值。 125 | fmt.Println(<-messages) 126 | fmt.Println(<-messages) 127 | } 128 | ``` 129 | 130 | ## 四、同步实现 131 | 132 | 我们可以通过channel实现同步,如下: 133 | 134 | ```go 135 | package main 136 | 137 | import "fmt" 138 | import "time" 139 | 140 | // 我们可以使用通道来同步 Go 协程间的执行状态。 141 | // 这里是一个使用阻塞的接受方式来等待一个 Go 协程的运行结束。 142 | func worker(done chan bool) { 143 | // 这是一个我们将要在 Go 协程中运行的函数。 144 | // done 通道将被用于通知其他 Go 协程这个函数已经工作完毕。 145 | fmt.Print("working...") 146 | time.Sleep(time.Second) 147 | fmt.Println("done") 148 | // 发送一个值来通知我们已经完工啦。 149 | done <- true 150 | } 151 | func main() { 152 | // 运行一个 worker Go协程,并给予用于通知的通道。 153 | done := make(chan bool, 1) 154 | go worker(done) 155 | // 程序将在接收到通道中 worker 发出的通知前一直阻塞。 156 | <-done 157 | // 如果你把 <- done 这行代码从程序中移除,程序甚至会在 worker还没开始运行时就结束了。 158 | } 159 | ``` 160 | 161 | ## 五、通道方向示例 162 | 163 | ```go 164 | package main 165 | 166 | import "fmt" 167 | 168 | // 当使用通道作为函数的参数时,你可以指定这个通道是不是只用来发送或者接收值。 169 | // 这个特性提升了程序的类型安全性。 170 | func ping(pings chan <- string, msg string) { 171 | // ping 函数定义了一个只允许发送数据的通道。 172 | // 尝试使用这个通道来接收数据将会得到一个编译时错误。 173 | pings <- msg 174 | } 175 | func pong(pings <-chan string, pongs chan <- string) { 176 | // pong 函数允许通道(pings)来接收数据,另一通道(pongs)来发送数据。 177 | msg := <-pings 178 | pongs <- msg 179 | } 180 | func main() { 181 | pings := make(chan string, 1) 182 | pongs := make(chan string, 1) 183 | ping(pings, "passed message") 184 | pong(pings, pongs) 185 | fmt.Println(<-pongs) 186 | } 187 | ``` 188 | 189 | ## 六、Go 遍历通道与关闭通道 190 | 191 | Go 通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。格式如下: 192 | 193 | ``` 194 | v, ok := <-ch 195 | ``` 196 | 197 | 如果通道接收不到数据后 ok 就为 false,这时通道就可以使用 **close()** 函数来关闭。 198 | 199 | ## 七、通道选择器(select) 200 | 201 | ```go 202 | select { 203 | case <-ch1: 204 | // ... 205 | case x := <-ch2: 206 | // ...use x... 207 | case ch3 <- y: 208 | // ... 209 | default: 210 | // ... 211 | } 212 | ``` 213 | 214 | select语句的一般形式。和switch语句稍微有点相似,也会有几个case和最后的default选择支。每一个case代表一个通信操作(在某个channel上进行发送或者接收)并且会包含一些语句组成的一个语句块。一个接收表达式可能只包含接收表达式自身(译注:不把接收到的值赋值给变量什么的),就像上面的第一个case,或者包含在一个简短的变量声明中,像第二个case里一样;第二种形式让你能够引用接收到的值。 215 | 216 | select会等待case中有能够执行的case时去执行。当条件满足时,select才会去通信并执行case之后的语句;这时候其它通信是不会执行的。一个没有任何case的select语句写作select{},会永远地等待下去。 217 | 218 | 示例: 219 | 220 | ```go 221 | package main 222 | 223 | import "time" 224 | import "fmt" 225 | 226 | // Go 的通道选择器 让你可以同时等待多个通道操作。 227 | // Go 协程和通道以及选择器的结合是 Go 的一个强大特性。 228 | func main() { 229 | // 在我们的例子中,我们将从两个通道中选择。 230 | c1 := make(chan string) 231 | c2 := make(chan string) 232 | // 各个通道将在若干时间后接收一个值,这个用来模拟例如并行的 Go 协程中阻塞的 RPC 操作 233 | go func() { 234 | time.Sleep(time.Second * 1) 235 | c1 <- "one" 236 | }() 237 | go func() { 238 | time.Sleep(time.Second * 2) 239 | c2 <- "two" 240 | }() 241 | 242 | // 我们使用 select 关键字来同时等待这两个值,并打印各自接收到的值。 243 | for i := 0; i < 2; i++ { 244 | select { 245 | case msg1 := <-c1: 246 | fmt.Println("received", msg1) 247 | case msg2 := <-c2: 248 | fmt.Println("received", msg2) 249 | } 250 | } 251 | // 我们首先接收到值 "one",然后就是预料中的 "two"了。 252 | // 注意从第一次和第二次 Sleeps 并发执行,总共仅运行了两秒左右。 253 | } 254 | ``` 255 | 256 | ## 八、超时实现 257 | 258 | ```go 259 | package main 260 | 261 | import "time" 262 | import "fmt" 263 | 264 | // 超时 对于一个连接外部资源,或者其它一些需要花费执行时间的操作的程序而言是很重要的。 265 | // 得益于通道和 select,在 Go中实现超时操作是简洁而优雅的。 266 | func main() { 267 | c1 := make(chan string, 1) 268 | // 在我们的例子中,假如我们执行一个外部调用,并在 2 秒后通过通道 c1 返回它的执行结果。 269 | go func() { 270 | time.Sleep(time.Second * 2) 271 | c1 <- "result 1" 272 | }() 273 | 274 | // 这里是使用 select 实现一个超时操作。res := <- c1 等待结果,<-Time.After 等待超时时间 1 秒后发送的值。 275 | // 由于 select 默认处理第一个已准备好的接收操作,如果这个操作超过了允许的 1 秒的话,将会执行超时 case。 276 | select { 277 | case res := <-c1: 278 | fmt.Println(res) 279 | case <-time.After(time.Second * 1): 280 | fmt.Println("timeout 1") 281 | } 282 | 283 | // 如果我允许一个长一点的超时时间 3 秒,将会成功的从 c2接收到值,并且打印出结果。 284 | c2 := make(chan string, 1) 285 | go func() { 286 | time.Sleep(time.Second * 2) 287 | c2 <- "result 2" 288 | }() 289 | 290 | select { 291 | case res := <-c2: 292 | fmt.Println(res) 293 | case <-time.After(time.Second * 3): 294 | fmt.Println("timeout 2") 295 | } 296 | // 运行这个程序,首先显示运行超时的操作,然后是成功接收的。 297 | // 使用这个 select 超时方式,需要使用通道传递结果。 298 | // 这对于一般情况是个好的方式,因为其他重要的 Go 特性是基于通道和select 的。 299 | } 300 | ``` 301 | 302 | ## 九、非阻塞选择器 303 | 304 | ```go 305 | package main 306 | 307 | import ( 308 | "fmt" 309 | ) 310 | 311 | // 常规的通过通道发送和接收数据是阻塞的。 312 | // 然而,我们可以使用带一个 default 子句的 select 来实现非阻塞 的发送、接收,甚至是非阻塞的多路 select。 313 | func main() { 314 | messages := make(chan string) 315 | signals := make(chan bool) 316 | // 这里是一个非阻塞接收的例子。 317 | // 如果在 messages 中存在,然后 select 将这个值带入 <-messages case中。 318 | // 如果不是,就直接到 default 分支中。 319 | select { 320 | case msg := <-messages: 321 | fmt.Println("received message", msg) 322 | default: 323 | fmt.Println("no message received") 324 | } 325 | 326 | // 一个非阻塞发送的实现方法和上面一样。 327 | msg := "hi" 328 | select { 329 | case messages <- msg: 330 | fmt.Println("sent message", msg) 331 | default: 332 | fmt.Println("no message sent") 333 | } 334 | 335 | // 我们可以在 default 前使用多个 case 子句来实现一个多路的非阻塞的选择器。 336 | // 这里我们试图在 messages和 signals 上同时使用非阻塞的接受操作。 337 | select { 338 | case msg := <-messages: 339 | fmt.Println("received message", msg) 340 | case sig := <-signals: 341 | fmt.Println("received signal", sig) 342 | default: 343 | fmt.Println("no activity") 344 | } 345 | } 346 | ``` 347 | 348 | -------------------------------------------------------------------------------- /doc/12.golang常用函数.md: -------------------------------------------------------------------------------- 1 | # golang常用方法 2 | 3 | ## 字符串函数 4 | 5 | ```go 6 | package main 7 | 8 | import s "strings" 9 | import "fmt" 10 | // 我们给 fmt.Println 一个短名字的别名,我们随后将会经常用到。 11 | var p = fmt.Println 12 | 13 | // 标准库的 strings 包提供了很多有用的字符串相关的函数。 14 | // 这里是一些用来让你对这个包有个初步了解的例子。 15 | func main() { 16 | // 这是一些 strings 中的函数例子。 17 | // 注意他们都是包中的函数,不是字符串对象自身的方法,这意味着我们需要考虑在调用时传递字符作为第一个参数进行传递。 18 | p("Contains: ", s.Contains("test", "es")) 19 | p("Count: ", s.Count("test", "t")) 20 | p("HasPrefix: ", s.HasPrefix("test", "te")) 21 | p("HasSuffix: ", s.HasSuffix("test", "st")) 22 | p("Index: ", s.Index("test", "e")) 23 | p("Join: ", s.Join([]string{"a", "b"}, "-")) 24 | p("Repeat: ", s.Repeat("a", 5)) 25 | p("Replace: ", s.Replace("foo", "o", "0", -1)) 26 | p("Replace: ", s.Replace("foo", "o", "0", 1)) 27 | p("Split: ", s.Split("a-b-c-d-e", "-")) 28 | p("ToLower: ", s.ToLower("TEST")) 29 | p("ToUpper: ", s.ToUpper("test")) 30 | // 你可以在 strings包文档中找到更多的函数 31 | p() 32 | // 虽然不是 strings 的一部分,但是仍然值得一提的是获取字符串长度和通过索引获取一个字符的机制。 33 | p("Len: ", len("hello")) 34 | p("Char:", "hello"[1]) 35 | } 36 | ``` 37 | 38 | 39 | 40 | ## 字符串格式化 41 | 42 | ```go 43 | package main 44 | 45 | import "fmt" 46 | import "os" 47 | 48 | type point struct { 49 | x, y int 50 | } 51 | 52 | func main() { 53 | // Go 为常规 Go 值的格式化设计提供了多种打印方式。例如,这里打印了 point 结构体的一个实例。 54 | p := point{1, 2} 55 | fmt.Printf("%v\n", p) 56 | // 如果值是一个结构体,%+v 的格式化输出内容将包括结构体的字段名。 57 | fmt.Printf("%+v\n", p) 58 | // %#v 形式则输出这个值的 Go 语法表示。例如,值的运行源代码片段。 59 | fmt.Printf("%#v\n", p) 60 | // 需要打印值的类型,使用 %T。 61 | fmt.Printf("%T\n", p) 62 | // 格式化布尔值是简单的。 63 | fmt.Printf("%t\n", true) 64 | // 格式化整形数有多种方式,使用 %d进行标准的十进制格式化。 65 | fmt.Printf("%d\n", 123) 66 | // 这个输出二进制表示形式。 67 | fmt.Printf("%b\n", 14) 68 | // 这个输出给定整数的对应字符。 69 | fmt.Printf("%c\n", 33) 70 | // %x 提供十六进制编码。 71 | fmt.Printf("%x\n", 456) 72 | // 对于浮点型同样有很多的格式化选项。使用 %f 进行最基本的十进制格式化。 73 | fmt.Printf("%f\n", 78.9) 74 | // %e 和 %E 将浮点型格式化为(稍微有一点不同的)科学技科学记数法表示形式。 75 | fmt.Printf("%e\n", 123400000.0) 76 | fmt.Printf("%E\n", 123400000.0) 77 | // 使用 %s 进行基本的字符串输出。 78 | fmt.Printf("%s\n", "\"string\"") 79 | // 像 Go 源代码中那样带有双引号的输出,使用 %q。 80 | fmt.Printf("%q\n", "\"string\"") 81 | // 和上面的整形数一样,%x 输出使用 base-16 编码的字符串,每个字节使用 2 个字符表示。 82 | fmt.Printf("%x\n", "hex this") 83 | // 要输出一个指针的值,使用 %p。 84 | fmt.Printf("%p\n", &p) 85 | // 当输出数字的时候,你将经常想要控制输出结果的宽度和精度,可以使用在 % 后面使用数字来控制输出宽度。 86 | // 默认结果使用右对齐并且通过空格来填充空白部分。 87 | fmt.Printf("|%6d|%6d|\n", 12, 345) 88 | // 你也可以指定浮点型的输出宽度,同时也可以通过 宽度.精度 的语法来指定输出的精度。 89 | fmt.Printf("|%6.2f|%6.2f|\n", 1.2, 3.45) 90 | // 要左对齐,使用 - 标志。 91 | fmt.Printf("|%-6.2f|%-6.2f|\n", 1.2, 3.45) 92 | // 你也许也想控制字符串输出时的宽度,特别是要确保他们在类表格输出时的对齐。 93 | // 这是基本的右对齐宽度表示。 94 | fmt.Printf("|%6s|%6s|\n", "foo", "b") 95 | // 要左对齐,和数字一样,使用 - 标志。 96 | fmt.Printf("|%-6s|%-6s|\n", "foo", "b") 97 | // 到目前为止,我们已经看过 Printf了,它通过 os.Stdout输出格式化的字符串。 98 | // Sprintf 则格式化并返回一个字符串而不带任何输出。 99 | s := fmt.Sprintf("a %s", "string") 100 | fmt.Println(s) 101 | // 你可以使用 Fprintf 来格式化并输出到 io.Writers而不是 os.Stdout。 102 | fmt.Fprintf(os.Stderr, "an %s\n", "error") 103 | } 104 | ``` 105 | 106 | ## 数字转换 107 | 108 | ```go 109 | package main 110 | 111 | import "strconv" 112 | import "fmt" 113 | 114 | // 从字符串中解析数字在很多程序中是一个基础常见的任务,在Go 中是这样处理的。 115 | func main() { 116 | // 内置的 strconv 包提供了数字解析功能。 117 | // 使用 ParseFloat 解析浮点数,这里的 64 表示表示解析的数的位数。 118 | f, _ := strconv.ParseFloat("1.234", 64) 119 | fmt.Println(f) 120 | // 在使用 ParseInt 解析整形数时,例子中的参数 0 表示自动推断字符串所表示的数字的进制。 121 | // 64 表示返回的整形数是以 64 位存储的。 122 | i, _ := strconv.ParseInt("123", 0, 64) 123 | fmt.Println(i) 124 | // ParseInt 会自动识别出十六进制数。 125 | d, _ := strconv.ParseInt("0x1c8", 0, 64) 126 | fmt.Println(d) 127 | // ParseUint 也是可用的。 128 | u, _ := strconv.ParseUint("789", 0, 64) 129 | fmt.Println(u) 130 | // Atoi 是一个基础的 10 进制整型数转换函数。 131 | k, _ := strconv.Atoi("135") 132 | fmt.Println(k) 133 | // 在输入错误时,解析函数会返回一个错误。 134 | _, e := strconv.Atoi("wat") 135 | fmt.Println(e) 136 | } 137 | ``` 138 | 139 | ## 时间函数 140 | 141 | ```go 142 | package main 143 | 144 | import "fmt" 145 | import "time" 146 | 147 | func main() { 148 | p := fmt.Println 149 | // 得到当前时间。 150 | now := time.Now() 151 | p(now) 152 | // 通过提供年月日等信息,你可以构建一个 time。时间总是关联着位置信息,例如时区。 153 | then := time.Date( 154 | 2009, 11, 17, 20, 34, 58, 651387237, time.UTC) 155 | p(then) 156 | // 你可以提取出时间的各个组成部分。 157 | p(then.Year()) 158 | p(then.Month()) 159 | p(then.Day()) 160 | p(then.Hour()) 161 | p(then.Minute()) 162 | p(then.Second()) 163 | p(then.Nanosecond()) 164 | p(then.Location()) 165 | // 输出是星期一到日的 Weekday 也是支持的。 166 | p(then.Weekday()) 167 | // 这些方法来比较两个时间,分别测试一下是否是之前,之后或者是同一时刻,精确到秒。 168 | p(then.Before(now)) 169 | p(then.After(now)) 170 | p(then.Equal(now)) 171 | // 方法 Sub 返回一个 Duration 来表示两个时间点的间隔时间。 172 | diff := now.Sub(then) 173 | p(diff) 174 | // 我们计算出不同单位下的时间长度值。 175 | p(diff.Hours()) 176 | p(diff.Minutes()) 177 | p(diff.Seconds()) 178 | p(diff.Nanoseconds()) 179 | // 你可以使用 Add 将时间后移一个时间间隔,或者使用一个 - 来将时间前移一个时间间隔。 180 | p(then.Add(diff)) 181 | p(then.Add(-diff)) 182 | 183 | p("################") 184 | // 格式化 185 | // 这里是一个基本的按照 RFC3339 进行格式化的例子,使用对应模式常量。 186 | t := time.Now() 187 | p(t.Format(time.RFC3339)) 188 | // 时间解析使用同 Format 相同的形式值。 189 | t1, e := time.Parse( 190 | time.RFC3339, 191 | "2012-11-01T22:08:41+00:00") 192 | p(t1) 193 | // ormat 和 Parse 使用基于例子的形式来决定日期格式, 194 | // 一般你只要使用 time 包中提供的模式常量就行了,但是你也可以实现自定义模式。 195 | // 模式必须使用时间 Mon Jan 2 15:04:05 MST 2006来指定给定时间/字符串的格式化/解析方式。 196 | // 时间一定要按照如下所示:2006为年,15 为小时,Monday 代表星期几,等等。 197 | p(t.Format("3:04PM")) 198 | p(t.Format("Mon Jan _2 15:04:05 2006")) 199 | p(t.Format("2006-01-02T15:04:05.999999-07:00")) 200 | form := "3 04 PM" 201 | t2, e := time.Parse(form, "8 41 PM") 202 | p(t2) 203 | // 对于纯数字表示的时间,你也可以使用标准的格式化字符串来提出出时间值得组成。 204 | fmt.Printf("%d-%02d-%02dT%02d:%02d:%02d-00:00\n", 205 | t.Year(), t.Month(), t.Day(), 206 | t.Hour(), t.Minute(), t.Second()) 207 | // Parse 函数在输入的时间格式不正确是会返回一个错误。 208 | ansic := "Mon Jan _2 15:04:05 2006" 209 | _, e = time.Parse(ansic, "8:41PM") 210 | p(e) 211 | } 212 | ``` 213 | 214 | ## JSON转换 215 | 216 | ```go 217 | package main 218 | 219 | import "encoding/json" 220 | import "fmt" 221 | import "os" 222 | 223 | // 下面我们将使用这两个结构体来演示自定义类型的编码和解码。 224 | type Response1 struct { 225 | Page int 226 | Fruits []string 227 | } 228 | type Response2 struct { 229 | Page int `json:"page"` 230 | Fruits []string `json:"fruits"` 231 | } 232 | 233 | func main() { 234 | // 首先我们来看一下基本数据类型到 JSON 字符串的编码过程。这里是一些原子值的例子。 235 | bolB, _ := json.Marshal(true) 236 | fmt.Println(string(bolB)) 237 | intB, _ := json.Marshal(1) 238 | fmt.Println(string(intB)) 239 | fltB, _ := json.Marshal(2.34) 240 | fmt.Println(string(fltB)) 241 | strB, _ := json.Marshal("gopher") 242 | fmt.Println(string(strB)) 243 | // 这里是一些切片和 map 编码成 JSON 数组和对象的例子。 244 | slcD := []string{"apple", "peach", "pear"} 245 | slcB, _ := json.Marshal(slcD) 246 | fmt.Println(string(slcB)) 247 | mapD := map[string]int{"apple": 5, "lettuce": 7} 248 | mapB, _ := json.Marshal(mapD) 249 | fmt.Println(string(mapB)) 250 | // JSON 包可以自动的编码你的自定义类型。 251 | // 编码仅输出可导出的字段,并且默认使用他们的名字作为 JSON 数据的键。 252 | res1D := &Response1{ 253 | Page: 1, 254 | Fruits: []string{"apple", "peach", "pear"}} 255 | res1B, _ := json.Marshal(res1D) 256 | fmt.Println(string(res1B)) 257 | // 你可以给结构字段声明标签来自定义编码的 JSON 数据键名称。 258 | // 在上面 Response2 的定义可以作为这个标签这个的一个例子。 259 | res2D := &Response2{ 260 | Page: 1, 261 | Fruits: []string{"apple", "peach", "pear"}} 262 | res2B, _ := json.Marshal(res2D) 263 | fmt.Println(string(res2B)) 264 | // 现在来看看解码 JSON 数据为 Go 值的过程。 265 | // 这里是一个普通数据结构的解码例子。 266 | byt := []byte(`{"num":6.13,"strs":["a","b"]}`) 267 | // 我们需要提供一个 JSON 包可以存放解码数据的变量。 268 | // 这里的 map[string]interface{} 将保存一个 string 为键,值为任意值的map。 269 | var dat map[string]interface{} 270 | // 这里就是实际的解码和相关的错误检查。 271 | if err := json.Unmarshal(byt, &dat); err != nil { 272 | panic(err) 273 | } 274 | fmt.Println(dat) 275 | // 为了使用解码 map 中的值,我们需要将他们进行适当的类型转换。 276 | // 例如这里我们将 num 的值转换成 float64类型。 277 | num := dat["num"].(float64) 278 | fmt.Println(num) 279 | // 访问嵌套的值需要一系列的转化。 280 | strs := dat["strs"].([]interface{}) 281 | str1 := strs[0].(string) 282 | fmt.Println(str1) 283 | // 我们也可以解码 JSON 值到自定义类型。 284 | // 这个功能的好处就是可以为我们的程序带来额外的类型安全加强,并且消除在访问数据时的类型断言。 285 | str := `{"page": 1, "fruits": ["apple", "peach"]}` 286 | res := Response2{} 287 | json.Unmarshal([]byte(str), &res) 288 | fmt.Println(res) 289 | fmt.Println(res.Fruits[0]) 290 | // 在上面的例子中,我们经常使用 byte 和 string 作为使用标准输出时数据和 JSON 表示之间的中间值。 291 | // 我们也可以和os.Stdout 一样,直接将 JSON 编码直接输出至 os.Writer流中,或者作为 HTTP 响应体。 292 | enc := json.NewEncoder(os.Stdout) 293 | d := map[string]int{"apple": 5, "lettuce": 7} 294 | enc.Encode(d) 295 | } 296 | ``` 297 | 298 | ## 文件写入 299 | 300 | ```go 301 | package main 302 | 303 | import ( 304 | "bufio" 305 | "fmt" 306 | "io/ioutil" 307 | "os" 308 | ) 309 | 310 | // Go 写文件和我们前面看过的读操作有着相似的方式。 311 | // 读取文件需要经常进行错误检查,这个帮助方法可以精简下面的错误检查过程。 312 | func check(e error) { 313 | if e != nil { 314 | panic(e) 315 | } 316 | } 317 | func main() { 318 | d1 := []byte("hello\ngo\n") 319 | // 开始,这里是展示如写入一个字符串(或者只是一些字节)到一个文件。 320 | err := ioutil.WriteFile("D:/study/dat1", d1, 0644) 321 | check(err) 322 | // 对于更细粒度的写入,先打开一个文件。 323 | f, err := os.Create("D:/study/dat2") 324 | check(err) 325 | // 打开文件后,习惯立即使用 defer 调用文件的 Close操作。 326 | defer f.Close() 327 | // 你可以写入你想写入的字节切片 328 | d2 := []byte{115, 111, 109, 101, 10} 329 | n2, err := f.Write(d2) 330 | check(err) 331 | fmt.Printf("wrote %d bytes\n", n2) 332 | // WriteString 也是可用的。 333 | n3, err := f.WriteString("writes\n") 334 | fmt.Printf("wrote %d bytes\n", n3) 335 | // 调用 Sync 来将缓冲区的信息写入磁盘。 336 | f.Sync() 337 | // bufio 提供了和我们前面看到的带缓冲的读取器一样的带缓冲的写入器。 338 | w := bufio.NewWriter(f) 339 | n4, err := w.WriteString("buffered\n") 340 | fmt.Printf("wrote %d bytes\n", n4) 341 | // 使用 Flush 来确保所有缓存的操作已写入底层写入器。 342 | w.Flush() 343 | 344 | } 345 | ``` 346 | 347 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [2020] [FLY的狐狸] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /doc/04.golang开发工具安装.md: -------------------------------------------------------------------------------- 1 | # 一、开发环境IDE安装 2 | 3 | **工欲善其事,必先利其器** 4 | 5 | 这里推荐两款golang开发工具,一个是`goland`,一个是`VSCode`;goland是收费的,收费还是有收费的道理,确实比较好用,个人比较推荐;VSCode需要安装插件,免费版本,这个是前端开发的利器,go语言开发感觉还是差一点; 6 | 7 | ## 1. Goland 8 | 9 | `JetBrains`旗下的产品众多,最出名的就是IDEA,java开发工具;当然PHP,Python,Scala等开发语言,数据库版本都是有的; 10 | 11 | `JetBrains`的官方网站为:https://www.jetbrains.com/go/ 12 | 13 | ![image-20200308213639999](04.golang开发工具安装.assets/image-20200308213639999.png) 14 | 15 | 1. 首先打开File->Setting或者Ctrl+Alt+S,设置goroot和gopath,默认会获取环境变量配置 16 | 17 | ![image-20200308214137180](04.golang开发工具安装.assets/image-20200308214137180.png) 18 | 19 | ![image-20200308214206249](04.golang开发工具安装.assets/image-20200308214206249.png) 20 | 21 | 2. 如果我们需要使用go modules功能,需要进行开启设置; 22 | 23 | ![image-20200308214256503](04.golang开发工具安装.assets/image-20200308214256503.png) 24 | 25 | 3. 最好我们编写helloworld运行 26 | 27 | 新建项目study1,选择目录 28 | 29 | ![image-20200308214352004](04.golang开发工具安装.assets/image-20200308214352004.png) 30 | 31 | 新建go文件 32 | 33 | ![image-20200308214422995](04.golang开发工具安装.assets/image-20200308214422995.png) 34 | 35 | 编写hello world 36 | 37 | ```go 38 | package main 39 | 40 | import "fmt" 41 | 42 | func main(){ 43 | fmt.Println("hello world!") 44 | } 45 | ``` 46 | 47 | ![image-20200308214456521](04.golang开发工具安装.assets/image-20200308214456521.png) 48 | 49 | 最后点击左侧启动运行,或者按Ctrl+Shift+F10运行程序 50 | 51 | ![image-20200308214525960](04.golang开发工具安装.assets/image-20200308214525960.png) 52 | 53 | 最终我们看到hello world运行成功! 54 | 55 | ## 2. Goland 常用快捷键 56 | 57 | 下面列举了一些 Goland 中经常使用到的快捷键。 58 | 59 | ### 文件操作 60 | 61 | | 快捷键 | 作用 | 62 | | ---------------- | ------------------------------------ | 63 | | Ctrl + E | 打开最近浏览过的文件 | 64 | | Ctrl + N | 快速打开某个 struct 结构体所在的文件 | 65 | | Ctrl + Shift + N | 快速打开文件 | 66 | | Shift + F6 | 重命名文件夹、文件、方法、变量名等 | 67 | 68 | ### 代码格式化 69 | 70 | | 快捷键 | 作用 | 71 | | ---------------- | ------------------------------------------------------------ | 72 | | Ctrl + Alt + L | 格式化代码 | 73 | | Ctrl + 空格 | 代码提示 | 74 | | Ctrl + / | 单行注释 | 75 | | Ctrl + Shift + / | 多行注释 | 76 | | Ctrl + B 或 F4 | 快速跳转到结构体或方法的定义位置(需将光标移动到结构体或方法的名称上) | 77 | | Ctrl +“+ 或 -” | 可以将当前(光标所在位置)的方法进行展开或折叠 | 78 | 79 | ### 查找和定位 80 | 81 | | 快捷键 | 作用 | 82 | | ---------------------- | ------------------------ | 83 | | Ctrl + R | 替换文本 | 84 | | Ctrl + F | 查找文本 | 85 | | Ctrl + Shift + F | 全局查找 | 86 | | Ctrl + G | 显示当前光标所在行的行号 | 87 | | Ctrl + Shift + Alt + N | 查找类中的方法或变量 | 88 | 89 | ### 编辑代码 90 | 91 | | 快捷键 | 作用 | 92 | | --------------------------- | ------------------------------------------------------------ | 93 | | Ctrl + J | 快速生成一个代码片段 | 94 | | Shift+Enter | 向光标的下方插入一行,并将光标移动到该行的开始位置 | 95 | | Ctrl + X | 删除当前光标所在行 | 96 | | Ctrl + D | 复制当前光标所在行 | 97 | | Ctrl + Shift + 方向键上或下 | 将光标所在的行进行上下移动(也可以使用 Alt+Shift+方向键上或下) | 98 | | Alt + 回车 | 自动导入需要导入的包 | 99 | | Ctrl + Shift + U | 将选中的内容进行大小写转化 | 100 | | Alt + Insert | 生成测试代码 | 101 | | Alt + Up/Down | 快速移动到上一个或下一个方法 | 102 | | Ctrl + Alt + Space | 类名或接口名提示(代码提示) | 103 | | Ctrl + P | 提示方法的参数类型(需在方法调用的位置使用,并将光标移动至`( )`的内部或两侧) | 104 | 105 | 5) 编辑器相关的快捷键 106 | 107 | | 快捷键 | 作用 | 108 | | ----------------------- | ------------------------------------ | 109 | | Ctrl + Alt + left/right | 返回至上次浏览的位置 | 110 | | Alt + left/right | 切换代码视图 | 111 | | Ctrl + W | 快速选中代码 | 112 | | Alt + F3 | 逐个向下查找选中的代码,并高亮显示 | 113 | | Tab | 代码标签输入完成后,按 Tab,生成代码 | 114 | | F2 或 Shift + F2 | 快速定位错误或警告 | 115 | | Alt + Shift + C | 查看最近的操作 | 116 | | Alt + 1 | 快速打开或隐藏工程面板 | 117 | 118 | ## 3. VSCode 119 | 120 | VSCode 全称 Visual Studio Code,是微软出的一款轻量级代码编辑器,免费、开源而且功能强大。它支持几乎所有主流的程序语言的语法高亮、智能代码补全、自定义热键、括号匹配、代码片段、代码对比 Diff、GIT 等特性,支持插件扩展,并针对网页开发和云端应用开发做了优化。 121 | 122 | VSCode的官网:https://code.visualstudio.com/ 123 | 124 | ![image-20200308213720888](04.golang开发工具安装.assets/image-20200308213720888.png) 125 | 126 | 在VSCode中安装Go插件 127 | 128 | 进入Extensions后直接搜索go,即可安装 129 | 130 | ![img](04.golang开发工具安装.assets/image-20200308213639991.png) 131 | 132 | 在编辑代码时会提示安装一些插件,选择安装即可; 133 | 134 | 如果没有提示也可以自行安装,大致如下 135 | 136 | ```bash 137 | go get -u -v github.com/bytbox/golint 138 | go get -u -v github.com/golang/tools 139 | go get -u -v github.com/lukehoban/go-outline 140 | go get -u -v github.com/newhook/go-symbols 141 | go get -u -v github.com/josharian/impl 142 | go get -u -v github.com/sqs/goreturns 143 | go get -u -v github.com/cweill/gotests 144 | ``` 145 | 146 | 147 | 148 | ## 4. VSCode 常用快捷键 149 | 150 | | 快捷键 | 功能 | 151 | | -------------------- | ------------------- | 152 | | F2 | 重命名符号 | 153 | | Ctrl + L | 选中当前行 | 154 | | Ctrl + / | 添加/关闭行注释 | 155 | | Ctrl + ←/→ | 按单词移动光标 | 156 | | Ctrl + Shift + ←/→ | 按单词进行选中 | 157 | | Shift + Alt +A | 添加/关闭块区域注释 | 158 | | Ctrl + Space | 输入建议 | 159 | | Ctrl + Shift + Space | 参数提示 | 160 | | F12 | 跳转到定义处 | 161 | | Alt + F12 | 代码片段显示定义 | 162 | | Shift + F12 | 显示所有引用 | 163 | | Shift + Alt + F | 格式化代码 | 164 | 165 | ------ 166 | 167 | ### 通用快捷键 168 | 169 | | 快捷键 | 作用 | 170 | | :-------------- | :--------------------- | 171 | | Ctrl+Shift+P,F1 | 展示全局命令面板 | 172 | | Ctrl+P | 快速打开最近打开的文件 | 173 | | Ctrl+Shift+N | 打开新的编辑器窗口 | 174 | | Ctrl+Shift+W | 关闭编辑器 | 175 | | Ctrl+, | 首选项 | 176 | | Ctrl+K Ctrl+S | 快捷键设置 | 177 | 178 | ------ 179 | 180 | ### 基础编辑 181 | 182 | | 快捷键 | 作用 | 183 | | :------------------- | :----------------------- | 184 | | Ctrl + X | 剪切 | 185 | | Ctrl + C | 复制 | 186 | | Alt + ↑/↓ | 移动行上下 | 187 | | Shift + Alt + ↑/↓ | 在当前行上下复制当前行 | 188 | | Ctrl + Shift + K | 删除行 | 189 | | Ctrl + Enter | 在当前行下插入新的一行 | 190 | | Ctrl + Shift + Enter | 在当前行上插入新的一行 | 191 | | Ctrl + Shift + \ | 匹配花括号的闭合处,跳转 | 192 | | Ctrl + ] / [ | 行缩进 | 193 | | Home / End | 光标跳转到行头/行尾 | 194 | | Ctrl + Home | 跳转到页头 | 195 | | Ctrl + End | 跳转到页尾 | 196 | | Ctrl + ↑/↓ | 行视图上下偏移 | 197 | | Alt + PgUp/PgDown | 屏视图上下偏移 | 198 | | Ctrl + Shift + [ | 折叠区域代码 | 199 | | Ctrl + Shift + ] | 展开区域代码 | 200 | | Ctrl + K Ctrl + [ | 折叠所有子区域代码 | 201 | | Ctrl + k Ctrl + ] | 展开所有折叠的子区域代码 | 202 | | Ctrl + K Ctrl + 0 | 折叠所有区域代码 | 203 | | Ctrl + K Ctrl + J | 展开所有折叠区域代码 | 204 | | Ctrl + K Ctrl + C | 添加行注释 | 205 | | Ctrl + K Ctrl + U | 删除行注释 | 206 | | Ctrl + / | 添加/关闭行注释 | 207 | | Shift + Alt +A | 添加/关闭块区域注释 | 208 | | Alt + Z | 添加/关闭词汇包含 | 209 | 210 | ------ 211 | 212 | ### 导航 213 | 214 | | 快捷键 | 作用 | 215 | | :----------------- | :----------------------- | 216 | | Ctrl + T | 列出所有符号 | 217 | | Ctrl + G | 跳转行 | 218 | | Ctrl + P | 跳转文件 | 219 | | Ctrl + Shift + O | 跳转到符号处 | 220 | | Ctrl + Shift + M | 打开问题展示面板 | 221 | | F8 | 跳转到下一个错误或者警告 | 222 | | Shift + F8 | 跳转到上一个错误或者警告 | 223 | | Ctrl + Shift + Tab | 切换到最近打开的文件 | 224 | | Alt + ←/→ | 向后、向前 | 225 | | Ctrl + M | 进入用Tab来移动焦点 | 226 | 227 | ------ 228 | 229 | ### 查询与替换 230 | 231 | | 快捷键 | 作用 | 232 | | :----------------- | :------------------------------- | 233 | | Ctrl + F | 查询 | 234 | | Ctrl + H | 替换 | 235 | | F3 / Shift + F3 | 查询下一个/上一个 | 236 | | Alt + Enter | 选中所有出现在查询中的 | 237 | | Ctrl + D | 匹配当前选中的词汇或者行 | 238 | | Ctrl + K Ctrl + D | 移动当前选择到下个匹配选择的位置 | 239 | | Alt + C / R / W | 不分大小写/使用正则/全字匹配 | 240 | 241 | ------ 242 | 243 | ### 多行光标操作与选择 244 | 245 | | 快捷键 | 作用 | 246 | | :------------------------------- | :--------------------------------------- | 247 | | Alt + Click | 插入光标-支持多个 | 248 | | Ctrl + Alt + ↑/↓ | 上下插入光标-支持多个 | 249 | | Ctrl + U | 撤销最后一次光标操作 | 250 | | Shift + Alt + I | 插入光标到选中范围内所有行结束符 | 251 | | Ctrl + L | 选中当前行 | 252 | | Ctrl + Shift + L | 选择所有出现在当前选中的行-操作 | 253 | | Ctrl + F2 | 选择所有出现在当前选中的词汇-操作 | 254 | | Shift + Alt + → | 从光标处扩展选中全行 | 255 | | Shift + Alt + ← | 收缩选择区域 | 256 | | Shift + Alt + (drag mouse) | 鼠标拖动区域,同时在多个行结束符插入光标 | 257 | | Ctrl + Shift + Alt + (Arrow Key) | 也是插入多行光标的[方向键控制] | 258 | | Ctrl + Shift + Alt + PgUp/PgDown | 也是插入多行光标的[整屏生效] | 259 | 260 | ------ 261 | 262 | ### 丰富的语言操作 263 | 264 | | 快捷键 | 作用 | 265 | | :------------------- | :----------------------------- | 266 | | Ctrl + Space | 输入建议[智能提示] | 267 | | Ctrl + Shift + Space | 参数提示 | 268 | | Tab | Emmet指令触发/缩进 | 269 | | Shift + Alt + F | 格式化代码 | 270 | | Ctrl + K Ctrl + F | 格式化选中部分的代码 | 271 | | F12 | 跳转到定义处 | 272 | | Alt + F12 | 代码片段显示定义 | 273 | | Ctrl + K F12 | 在其他窗口打开定义处 | 274 | | Ctrl + . | 快速修复部分可以修复的语法错误 | 275 | | Shift + F12 | 显示所有引用 | 276 | | F2 | 重命名符号 | 277 | | Ctrl + Shift + . / , | 替换下个值 | 278 | | Ctrl + K Ctrl + X | 移除空白字符 | 279 | | Ctrl + K M | 更改页面文档格式 | 280 | 281 | ------ 282 | 283 | ### 编辑器管理 284 | 285 | | 快捷键 | 作用 | 286 | | :------------------------- | :----------------------- | 287 | | Ctrl + F4, Ctrl + W | 关闭编辑器 | 288 | | Ctrl + k F | 关闭当前打开的文件夹 | 289 | | Ctrl + \ | 切割编辑窗口 | 290 | | Ctrl + 1/2/3 | 切换焦点在不同的切割窗口 | 291 | | Ctrl + K Ctrl ←/→ | 切换焦点在不同的切割窗口 | 292 | | Ctrl + Shift + PgUp/PgDown | 切换标签页的位置 | 293 | | Ctrl + K ←/→ | 切割窗口位置调换 | 294 | 295 | ------ 296 | 297 | ### 文件管理 298 | 299 | | 快捷键 | 作用 | 300 | | :----------------- | :------------------------------------- | 301 | | Ctrl + N | 新建文件 | 302 | | Ctrl + O | 打开文件 | 303 | | Ctrl + S | 保存文件 | 304 | | Ctrl + Shift + S | 另存为 | 305 | | Ctrl + K S | 保存所有当前已经打开的文件 | 306 | | Ctrl + F4 | 关闭当前编辑窗口 | 307 | | Ctrl + K Ctrl + W | 关闭所有编辑窗口 | 308 | | Ctrl + Shift + T | 撤销最近关闭的一个文件编辑窗口 | 309 | | Ctrl + K Enter | 保持开启 | 310 | | Ctrl + Shift + Tab | 调出最近打开的文件列表,重复按会切换 | 311 | | Ctrl + Tab | 与上面一致,顺序不一致 | 312 | | Ctrl + K P | 复制当前打开文件的存放路径 | 313 | | Ctrl + K R | 打开当前编辑文件存放位置【文件管理器】 | 314 | | Ctrl + K O | 在新的编辑器中打开当前编辑的文件 | 315 | 316 | ------ 317 | 318 | ### 显示 319 | 320 | | 快捷键 | 作用 | 321 | | :--------------- | :--------------------------- | 322 | | F11 | 切换全屏模式 | 323 | | Shift + Alt + 1 | 切换编辑布局【目前无效】 | 324 | | Ctrl + =/- | 放大 / 缩小 | 325 | | Ctrl + B | 侧边栏显示隐藏 | 326 | | Ctrl + Shift + E | 资源视图和编辑视图的焦点切换 | 327 | | Ctrl + Shift + F | 打开全局搜索 | 328 | | Ctrl + Shift + G | 打开Git可视管理 | 329 | | Ctrl + Shift + D | 打开DeBug面板 | 330 | | Ctrl + Shift + X | 打开插件市场面板 | 331 | | Ctrl + Shift + H | 在当前文件替换查询替换 | 332 | | Ctrl + Shift + J | 开启详细查询 | 333 | | Ctrl + Shift + U | 打开输出窗口 | 334 | | Ctrl + Shift + V | 预览Markdown文件 | 335 | | Ctrl + K V | 在边栏打开Markdown预览 | 336 | | Ctrl + K Z | Zen模式 | 337 | 338 | ------ 339 | 340 | ### 调试 341 | 342 | | 快捷键 | 作用 | 343 | | :---------------- | :------------------ | 344 | | F9 | 添加/解除断点 | 345 | | F5 | 启动调试 / 继续 | 346 | | F11 / Shift + F11 | 单步进入 / 单步跳出 | 347 | | F10 | 单步跳过 | 348 | | Ctrl + K Ctrl + I | 显示悬浮 | 349 | 350 | ------ 351 | 352 | ### 集成终端 353 | 354 | | 快捷键 | 作用 | 355 | | :-------------------- | :------------------- | 356 | | Ctrl + ` | 打开集成终端 | 357 | | Ctrl + Shift + ` | 创建一个新的终端 | 358 | | Ctrl + C | 复制所选 | 359 | | Ctrl + V | 复制到当前激活的终端 | 360 | | Shift + PgUp / PgDown | 页面上下翻屏 | 361 | | Ctrl + Home / End | 滚动到页面头部或尾部 | --------------------------------------------------------------------------------