1.1 GO语言基础
1 初识Go语言
1.1.1 开发环境搭建
参考文档:《Windows Go语言环境搭建》
1.2.1 Go语言特性-垃圾回收
a. 内存自动回收,再也不需要开发人员管理内存
b. 开发人员专注业务实现,降低了心智负担
c. 只需要new分配内存,不需要释放
d. gc 垃圾回收
1.2.2 Go语言特性-天然并发
a. 从语言层面支持并发,非常简单
b. goroutine,轻量级线程,创建成千上万个goroutine成为可能
c. 基于CSP(Communicating Sequential Process)模型实现
Go
package main
import(
"time"
)
func main() {
for i := 0; i < 100; i++ {
go test_goroute(i)
}
time.Sleep(time.Second)
}
进一步阅读:《 GO Channel并发、死锁问题》 https://www.cnblogs.com/show58/p/12699083.html
《 Go的CSP并发模型实现:M, P, G 》https://www.cnblogs.com/sunsky303/p/9115530.html
1.2.3 Go语言特性-channel
a. 管道,类似unix/linux中的pipe
b. 多个goroutine之间通过channel进行通信
c. 支持任何类型
Go
func main() {
pipe := make(chan int, 3)
pipe <- 1
pipe <- 2
}
Go
package main
import "fmt"
func test_pipe() {
pipe := make(chan int, 3)
pipe <- 1
pipe <- 2
pipe <- 3
var t1 int
t1 = <-pipe
fmt.Println("t1: ", t1)
}
func sum(s []int, c chan int) {
test_pipe()
sum := 0
for _, v := range s {
sum += v
}
fmt.Println("sum:", sum)
c <- sum // send sum to c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c) // 7+2+8 = 17, -9 + 4+0 = -5
go sum(s[len(s)/2:], c)
// x, y := <-c, <-c // receive from c
x := <-c
y := <-c
fmt.Println(x, y, x+y)
}
1.2.4 Go语言特性-多返回值
一个函数返回多个值
Go
func calc(a int, b int) (int, int) {
sum := a+b
avg := (a+b)/2
return sum, avg
}
1.4.1包的概念
-
和python一样,把相同功能的代码放到一个目录,称之为包
-
包可以被其他包引用
-
main包是用来生成可执行文件,每个程序只有一个main包
-
包的主要用途是提高代码的可复用性
2. Go语言基础
2 基本数据类型和操作符
-
文件名&关键字&标识符
-
Go程序基本结构
-
常量和变量
-
数据类型和操作符
-
字符串类型
2.1文件名&关键字&标识符
-
所有go源码以.go结尾
-
标识符以字母或下划线开头,大小写敏感,比如:
a. boy
b. Boy
c. a+b
d. 0boy
e. _boy
f. =_boy
g. _
-
_是特殊标识符,用来忽略结果
-
保留关键字
2.1文件名&关键字&标识符-关键字1

2.1文件名&关键字&标识符-关键字2
◼ var和const :变量和常量的声明
var varName type 或者 varName : = value
◼ package and import: 包和导入
◼ func: 用于定义函数和方法
◼ return :用于从函数返回
◼ defer someCode :在函数退出之前执行
◼ go : 用于并行
◼ select 用于选择不同类型的通讯
◼ interface 用于定义接口
◼ struct 用于定义抽象数据类型
2.1文件名&关键字&标识符-关键字3
◼ break、case、continue、for、fallthrough、else、if、switch、goto、default 流程控制
◼ fallthrough的用法注意总结 [推荐阅读
https://blog.csdn.net/ajdfhajdkfakr/article/details/79086125\]
◼ 1.加了fallthrough后,会直接运行【紧跟的后一个】case或default语句,不论条件是否满
足都会执行
◼ 2.加了fallthrough语句后,【紧跟的后一个】case条件不能定义常量和变量
◼ 3.执行完fallthrough后直接跳到下一个条件语句,本条件执行语句后面的语句不执行
◼ chan用于channel通讯
◼ type用于声明自定义类型
◼ map用于声明map类型数据
◼ range用于读取slice、map、channel数据
2.2 Go程序的基本结构1
-
任何一个代码文件隶属于一个包
-
import 关键字,引用其他包:
2.2 Go程序的基本结构2
- golang可执行程序,package main,
并且有且只有一个main入口函数
- 包中函数调用:
a. 同一个包中函数,直接调用
b. 不同包中函数,通过包名+点+
函数名进行调用
- 包访问控制规则:
a. 大写意味着这个函数/变量是可导出的
b. 小写意味着这个函数/变量是私有的,
包外部不能访问
2.4 常量1
-
常量使用const 修饰,代表永远是只读的,不能修改。
-
const 只能修饰boolean,number(int相关类型、浮点类型、complex)和string。
-
语法:const identifier [type] = value,其中type可以省略。
举例: const b string = "hello world"
const b = "hello world"
const Pi = 3.1414926
const a = 9/3
const c = getValue()
Go
package main
import (
"fmt"
"time"
)
const (
Man = 1
Female = 2
)
func main() {
for {
second := time.Now().Unix()
if second%Female == 0 {
fmt.Println("female")
} else {
fmt.Println("man")
}
time.Sleep(1000 * time.Millisecond)
}
}
2.4 常量2
- 比较优雅的写法:
尽量减少我们写代码 const (
a = 0
b = 1
c = 2
)
- 更加专业的写法:
const (
a = iota
b //1
c //2
)
2.5 变量1
- 语法:var identifier type
2.5 变量2
Var (
a int //默认为0
b string //默认为""
c bool //默认为false
d = 8
e = "hello world"
)
练习
写一个程序获取当前运行的操作系统名称和PATH环境环境变量的值,并打印在终端
Go
package main
import (
"fmt"
"os"
)
func main() {
var goos string = os.Getenv("GOOS")
fmt.Printf("The operating system is: %s\n", goos)
path := os.Getenv("Path")
fmt.Printf("Path is %s\n", path)
}
2.6 值类型和引用类型
- 值类型:变量直接存储值,内存通常在栈中分配。

- 引用类型:变量存储的是一个地址,这个地址存储最终的值。内存通常在堆上分配。通过GC回收。

2.6值类型和引用类型
-
值类型:基本数据类型int、float、bool、string以及数组和struct。
-
引用类型:指针、slice、map、chan等都是引用类型。
练习:写一个程序,交换两个整数的值。比如: a=3; b=4; 交换之后:a=4;b=3
代码:2-6-swap.go
Go
package main
import "fmt"
func swap(a *int, b *int) {
tmp := *a
*a = *b
*b = tmp
return
}
func swap1(a int, b int) (int, int) {
return b, a
}
func test() {
var a = 100
fmt.Println(a)
//var b int
for i := 0; i < 100; i++ {
var b = i * 2
fmt.Println(b)
}
//fmt.Println(c)
//fmt.Println(b)
}
func test2() {
var a int8 = 100
var b int16 = int16(a)
fmt.Printf("a=%d b=%d\n", a, b)
}
func main() {
first := 100
second := 200
//swap(&first, &second)
//first, second = swap1(first, second)
first, second = second, first
fmt.Println("first=", first)
fmt.Println("second=", second)
test()
test2()
}
练习:写一个程序用来打印值类型和引用类型变量到终端,并观察输出结果。
代码: 2-6-value_quote.go
Go
package main
import (
"fmt"
)
func modify(a int) {
a = 10
return
}
func modify1(a *int) {
*a = 10
}
func main() {
a := 5
b := make(chan int, 1)
fmt.Println("a=", a)
fmt.Println("b=", b)
modify(a)
fmt.Println("a=", a)
modify1(&a)
fmt.Println("a=", a)
}
2.7 变量的作用域
- 在函数内部声明的变量叫做局部变量,生命周期仅限于函数内部。
2.在函数外部声明的变量叫做全局变量,生命周期作用于整个包,如果是大写的,
则作用于整个程序。
请指出下面程序的输出是什么
Go
package main
var a = "G"
func main() {
n() // G
m() // O
n() //O
}
func n() {
fmt.Println(a)
}
func m() {
a = "O"
fmt.Println(a)
}
2.8 数据类型和操作符1
-
bool 类型,只能存 true 和 false
-
相关操作符, !、 && 、 ||
2.8 数据类型和操作符2 -
数字类型,主要有int、int8、int16、int32、int64、uint8、uint16、uint32、uint64、
float32、float64
- 类型转换,type(variable),比如:var a int=8; var b int32=int32(a)
Go
package main
func main() {
var a int
var b int32
a = 15
b = a + a // compiler error cannot use a + a (value of type int) as int32 value in assignment
b = b + 5 // ok: 5 is a constant
}
Go
package main
import (
"fmt"
"math/rand"
"time"
)
func init() {
rand.Seed(time.Now().UnixNano())
}
func main() {
for i := 0; i < 10; i++ {
a := rand.Int()
fmt.Println(a)
}
for i := 0; i < 10; i++ {
a := rand.Intn(100)
fmt.Println(a)
}
for i := 0; i < 10; i++ {
a := rand.Float32()
fmt.Println(a)
}
}
2.8 数据类型和操作符3
- 字符类型:var a byte
var a byte = 'c'
- 字符串类型: var str string
练习:请指出下面程序的输出是什么?
package main
import "fmt"
func main() {
var n int16 = 34
var m int32
m = n
m = int32(n)
fmt.Printf("32 bit int is: %d\n", m)
fmt.Printf("16 bit int is: %d\n", n)
}
PS D:\Workspace\Go\src\projects\demo> go run main.go
command-line-arguments
.\main.go:8:6: cannot use n (variable of type int16) as type int32 in assignment
练习:使用 math/rand 生成 10 个随机整数, 10 个小于 100 的
随机整数以及 10 个随机浮点数 代码:
2.9数据类型和操作符1
- 逻辑操作符: == 、!=、
<
、
<=
、
>和 >=
2.9数据类型和操作符2
- 数学操作符:+、-、
*
、/等等
Go
package main
import (
"fmt"
"strings"
"unsafe"
_ "unsafe"
)
func test1() {
bytes := []byte("I am byte array !")
str := string(bytes)
bytes[0] = 'i' //注意这一行,bytes在这里修改了数据,但是str打印出来的依然没变化,
fmt.Println(str)
}
func test2() {
bytes := []byte("I am byte array !")
str := (*string)(unsafe.Pointer(&bytes))
bytes[0] = 'i'
fmt.Println(*str)
}
func test3() {
var data [10]byte
data[0] = 'T'
data[1] = 'E'
var str string = string(data[:])
fmt.Println(str)
}
func str2bytes(s string) []byte {
x := (*[2]uintptr)(unsafe.Pointer(&s))
h := [3]uintptr{x[0], x[1], x[1]}
return *(*[]byte)(unsafe.Pointer(&h))
}
func bytes2str(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
func test4() {
s := strings.Repeat("abc", 3)
fmt.Println("str2bytes")
b := str2bytes(s)
fmt.Println("bytes2str")
s2 := bytes2str(b)
fmt.Println(b, s2)
}
func main() {
test1()
test2()
test3()
test4()
}
2.9 数据类型和操作符3
字符串表示两种方式: 1)双引号 2)`` (反引号)
Go
package main
import "fmt"
func main() {
var str = "hello world\n\n"
var str2 = `hello \n \n \n
this is a test string
This is a test string too·`
fmt.Println("str=", str)
fmt.Println("str2=", str2)
}
Go
package main
import (
"fmt"
)
func test_switch1() {
a := 2
switch a {
case 1:
fmt.Println("a=1")
case 2:
fmt.Println("a=2")
case 3:
fmt.Println("a=3")
case 4:
fmt.Println("a=4")
default:
fmt.Println("default")
}
}
func test_switch2() {
a := 2
switch a {
case 1:
fmt.Println("a=1")
case 2:
fmt.Println("a=2")
fallthrough
case 3:
fmt.Println("a=3")
case 4:
fmt.Println("a=4")
default:
fmt.Println("default")
}
}
func main() {
fmt.Printf("执行test_switch%d\n", 1)
test_switch1()
fmt.Printf("执行test_switch%d\n", 2)
test_switch2()
}
3. Go函数
3 流程控制
for range 语句
str := "hello world,中国"
for i, v := range str {
fmt.Printf("index[%d] val[%c]\n", i, v)
}
用来遍历数组、slice、map、chan。
Go
package main
import "fmt"
func modify(p *int) {
fmt.Println(p)
*p = 1000900
return
}
func main() {
var a int = 10
fmt.Println(&a)
var p *int
p = &a
fmt.Println("the address of p:", &p)
fmt.Println("the value of p:", p)
fmt.Println("the value of p point to variable:", *p)
fmt.Println(*p)
*p = 100
fmt.Println(a)
var b int = 999
p = &b
*p = 5
fmt.Println(a)
fmt.Println(b)
modify(&a)
fmt.Println(a)
}
3 函数1
- 声明语法:func 函数名 (参数列表) [(返回值列表)] {}
func add()
{
}
3 函数2
- golang函数特点:
a. 不支持重载,一个包不能有两个名字一样的函数
b. 函数是一等公民,函数也是一种类型,一个函数可以赋值给变量
c. 匿名函数
d. 多返回值
3 函数2
- golang函数特点:
Go
package main
import (
"fmt"
"reflect"
)
func add(a, b int) int {
return a + b
}
func main() {
c := add
fmt.Println(c)
sum := c(10, 20)
fmt.Println(sum)
sf1 := reflect.ValueOf(c)
sf2 := reflect.ValueOf(add)
if sf1 == sf2 {
fmt.Println("c equal add")
}
}
Go
package main
import (
"fmt"
)
type add_func func(int, int) int
func add(a, b int) int {
return a + b
}
func operator(op add_func, a int, b int) int {
return op(a, b)
}
func main() {
c := add
fmt.Println(c)
sum := operator(c, 100, 200)
fmt.Println(sum)
}
3 函数3
- 函数参数传递方式:
1). 值传递
2). 引用传递
注意1:无论是值传递,还是引用传递,传递给函数的都是变量的副本,不过,值
传递是值的拷贝。引用传递是地址的拷贝,一般来说,地址
拷贝更为高效。而值拷贝取决于拷贝的对象大小,对象越大,则性能越低。
3 函数3
- 函数参数传递方式: 1). 值传递
2). 引用传递
注意2:map、slice、chan、指针、interface默认以引用的方式传递,有待考证?
go只有值传递、浅拷贝
Go
package main
import "fmt"
func modify(a int) {
a = 100
}
func main() {
a := 8
fmt.Println(a)
modify(a)
fmt.Println(a)
}
3 函数4
- 命名返回值的名字
func add(a, b int) (c int) {
c = a + b
return
}
func calc(a, b int) (sum int, avg int) {
sum = a + b
avg = (a + b) / 2
return
}
3 函数5
- _标识符,用来忽略返回值:
func calc(a, b int) (sum int, avg int) {
sum = a + b
avg = (a +b)/2
return
}
func main() {
sum, _ := calc(100, 200)
}
3 函数6
- 可变参数: func add(arg...int) int {
}
0个或多个参数
func add(a int, arg...int) int {
}
1个或多个参数
func add(a int, b int, arg...int) int {
}
2个或多个参数
注意:其中arg是一个slice,我们可以通过arg[index]依次访问所有参数
通过len(arg)来判断传递参数的个数
Go
package main
import "fmt"
func add(a int, arg ...int) int {
var sum int = a
for i := 0; i < len(arg); i++ {
sum += arg[i]
}
return sum
}
func concat(a string, arg ...string) (result string) {
result = a
for i := 0; i < len(arg); i++ {
result += arg[i]
}
return
}
func main() {
sum := add(10, 3, 3, 3, 3)
fmt.Println(sum)
res := concat("hello", " ", "world")
fmt.Println(res)
}
3 函数7
-
defer用途:
-
当函数返回时,执行defer语句。因此,可以用来做资源清理
-
多个defer语句,按先进后出的方式执行
-
defer语句中的变量,在defer声明时就决定了。
3 函数7 defer用途
package main
import "fmt"
func main() {
i := 0
defer fmt.Println(i)
i++
return
}
打印是多少?
PS D:\Workspace\Go\src\projects\demo> go run main.go
0
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
defer fmt.Printf("%d", i)
}
}
PS D:\Workspace\Go\src\projects\demo> go run main.go
43210
3 函数7 defer用途
1 关闭文件句柄
func read() {
file := open(filename)
defer file.Close()
//文件操作
}
3 函数7 defer用途
- 锁资源释放
func read() {
mc.Lock()
defer mc.Unlock()
//其他操作
}
3 函数7 defer用途
- 数据库连接释放
func read() {
conn := openDatabase()
defer conn.Close()
//其他操作
}
4. Go数组和切片
4 常用结构
-
内置函数、闭包
-
数组与切片
-
map数据结构
-
package介绍
4.1 内置函数
-
close:主要用来关闭channel
-
len:用来求长度,比如string、array、slice、map、channel
-
new:用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针
-
make:用来分配内存,主要用来分配引用类型,比如chan、map、slice
-
append:用来追加元素到数组、slice中
-
panic和recover:用来做错误处理
-
new和make的区别

Go
package main
import (
"fmt"
"strings"
)
// 缩小变量作用域,减少对全局变量的污染。下面的累加如果用全局变量进行实现,全局变量容易被其他人污染。
// 同时,所有我要实现n个累加器,那么每次需要n个全局变量。利用闭包,
// 每个生成的累加器myAdder1, myAdder2 := adder(), adder()有自己独立的sum,sum可以看作为myAdder1.sum与myAdder2.sum。
func Adder() func(int) int {
var x int
f := func(d int) int {
x += d
return x
}
return f
}
func makeSuffix(suffix string) func(string) string {
f := func(name string) string {
if strings.HasSuffix(name, suffix) == false {
return name + suffix
}
return name
}
return f
}
func main() {
f := Adder()
fmt.Println(f(1))
fmt.Println(f(100))
// fmt.Println(f(1000))
/*
f1 := makeSuffix(".bmp")
fmt.Println(f1("test"))
fmt.Println(f1("pic"))
f2 := makeSuffix(".jpg")
fmt.Println(f2("test"))
fmt.Println(f2("pic"))
*/
}
4.2闭包
- 闭包:一个函数和与其相关的引用环境组合而成的实体
package main
import "fmt"
func main() {
var f = Adder()
fmt.Print(f(1)," - ")
fmt.Print(f(20)," - ")
fmt.Print(f(300))
}
func Adder() func(int) int {
var x int
return func(delta int) int {
x += delta
return x
}
}
4.2闭包 例子
package main
import (
"fmt"
"strings"
)
func makeSuffixFunc(suffix string) func(string) string {
return func(name string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
}
func main() {
func1 := makeSuffixFunc(".bmp")
func2 := makeSuffixFunc(".jpg")
fmt.Println(func1("test"))
fmt.Println(func2("test"))
}
4.3数组与切片
-
数组:是同一种数据类型的固定长度的序列。
-
数组定义:var a [len]int,比如:var a[5]int 一旦定义,长度不能变
-
长度是数组类型的一部分,因此,var a[5] int和var a[10]int是不同的类型
-
数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是:len-1
for i := 0; i < len(a); i++ {
}
-
访问越界,如果下标在数组合法范围之外,则触发访问越界,会panic
-
数组是值类型,因此改变副本的值,不会改变本身的值
arr2 := arr1
arr2[2] = 100
Go
package main
import "fmt"
func test1() {
var a [10]int
//j := 10
a[0] = 100
//a[j] = 200
fmt.Println(a)
for i := 0; i < len(a); i++ {
fmt.Println(a[i])
}
for index, val := range a {
fmt.Printf("a[%d]=%d\n", index, val)
}
}
func test3(arr *[5]int) {
(*arr)[0] = 1000
}
func test2() {
var a [10]int
b := a
b[0] = 100
fmt.Println(a)
}
func main() {
//test1()
test2()
var a [5]int
test3(&a)
fmt.Println(a)
}
4.3数组与切片-案例
package main
import (
"fmt"
)
func modify(arr [5]int) {
arr[0] = 100
return
}
func main() {
var a [5]int //数组大小是固定的
modify(a)
for i := 0; i < len(a); i++ {
fmt.Println(a[i])
}
}
package main
import (
"fmt"
)
func modify(arr *[5]int) {
(*arr)[0] = 100
return
}
func main() {
var a [5]int
modify(&a)
for i := 0; i < len(a); i++ {
fmt.Println(a[i])
}
}
4.3 数组与切片-数组
- 数组初始化 对于数组 []里面肯定要有东西
b. var age1 = [5]int{1,2,3,4,5}
c. var age2 = [...]int{1,2,3,4,5,6}
a. var age0 [5]int = [5]int{1,2,3}
d. var str = [5]string{3:"hello world", 4:"tom"}
- 多维数组
a. var age [5][3]int
b. var f [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}
- 多维数组遍历
package main
import (
"fmt"
)
func main() {
var f [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}
for k1, v1 := range f {
for k2, v2 := range v1 {
fmt.Printf("(%d,%d)=%d ", k1, k2, v2)
}
fmt.Println()
}
}
4.3 数组与切片-切片定义
-
切片:切片是数组的一个引用,因此切片是引用类型
-
切片的长度可以改变,因此,切片是一个可变的数组
-
切片遍历方式和数组一样,可以用len()求长度
-
cap可以求出slice最大的容量,0 <= len(slice) <= (array),其中array是slice引用的数组
-
切片的定义:var 变量名 []类型,比如 var str []string var arr []int
4.3 数组与切片-切片初始化
- 切片初始化:var slice []int = arr[start:end]
包含start到end之间的元素,但不包含end
-
Var slice []int = arr[0:end]可以简写为 var slice []int=arr[:end]
-
Var slice []int = arr[start:len(arr)] 可以简写为 var slice[]int = arr[start:]
-
Var slice []int = arr[0, len(arr)] 可以简写为 var slice[]int = arr[:]
-
如果要切片最后一个元素去掉,可以这么写:
Slice = slice[:len(slice)-1]
Go
package main
import "fmt"
type slice struct {
ptr *[100]int
len int
cap int
}
func make1(s slice, cap int) slice {
s.ptr = new([100]int)
s.cap = cap
s.len = 0
return s
}
func modify(s slice) {
s.ptr[1] = 1000
}
func testSlice2() {
var s1 slice
s1 = make1(s1, 10)
s1.ptr[0] = 100
modify(s1)
fmt.Println(s1.ptr)
}
func testSlice() {
var slice []int
var arr [5]int = [...]int{1, 2, 3, 4, 5}
slice = arr[:]
slice = slice[1:]
slice = slice[:len(slice)-1]
fmt.Println(slice)
fmt.Println(len(slice))
fmt.Println(cap(slice))
slice = slice[0:1]
fmt.Println(len(slice))
fmt.Println(cap(slice))
}
func modify1(a []int) {
a[1] = 1000
}
func testSlice3() {
var b []int = []int{1, 2, 3, 4}
modify1(b)
fmt.Println(b)
}
func testSlice4() {
var a = [10]int{1, 2, 3, 4}
b := a[1:5]
fmt.Printf("%p\n", b)
fmt.Printf("%p\n", &a[1])
}
func main() {
//testSlice()
//testSlice2()
//testSlice3()
testSlice4()
}
4.3 数组与切片-切片实战1
- 练习:写一个程序,演示切片的各个用法
代码:4-3-slice1.go
Go
package main
import "fmt"
func testSlice() {
var a [5]int = [...]int{1, 2, 3, 4, 5}
s := a[1:]
fmt.Printf("before len[%d] cap[%d]\n", len(s), cap(s))
s[1] = 100
fmt.Printf("s=%p a[1]=%p\n", s, &a[1])
fmt.Println("before a:", a)
s = append(s, 10)
s = append(s, 10)
fmt.Printf("after len[%d] cap[%d]\n", len(s), cap(s))
s = append(s, 10)
s = append(s, 10)
s = append(s, 10)
s[1] = 1000
fmt.Println("after a:", a)
fmt.Println(s)
fmt.Printf("s=%p a[1]=%p\n", s, &a[1])
}
func testCopy() {
var a []int = []int{1, 2, 3, 4, 5, 6}
b := make([]int, 1)
copy(b, a)
fmt.Printf("copy len[%d] cap[%d]\n", len(b), cap(b))
fmt.Println(b)
}
func testString() {
s := "hello world"
s1 := s[0:5]
s2 := s[6:]
fmt.Println(s1)
fmt.Println(s2)
}
func testModifyString() {
s := "我hello world"
s1 := []rune(s)
s1[0] = 200
s1[1] = 128
s1[2] = 64
str := string(s1)
fmt.Println(str)
}
func main() {
//testSlice()
//testCopy()
//testString()
testModifyString()
}
4.3 数组与切片-切片实战2
- 切片的内存布局,类似C++ vector:

- 练习,写一个程序,演示切片的内存布局
4.3 数组与切片-切片实战3
- 通过make来创建切片
var slice []type = make([]type, len)
slice := make([]type, len)
slice := make([]type, len, cap)

4.3 数组与切片-切片实战4
- 用append内置函数操作切片
slice = append(slice, 10)
var a = []int{1,2,3}
var b = []int{4,5,6}
a = append(a, b...)
- For range 遍历切片
for index, val := range slice {
}
- 切片resize
var a = []int {1,3,4,5}
b := a[1:2]
b = b[0:3]
- 切片拷贝
s1 := []int{1,2,3,4,5}
s2 := make([]int, 10)
copy(s2, s1)
s3 := []int{1,2,3}
s3 = append(s3, s2...)
s3 = append(s3, 4,5,6)
4-3-slice-make.go
4.3 数组与切片-切片实战5
- string与slice
string底层就是一个byte的数组,因此,也
可以进行切片操作
str := "hello world"
s1 := str[0:5]
fmt.Println(s1)
s2 := str[5:]
fmt.Println(s2)
9. string 的底层布局
4.3 数组与切片-切片实战6
- 如何改变string中的字符值?
string本身是不可变的,因此要改变string中字符,需要如下操作:
str := "hello world"
s := []byte(str)
s[0] = 'o'
str = string(s)
4.4 数组与切片的区别1
它们的定义:
数组:类型 [n]T 表示拥有 n 个 T 类型的值的数组。
切片:类型 []T 表示一个元素类型为 T 的切片。
数组的例子
var x[3]int = [3]int{1,2,3}
var y[3]int = x
fmt.Println(x,y)
y[0]=999
fmt.Println(x,y)
切片的例子
var x[]int = []int{1,2,3}
var y[]int = x
fmt.Println(x,y)
y[0]=999
fmt.Println(x,y)
4.4 数组与切片的区别2
它们的定义:
数组:类型 [n]T 表示拥有 n 个 T 类型的值的数组。
切片:类型 []T 表示一个元素类型为 T 的切片。
数组是需要指定个数的,而切片则不需要。数组赋值也可是使用如下方式,忽略元素个数,使用
"..."代替
x:= [...]int{1,2,3}
y := x
fmt.Println(x,y)
y[0]=999
fmt.Println(x,y)
4.5 new和make的区别
new
func main() {
var i *int
i=new(int)
*i=10
fmt.Println(*i)
}
make
func make(t Type, size ...IntegerType) Type func new(Type) *Type
make也是用于内存分配的,但是和new不同,它只用于
chan、map以及切片的内存创建,而且它返回的类型就是这
三个类型本身,而不是他们的指针类型,因为这三种类型
就是引用类型,所以就没有必要返回他们的指针了。
5. Go test方法
5 Go test
前置条件:
1、文件名须以"_test.go"结尾
2、方法名须以"Test"打头,并且形参为 (t *testing.T)
5 Go test 举例
举例:gotest.go
package mytest
import (
"errors"
)
func Division(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("除数不能为0")
}
return a / b, nil
}
gotest_test.go
package mytest
import (
"testing"
)
func Test_Division_1(t *testing.T) {
if i, e := Division(6, 2); i != 3 || e != nil { //try a unit test on function
t.Error("除法函数测试没通过") // 如果不是如预期的那么就报错
} else {
t.Log("第一个测试通过了") //记录一些你期望记录的信息
}
}
func Test_Division_2(t *testing.T) {
if _, e := Division(6, 0); e == nil { //try a unit test on function
t.Error("Division did not work as expected.") // 如果不是如预期的那么就
报错
} else {
t.Log("one test passed.", e) //记录一些你期望记录的信息
}
}
5 Go test 测试
- 在目录下执行 go test 是测试目录所有以XXX_test.go 结尾的文件。
2.测试单个方法
go test -v -run="Test_Division_1" -count 5
3.查看帮助 go help test
Go
package mytest
import (
"testing"
)
func Test_Division_1(t *testing.T) {
if i, e := Division(6, 2); i != 3 || e != nil { //try a unit test on function
t.Error("除法函数测试没通过") // 如果不是如预期的那么就报错
} else {
t.Log("第一个测试通过了") //记录一些你期望记录的信息
}
}
func Test_Division_2(t *testing.T) {
if _, e := Division(6, 0); e == nil { //try a unit test on function
t.Error("Division did not work as expected.") // 如果不是如预期的那么就报错
} else {
t.Log("one test passed.", e) //记录一些你期望记录的信息
}
}
5 Go test 命令介绍1
通过go help test可以看到go test的使用说明:
格式形如:
go test [-c] [-i] [build flags] [packages] [flags for test binary]
参数解读:
-c : 编译go test成为可执行的二进制文件,但是不运行测试。
-i : 安装测试包依赖的package,但是不运行测试。
关于build flags,调用go help build,这些是编译运行过程中需要使用到的参数,一般设置为空
关于packages,调用go help packages,这些是关于包的管理,一般设置为空
关于flags for test binary,调用go help testflag,这些是go test过程中经常使用到的参数
-test.v : 是否输出全部的单元测试用例(不管成功或者失败),默认没有加上,所以只输出失败的单元测试用例。
-test.run pattern: 只跑哪些单元测试用例
-test.bench patten: 只跑那些性能测试用例
-test.benchmem : 是否在性能测试的时候输出内存情况
-test.benchtime t : 性能测试运行的时间,默认是1s
-test.cpuprofile cpu.out : 是否输出cpu性能分析文件
-test.memprofile mem.out : 是否输出内存性能分析文件
5 Go test 命令介绍2-续
-test.blockprofile block.out : 是否输出内部goroutine阻塞的性能分析文件
-test.memprofilerate n : 内存性能分析的时候有一个分配了多少的时候才打点记录的问题。这个参数就是设置打
点的内存分配间隔,也就是profile中一个sample代表的内存大小。默认是设置为512 * 1024的。如果你将它设置
为1,则每分配一个内存块就会在profile中有个打点,那么生成的profile的sample就会非常多。如果你设置为0,
那就是不做打点了。
你可以通过设置memprofilerate=1和GOGC=off来关闭内存回收,并且对每个内存块的分配进行观察。
-test.blockprofilerate n: 基本同上,控制的是goroutine阻塞时候打点的纳秒数。默认不设置就相当于-
test.blockprofilerate=1,每一纳秒都打点记录一下
-test.parallel n : 性能测试的程序并行cpu数,默认等于GOMAXPROCS。
-test.timeout t : 如果测试用例运行时间超过t,则抛出panic
-test.cpu 1,2,4 : 程序运行在哪些CPU上面,使用二进制的1所在位代表,和nginx的nginx_worker_cpu_affinity是一个道理
-test.short : 将那些运行时间较长的测试用例运行时间缩短
1.2 Go语言接口与反射
Go语言接口与反射
-
结构
-
接口
-
反射
1. 结构
1.1 struct简介
◼ Go通过结构体struct和interface实现oop(面向对象编程)
◼ struct的成员(也叫属性或字段)可以是任何类型,如普通类型、复合类型、函数、map、interface、
struct等

1.2 struct详解-struct定义

1.2 struct详解-声明与初始化
声明与初始化
var stu1 Student
var stu2 *Student= &Student{} //简写stu2 := &Student{}
var stu3 *Student = new(Student) //简写stu3 := new(Student)
1.2 struct详解-struct使用
◼ 访问其成员都使用 "."
◼ struct 分配内存使用 new ,返回的是指针
◼ struct 没有构造函数,但是我们可以自己定义"构造函数"
◼ struct 是我们自己定义的类型,不能和其他类型进行强制转换
type Student struct {
name string
age int
Class string
}
Go
var stu1 Student
stu1.age = 34
stu1.name = "dar"
stu1.Class = "class1"
fmt.Println(stu1.name) //dar
var stu2 *Student = new(Student)
stu2.name = "ki"
stu2.age = 33
fmt.Println(stu2.name, (*stu2).name) //ki
var stu3 *Student = &Student{name: "rose", age: 18, Class: "class3"}
fmt.Println(stu3.name, (*stu3).name) //rose rose
Go
package main
import (
"fmt"
"unsafe"
)
type Student struct {
name string
age int32 // 小写 私密 只能在自己的包里面用
Class string // 大写 公开 类似C++ public
}
func main() {
// 1 值形式
var stu1 Student // 里面的变量全是零 栈上的
fmt.Println("stu1:", stu1)
stu1.age = 34
stu1.name = "dar"
stu1.Class = "class1"
fmt.Println(stu1.name) //dar
// 2 new 函数创建
var stu2 *Student = new(Student) // new出来的是堆上
stu2.name = "king"
stu2.age = 33
fmt.Println(stu2.name, (*stu2).name) //king
// &形式创建
var stu3 *Student = &Student{
name: "rose",
age: 18,
Class: "class3", // 如果分行的时候每行都要,
}
// var stu3 *Student = &Student{name: "rose", age: 18, Class: "class3"}
fmt.Println(stu3.name, (*stu3).name) //rose rose
fmt.Printf("addr: %p, %p, %p\n", &stu1, stu2, stu3)
// 值 初始化
var stu4 Student = Student{ // KV 形式初始化值
name: "老师",
age: 18,
Class: "Go", // 注意这里的逗号不能少
}
fmt.Println("stu4:", stu4) // stu4: {柚子老师 18 }
// 值顺序初始化
var stu5 Student = Student{ // 顺序形式 形式初始化值
"1",
18,
"音视频", // 注意这里的逗号不能少
}
fmt.Println("stu5:", stu5)
// nil结构体
var stu6 *Student = nil
fmt.Println("stu6:", stu6)
// 结构体大小
fmt.Println("unsafe.Sizeof(stu5):", unsafe.Sizeof(stu5))
fmt.Println("unsafe.Sizeof(stu6):", unsafe.Sizeof(stu6))
// fmt.Println("unsafe.Sizeof(string):", unsafe.Sizeof(string))
// fmt.Println("unsafe.Sizeof(int):", unsafe.Sizeof(int))
}
1.2 struct详解-自定义构造函数
◼ 通过工厂模式自定义构造函数方法
func Newstu(name1 string,age1 int,class1 string) *Student {
return &Student{name:name1,age:age1,Class:class1}
}
func main() {
stu1 := Newstu("dar",34,"math")
fmt.Println(stu1.name) // dar
}
Go
package main
import "fmt"
type Student struct {
name string
age int
Class string
}
func Newstu(name1 string, age1 int, class1 string) *Student {
return &Student{name: name1, age: age1, Class: class1}
}
func main() {
stu1 := Newstu("dar", 34, "math")
fmt.Println(stu1.name) // dar
}
1.3 struct tag
◼ tag可以为结构体的成员添加说明或者标签便于使用,这些说明可以通过反射获取到。
◼ 结构体中的成员首字母小写对外不可见,但是我们把成员定义为首字母大写这样与外界进行数据
交互会带来极大的不便,此时tag带来了解决方法
type Student struct {
Name string "the name of student"
Age int "the age of student"
Class string "the class of student"
}
1.3 struct tag --应用场景json示例
应用场景示例,json序列化操作(序列化和反序列化演示)
Go
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
var stu = Student{Name: "dar", Age: 34}
data, err := json.Marshal(stu)
if err != nil {
fmt.Println("json encode failed err:", err)
return
}
fmt.Println(string(data)) //{"name":"dar","age":34}
}
Go
package main
import (
"encoding/json"
"fmt"
)
// stu: 序列化后:{"name":"dar","age":34}
// {"name1":"dar","age2":34}
type Student struct {
Name string `json:"name1"`
Age int `json:"age2"`
}
func main() {
var stu = Student{Name: "dar", Age: 34}
data, err := json.Marshal(stu) // {"name1":"dar","age2":34}
if err != nil {
fmt.Println("json encode failed err:", err)
return
}
fmt.Println("stu: ", string(data)) //{"name":"dar","age":34}
var stu2 Student
err = json.Unmarshal(data, &stu2) // 反序列化
fmt.Println("stu2: ", stu2) // {dar 34}
}
1.4 struct匿名成员(字段、属性)
◼ 结构体中,每个成员不一定都有名称,也允许字段没有名字,即匿名成员。
◼ 匿名成员的一个重要作用,可以用来实现oop中的继承。
◼ 同一种类型匿名成员只允许最多存在一个。
◼ 当匿名成员是结构体时,且两个结构体中都存在相同字段时,优先选择最近的字段。
type Person struct {
Name string
Age int
}
type Student struct {
score string
Age int
Person // 匿名内嵌结构体
}
Go
func main() {
var stu = new(Student)
stu.Age = 34 //优先选择Student中的Age
fmt.Println(stu.Person.Age, stu.Age) // 0,34
}
Go
// 1.4 struct匿名成员(字段、属性)
package main
import "fmt"
type Person struct {
Name string
Age int
}
type Student struct {
score string
Age int
Person // 匿名内嵌结构体
}
func main() {
var stu = new(Student)
stu.Age = 22 //优先选择Student中的Age
fmt.Println(stu.Person.Age, stu.Age) // 0,22
var stu2 = Student{
score: "100",
Age: 20,
Person: Person{
Name: "柚子老师",
Age: 18,
},
}
fmt.Println("stu2: ", stu2)
}
1.5 struct-继承、多继承
◼ 当结构体中的成员也是结构体时,该结构体就继承了这个结构体,继承了其所有的方法与属性,当然有多个
结构体成员也就是多继承。
◼ 访问父结构中属性也使用".",但是当子结构体中存在和父结构中的字段相同时候,只能使用:"子结构体.父
结构体.字段"访问父结构体中的属性,如上面示例的stu.Person.Age
◼ 继承结构体可以使用别名,访问的时候通过别名访问,如下面示例man1.job.Salary:
type Person struct {
Name string
Age int
}
type Teacher struct {
Salary int
Classes string
}
Go
type man struct {
sex string
job Teacher //别名,继承Teacher 这个时候就不是匿名了
Person //继承Person
}
func main() {
var man1 = new(man)
man1.Age = 34
man1.Name = "dar"
man1.job.Salary = 100000
fmt.Println(man1, man1.job.Salary) //&{ {8500 } {dar 34}} 8500
}
Go
// 1.5 struct-继承、多继承
package main
import "fmt"
type Person struct {
Name string
Age int
}
type Teacher struct {
Salary int
Class string
}
type Man struct {
sex string
job Teacher //别名,继承Teacher
Person //继承Person
}
func main() {
var man1 = new(Man)
man1.Age = 34
man1.Name = "dar"
man1.job.Salary = 100000
fmt.Println("man1:", man1, man1.job.Salary) //&{ {8500 } {dar 34}} 8500
var man2 = Man{
sex: "女",
job: Teacher{
Salary: 8000,
Class: "班班",
},
Person: Person{ // 匿名初始化方式
Name: "老师",
Age: 18,
},
}
fmt.Println("man2", man2)
}
1.6 struct-结构体中的方法
方法是什么
◼ Go方法是作用在接受者(个人理解成作用对象)上的一个函数,接受者是某种类型的变量,因此方法是一种特殊
类型的函数。
◼ 接收者可以是任何类型,不仅仅是结构体,Go中的基本类型(int,string,bool等)也是可以,或者说数组的别名
类型,甚至可以是函数类型。但是,接受者不能是一个接口类型,因为接口是一个抽象的定义,方法是一个具体
实现。
◼ 一个类型加上它的方法等价于面向对象中的一个类。一个重要的区别是:在 Go 中,类型的代码和绑定在它上面的
方法的代码可以不放置在一起,它们可以存在在不同的源文件,唯一的要求是:它们必须是同一个包的。
◼ 类型 T(或 *T)上的所有方法的集合叫做类型 T(或 *T)的方法集。
◼ 因为方法是函数,所以同样的,不允许方法重载,即对于一个类型只能有一个给定名称的方法。但是如果基于接
收者类型,是有重载的:具有同样名字的方法可以在 2 个或多个不同的接收者类型上存在,比如在同一个包里这么
做是允许的
◼ 别名类型不能有它原始类型上已经定义过的方法(因为别名类型和原始类型底层是一样的)。
定义方法的格式
func(recv recevier_type(结构体))methodName(parameter_list)(return_value_list){}
1.6 struct-结构体中的方法-示例
type Person struct {
Name string
Age int
}
func (p Person) Getname() string { //p代表结构体本身的实列,类似python中的self,这里p可以写为self
fmt.Println(p.Name)
return p.Name
}
func main() {
var person1 = new(Person)
person1.Age = 34
person1.Name = "dar"
person1.Getname() // dar
}
结构体的指针方法

Go
package main
import (
"fmt"
"math"
)
type Circle struct {
x int
y int
Radius int
}
// 面积
func (c Circle) Area() float64 {
return math.Pi * float64(c.Radius) * float64(c.Radius)
}
// 周长
func (c Circle) Circumference() float64 {
return 2 * math.Pi * float64(c.Radius)
}
func (c Circle) expand() {
c.Radius *= 2
}
func (c *Circle) expand2() {
c.Radius *= 2
}
func main() {
var c = Circle{Radius: 50}
fmt.Println(c.Area(), c.Circumference())
// 指针变量调用方法形式上是一样的
var pc = &c
pc.expand2()
fmt.Println(pc.Area(), pc.Circumference())
}
Go
type Person struct {
Name string
Age int
}
func (p Person) Getname() string { //p代表结构体本身的实列,类似python中的self,这里p可以写为self
fmt.Println(p.Name)
return p.Name
}
func main() {
var person1 = new(Person)
person1.Age = 34
person1.Name = "dar"
person1.Getname() // dar
}
1.6 struct-结构体中的方法-方法和函数的区别
◼ 方法只能被其接受者调用
◼ 接收者是指针时,方法可以改变接受者的值(或状态),函数也能做到
◼ 接受者和方法必须在同一个包内
1.6 struct-结构体中的方法-方法的接受者是值或者指针的区别
◼ 当接受者是一个值的时候,这个值是该类型实例的拷贝
◼ 如果想要方法改变接受者的数据,就在接受者的指针类型上定义该方法。否则,就在普
通的值类型上定义方法。
总结:指针方法和值方法都可以在指针或者非指针上被调用。也就是说,方法接收者是
指针类型时,指针类型的值也是调用这个方法,反之亦然。
1.7 struct-内存分布
go中的结构体内存布局和c结构体布局类似,每个成员的内存分布是连续的

Go
type Student struct {
Name string
Age int64
wight int64
high int64
score int64
}

Go
package main
import (
"fmt"
"reflect"
)
// type Student struct {
// Name string // 16
// Age int64 // 8
// wight int64 // 8
// high int64 // 8
// score int64 // 8
// }
type Student struct {
Name string // 16 有两个变量: 指针, length
Age int8 //
wight int64 // 8
high int8 // 8
score int64 // 8
}
func main() {
var stu1 = new(Student)
stu1.Name = "只为你"
fmt.Printf("地址分布:")
fmt.Printf("%p\n", &stu1.Name)
fmt.Printf("%p\n", &stu1.Age)
fmt.Printf("%p\n", &stu1.wight)
fmt.Printf("%p\n", &stu1.high)
fmt.Printf("%p\n", &stu1.score)
typ := reflect.TypeOf(Student{})
fmt.Printf("Struct is %d bytes long\n", typ.Size())
// We can run through the fields in the structure in order
n := typ.NumField()
for i := 0; i < n; i++ {
field := typ.Field(i) // 反射出filed
fmt.Printf("%s at offset %v, size=%d, align=%d\n",
field.Name, field.Offset, field.Type.Size(),
field.Type.Align())
}
}
2. 接口
2.1 interface简介
interface(接口)是golang最重要的特性之一,Interface类型可以定义一组方法,但是这些不需要实
现。并且interface不能包含任何变量。
◼ interface 是方法的集合
◼ interface是一种类型,并且是指针类型
◼ interface的 更重要的作用在于多态实现
◼ interface 不能包含任何变量
2.2 interface定义
type 接口名称 interface {
method1 (参数列表) 返回值列表
method2 (参数列表) 返回值列表
...
}
2.3 interface使用
◼ 接口的使用不仅仅针对结构体,自定义类型、变量等等都可以实现接口。
◼ 如果一个接口没有任何方法,我们称为空接口,由于空接口没有方法,任意结构体都
隐式地实现了空接口。
◼ 要实现一个接口,必须实现该接口里面的所有方法。

Go
// 2.3 interface使用
package main
import "fmt"
//定义接口
type Skills interface {
Running()
Getname() string
}
type Student struct {
Name string
Age int
}
type Teacher struct {
Name string
Age int
}
// 实现接口
func (p Student) Getname() string { //实现Getname方法
fmt.Println(p.Name)
return p.Name
}
func (p Student) Running() { // 实现 Running方法
fmt.Printf("%s running\n", p.Name)
}
func (p Teacher) Getname() string { //实现Getname方法
fmt.Println(p.Name)
return p.Name
}
func (p Teacher) Running() { // 实现 Running方法
fmt.Printf("%s running\n", p.Name)
}
func (p Teacher) Running2() { // 实现 Running方法
fmt.Printf("%s running\n", p.Name)
}
// 想用接口,那就要实现对应接口的所有方法
func main() {
var skill Skills // 一个接口变量
var stu1 Student // 结构体变量
stu1.Name = "dar"
stu1.Age = 34
skill = stu1
skill.Running() //调用接口
var teacher Teacher = Teacher{"老师", 18}
skill = teacher
skill.Running() //调用接口
teacher.Running()
}
Go
// 空接口
package main
import "fmt"
// Go 语言为了避免用户重复定义很多空接口,它自己内置了一个,这个空接口的名字特别奇怪,叫 interface{}
/*
空接口里面没有方法,所以它也不具有任何能力,其作用相当于 Java 的 Object 类型,可以容纳任意对象,
它是一个万能容器。比如一个字典的 key 是字符串,但是希望 value 可以容纳任意类型的对象,
类似于 Java 语言的 Map 类型,这时候就可以使用空接口类型 interface{}。*/
// 空接口 map 里面用
func main() {
// map k-v 一个map里面是有key都是同一类型,value也是同一类型
// map[string]int k-string v-int
// 连续两个大括号,是不是看起来很别扭
// 代码中 user 字典变量的类型是 map[string]interface{},
// 从这个字典中直接读取得到的 value 类型是 interface{},需要通过类型转换才能得到期望的变量。
var user = map[string]interface{}{
"age": 30,
"address": "Beijing",
"married": true,
}
fmt.Println(user)
// 类型转换语法来了
var age = user["age"].(int)
var address = user["address"].(string)
var married = user["married"].(bool)
fmt.Println(age, address, married)
user["price"] = 5.5
var price = user["price"].(float64) // ?报错?
fmt.Println("user: ", user, price)
fmt.Println("user2: ")
var user2 = map[interface{}]interface{}{
111: 30,
"address2": "Beijing",
1.2: true,
}
fmt.Println("user2: ", user2)
}
2.4 interface多态
◼ go语言中interface是实现多态的一种形式,所谓多态,就是一种事物的多种形态
◼ 同一个interface,不同的类型实现,都可以进行调用,它们都按照统一接口进行操作
Go
// 2.4 interface多态
package main
import "fmt"
type Skills interface {
Running()
Getname() string
}
type Student struct {
Name string
Age int
}
type Teacher struct {
Name string
Salary int
}
func (p Student) Getname() string { //实现Getname方法
fmt.Println(p.Name)
return p.Name
}
func (p Student) Running() { // 实现 Running方法
fmt.Printf("%s running", p.Name)
}
func (p Teacher) Getname() string { //实现Getname方法
fmt.Println(p.Name)
return p.Name
}
func (p Teacher) Running() { // 实现 Running方法
fmt.Printf("\n%s running", p.Name)
}
func main() {
var skill Skills
var stu1 Student
var t1 Teacher
t1.Name = "ki"
stu1.Name = "dar"
stu1.Age = 22
skill = stu1
skill.Running()
skill = t1
t1.Running()
}
2.5 interface接口嵌套
◼ go语言中的接口可以嵌套,可以理解为继承,子接口拥有父接口的所有方法
◼ 如果使用该子接口,必须将父接口和子接口的所有方法都实现
Go
type Skills interface {
Running()
Getname() string
}
type Test interface {
sleeping()
Skills //继承Skills
}
Go
// 2.5 interface接口嵌套
package main
import "fmt"
type Skills interface {
Running()
// Running(is int) // 函数名是唯一的
Getname() string
}
type Test interface {
Sleeping()
Skills //继承Skills
}
type Student struct {
Name string
Age int
}
type Teacher struct {
skill Skills // skill也只能当变量去用
Name string
Salary int
}
func (p Student) Getname() string { //实现Getname方法
fmt.Println(p.Name)
return p.Name
}
func (p Student) Running() { // 实现 Running方法
fmt.Printf("%s running", p.Name)
}
func (p Teacher) Getname() string { //实现Getname方法
fmt.Println(p.Name)
return p.Name
}
func (p Teacher) Running() { // 实现 Running方法
fmt.Printf("\n%s running", p.Name)
}
func (p Teacher) Sleeping() { // 实现 Running方法
fmt.Printf("\n%s Sleeping", p.Name)
}
func main() {
var skill Skills
var stu1 Student
var t1 Teacher
t1.Name = "ki"
stu1.Name = "dar"
stu1.Age = 22
skill = stu1
skill.Running()
skill = t1
t1.Running()
var test Test
test = t1
test.Sleeping()
// test = stu1
}
2.6 interface接口组合
◼ 接口的定义也支持组合继承

Go
// 2.6 接口的组合继承
package main
import "fmt"
// 可以闻
type Smellable interface {
smell()
}
// 可以吃
type Eatable interface {
eat()
}
type Fruitable interface {
Smellable
Eatable
}
// 苹果既可能闻又能吃
type Apple struct{}
func (a Apple) smell() {
fmt.Println("apple can smell")
}
func (a Apple) eat() {
fmt.Println("apple can eat")
}
// 花只可以闻
type Flower struct{}
func (f Flower) smell() {
fmt.Println("flower can smell")
}
// func TestType(items ...interface{}) {
// for k, v := range items {
// switch v.(type) {
// case string:
// fmt.Printf("type is string, %d[%v]\n", k, v)
// case bool:
// fmt.Printf("type is bool, %d[%v]\n", k, v)
// case int:
// fmt.Printf("type is int, %d[%v]\n", k, v)
// case float32, float64:
// fmt.Printf("type is float, %d[%v]\n", k, v)
// case Smellable:
// fmt.Printf("type is Smellable, %d[%v]\n", k, v)
// case *Smellable:
// fmt.Printf("type is *Smellable, %d[%p]\n", k, v)
// case Eatable:
// fmt.Printf("type is Eatable, %d[%v]\n", k, v)
// case *Eatable:
// fmt.Printf("type is Eatable, %d[%p]\n", k, v)
// case Fruitable:
// fmt.Printf("type is Fruitable, %d[%v]\n", k, v)
// case *Fruitable:
// fmt.Printf("type is Fruitable, %d[%p]\n", k, v)
// }
// }
// }
func main() {
var s1 Smellable
var s2 Eatable
var apple = Apple{}
var flower = Flower{}
s1 = apple
s1.smell()
s1 = flower
s1.smell()
s2 = apple
s2.eat()
fmt.Println("\n组合继承")
var s3 Fruitable
s3 = apple
s3.smell()
s3.eat()
// TestType(s1, s2, s3, apple, flower)
}
2.7 interface类型转换
由于接口是一般类型,当我们使用接口时候可能不知道它是那个类型实现的,
基本数据类型我们有对应的方法进行类型转换,当然接口类型也有类型转换。
var s int
var x interface
x = s
y , ok := x.(int) //将interface 转为int,ok可省略 但是省略以后转换失败会报错,
true转换成功,false转换失败, 并采用默认值
Go
// 2.7 interface类型转换
package main
import "fmt"
func main() {
var x interface{}
s := "dar"
x = s // 为什么能赋值到空接口, 每种类型都已经隐藏实现了空接口
y, ok := x.(int)
z, ok1 := x.(string)
fmt.Println(y, ok)
fmt.Println(z, ok1)
}
//0 false
//dar true
2.8 interface类型判断
func TestType(items ...interface{}) {
for k, v := range items {
switch v.(type) {
case string:
fmt.Printf("type is string, %d[%v]\n", k, v)
case bool:
fmt.Printf("type is bool, %d[%v]\n", k, v)
case int:
fmt.Printf("type is int, %d[%v]\n", k, v)
case float32, float64:
fmt.Printf("type is float, %d[%v]\n", k, v)
case Student:
fmt.Printf("type is Student, %d[%v]\n", k, v)
case *Student:
fmt.Printf("type is Student, %d[%p]\n", k, v)
}
}
}
Go
package main
import "fmt"
type Student struct {
Name string
}
func TestType(items ...interface{}) {
for k, v := range items {
switch v.(type) {
case string:
fmt.Printf("type is string, %d[%v]\n", k, v)
case bool:
fmt.Printf("type is bool, %d[%v]\n", k, v)
case int:
fmt.Printf("type is int, %d[%v]\n", k, v)
case float32, float64:
fmt.Printf("type is float, %d[%v]\n", k, v)
case Student:
fmt.Printf("type is Student, %d[%v]\n", k, v)
case *Student:
fmt.Printf("type is Student, %d[%p]\n", k, v)
}
}
}
func main() {
var stu Student
TestType("dar", 100, stu, 3.3)
}
//type is string, 0[dar]
//type is int, 1[100]
//type is Student, 2[{}]
//type is float, 3[3.3]
2.9 指向指针的接口变量

3 reflect反射是什么,为什么需要反射
反射定义:在计算机科学中,反射是指计算机程序在运行时(Run time)可以访问、检测和修
改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够"观察"并且修
改自己的行为。
GO 反射的意义:Go 语言的 ORM 库离不开它,Go 语言的 json 序列化库离不开它, fmt包字
符串格式化离不开它,Go 语言的运行时更是离不开它。
反射的目标:
- 获取变量的类型信息,例如这个类型的名称、占用字节数、所有的方法列表、所有的内部字段结
构、它的底层存储类型等等。
- 动态的修改变量内部字段值。比如 json 的反序列化,你有的是对象内部字段的名称和相应的值,
你需要把这些字段的值循环填充到对象相应的字段里
Go
package main
import "fmt"
type Rect struct {
Width int
Height int
}
func main() {
var a interface{}
var r = Rect{50, 50}
a = &r // 指向了结构体指针
var rx = a.(*Rect) // 转换成指针类型
r.Width = 100
r.Height = 100
fmt.Println("r:", r)
fmt.Println("rx:", rx)
fmt.Printf("rx:%p, r:%p\n", rx, &r)
}
3 reflect反射
◼ go语言中的反射通过refect包实现,reflect包实现了运行时反射,允许程序操作任意类型的对象
◼ reflect包中的两个关键数据类Type和Value
func TypeOf(v interface{}) Type // 返回类型 实际是接口
func ValueOf(v interface{}) Value // 返回值 结构体

Go
/*
但是有时你希望在运行时使用变量的在编写程序时还不存在的信息。
比如你正在尝试将文件或网络请求中的数据映射到变量中。或者你想构建一个适用于不同类型的工具。
在这种情况下,你需要使用反射。反射使您能够在运行时检查类型。
它还允许您在运行时检查,修改和创建变量,函数和结构体。
*/
package main
import (
"fmt"
"reflect"
"strings"
)
type Foo struct {
A int `tag1:"First Tag" tag2:"Second Tag"`
B string
}
func main() {
sl := []int{1, 2, 3}
greeting := "hello"
greetingPtr := &greeting
f := Foo{A: 10, B: "Salutations"}
fp := &f
slType := reflect.TypeOf(sl)
gType := reflect.TypeOf(greeting)
grpType := reflect.TypeOf(greetingPtr)
fType := reflect.TypeOf(f)
fpType := reflect.TypeOf(fp)
examiner(slType, 0)
examiner(gType, 0)
examiner(grpType, 0)
examiner(fType, 0)
examiner(fpType, 0)
}
func examiner(t reflect.Type, depth int) {
fmt.Println(strings.Repeat("\t", depth), "Type is", t.Name(), "and kind is", t.Kind())
switch t.Kind() {
case reflect.Array, reflect.Chan, reflect.Map, reflect.Ptr, reflect.Slice:
fmt.Println(strings.Repeat("\t", depth+1), "Contained type:")
examiner(t.Elem(), depth+1)
case reflect.Struct:
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
fmt.Println(strings.Repeat("\t", depth+1), "Field", i+1, "name is", f.Name, "type is", f.Type.Name(), "and kind is", f.Type.Kind())
if f.Tag != "" {
fmt.Println(strings.Repeat("\t", depth+2), "Tag is", f.Tag)
fmt.Println(strings.Repeat("\t", depth+2), "tag1 is", f.Tag.Get("tag1"), "tag2 is", f.Tag.Get("tag2"))
}
}
}
}
PS D:\Workspace\Go\src\projects\demo> go run main.go
Type is and kind is slice
Contained type:
Type is int and kind is int
Type is string and kind is string
Type is and kind is ptr
Contained type:
Type is string and kind is string
Type is Foo and kind is struct
Field 1 name is A type is int and kind is int
Tag is tag1:"First Tag" tag2:"Second Tag"
tag1 is First Tag tag2 is Second Tag
Field 2 name is B type is string and kind is string
Type is and kind is ptr
Contained type:
Type is Foo and kind is struct
Field 1 name is A type is int and kind is int
Tag is tag1:"First Tag" tag2:"Second Tag"
tag1 is First Tag tag2 is Second Tag
Field 2 name is B type is string and kind is string
Go
// 使用反射创建新实例
/*
除了检查变量的类型外,还可以使用反射来读取,设置或创建值。
首先,需要使用refVal := reflect.ValueOf(var)为变量创建一个reflect.Value实例。
如果希望能够使用反射来修改值,则必须使用refPtrVal := reflect.ValueOf(&var);
获得指向变量的指针。如果不这样做,则可以使用反射来读取该值,但不能对其进行修改。
一旦有了reflect.Value实例就可以使用Type()方法获取变量的reflect.Type。
如果要修改值,请记住它必须是一个指针,并且必须首先对其进行解引用。
使用refPtrVal.Elem().Set(newRefVal)来修改值,并且传递给Set()的值也必须是reflect.Value。
*/
package main
import (
"fmt"
"reflect"
)
type Foo struct {
A int
B string
}
// 使用反射创建新实例
func main() {
greeting := "hello"
f := Foo{A: 10, B: "Salutations"}
gVal := reflect.ValueOf(greeting)
// not a pointer so all we can do is read it
fmt.Println(gVal.Interface()) // hello
gpVal := reflect.ValueOf(&greeting)
// it's a pointer, so we can change it, and it changes the underlying variable
gpVal.Elem().SetString("goodbye")
fmt.Println(greeting) // 修改成了goodbye
fType := reflect.TypeOf(f)
fVal := reflect.New(fType)
fVal.Elem().Field(0).SetInt(20)
fVal.Elem().Field(1).SetString("Greetings")
f2 := fVal.Elem().Interface().(Foo) // 调用Interface()方法从reflect.Value回到普通变量值
fmt.Printf("f2: %+v, %d, %s\n", f2, f2.A, f2.B)
fmt.Println("f2:", f2)
fmt.Println("f:", f)
}
PS D:\Workspace\Go\src\projects\demo> go run main.go
hello
goodbye
f2: {A:20 B:Greetings}, 20, Greetings
f2: {20 Greetings}
f: {10 Salutations}
Go
// 反射创建引用类型的实例
/*
使用反射来生成通常需要make函数的实例。可以使用reflect.MakeSlice,
reflect.MakeMap和reflect.MakeChan函数制作切片,Map或通道。
在所有情况下,都提供一个reflect.Type,然后获取一个reflect.Value,
可以使用反射对其进行操作,或者可以将其分配回一个标准变量。
*/
package main
import (
"fmt"
"reflect"
)
func main() {
// 定义变量
intSlice := make([]int, 0)
mapStringInt := make(map[string]int)
// 获取变量的 reflect.Type
sliceType := reflect.TypeOf(intSlice)
mapType := reflect.TypeOf(mapStringInt)
// 使用反射创建类型的新实例
intSliceReflect := reflect.MakeSlice(sliceType, 0, 0)
mapReflect := reflect.MakeMap(mapType)
// 将创建的新实例分配回一个标准变量
v := 10
rv := reflect.ValueOf(v)
intSliceReflect = reflect.Append(intSliceReflect, rv)
intSlice2 := intSliceReflect.Interface().([]int)
fmt.Println("intSlice2: ", intSlice2)
fmt.Println("intSlice : ", intSlice)
k := "hello"
rk := reflect.ValueOf(k)
mapReflect.SetMapIndex(rk, rv)
mapStringInt2 := mapReflect.Interface().(map[string]int)
fmt.Println("mapStringInt2: ", mapStringInt2)
fmt.Println("mapStringInt : ", mapStringInt)
}
PS D:\Workspace\Go\src\projects\demo> go run main.go
intSlice2: [10]
intSlice : []
mapStringInt2: map[hello:10]
mapStringInt : map[
Go
/*
使用反射创建函数
反射不仅仅可以为存储数据创造新的地方。还可以使用reflect.MakeFunc函数使用reflect来创建新函数。
该函数期望我们要创建的函数的reflect.Type,以及一个闭包,其输入参数为[]reflect.Value类型,
其返回类型也为[] reflect.Value类型。下面是一个简单的示例,它为传递给它的任何函数创建一个定时包装器
*/
package main
import (
"fmt"
"reflect"
"runtime"
"time"
)
func MakeTimedFunction(f interface{}) interface{} {
rf := reflect.TypeOf(f)
fmt.Println("rf: ", rf)
if rf.Kind() != reflect.Func {
panic("expects a function")
}
vf := reflect.ValueOf(f)
fmt.Println("vf: ", vf)
wrapperF := reflect.MakeFunc(rf, func(in []reflect.Value) []reflect.Value {
start := time.Now()
out := vf.Call(in)
end := time.Now()
fmt.Printf("calling %s took %v\n", runtime.FuncForPC(vf.Pointer()).Name(), end.Sub(start))
return out
})
return wrapperF.Interface()
}
func timeMe() {
fmt.Println("starting")
time.Sleep(1 * time.Second)
fmt.Println("ending")
}
func timeMeToo(a int) int {
fmt.Println("starting")
time.Sleep(time.Duration(a) * time.Second)
result := a * 2
fmt.Println("ending")
return result
}
func main() {
fmt.Println("MakeTimedFunction1: ")
timed := MakeTimedFunction(timeMe).(func())
fmt.Println("转成普通函数1: ")
timed()
fmt.Println("\n\nMakeTimedFunction2: ")
timedToo := MakeTimedFunction(timeMeToo).(func(int) int)
fmt.Println("转成普通函数2: ")
fmt.Println(timedToo(5))
}
PS D:\Workspace\Go\src\projects\demo> go run main.go
MakeTimedFunction1:
rf: func()
vf: 0x733a80
转成普通函数1:
starting
ending
calling main.timeMe took 1.0150935s
MakeTimedFunction2:
rf: func(int) int
vf: 0x733b40
转成普通函数2:
starting
ending
calling main.timeMeToo took 5.0133999s
10
3 reflect反射- Type和Value

TypeOf() 方法返回变量的类型信息得到的是一个类型为 reflect.Type 的变量,
ValueOf() 方法返回变量的值信息得到的是一个类型为 reflect.Value 的变量。
Go
package main
import (
"fmt"
"reflect"
)
type Skills interface {
reading()
running()
}
type Student struct {
Age int
Name string
}
func (self Student) runing() {
fmt.Printf("%s is running\n", self.Name)
}
func (self Student) reading() {
fmt.Printf("%s is reading\n", self.Name)
}
func main() {
stu1 := Student{Name: "dar", Age: 34}
inf := new(Skills)
stu_type := reflect.TypeOf(stu1)
inf_type := reflect.TypeOf(inf).Elem() // 获取指针所指的对象类型
fmt.Println("类型stu_type:", stu_type)
fmt.Println(stu_type.String()) //main.Student
fmt.Println(stu_type.Name()) //Student
fmt.Println(stu_type.PkgPath()) //main
fmt.Println(stu_type.Kind()) //struct
fmt.Println(stu_type.Size()) //24
fmt.Println("\n类型inf_type:", inf_type)
fmt.Println(inf_type.NumMethod()) //2
fmt.Println(inf_type.Method(0), inf_type.Method(0).Name) // {reading main func() <invalid Value> 0} reading
fmt.Println(inf_type.MethodByName("reading")) //{reading main func() <invalid Value> 0} true
}
PS D:\Workspace\Go\src\projects\demo> go run main.go
类型stu_type: main.Student
main.Student
Student
main
struct
24
类型inf_type: main.Skills
2
{reading main func() <invalid Value> 0} reading
{reading main func() <invalid Value> 0} true
Go
package main
import (
"fmt"
"reflect"
)
type Skills interface {
reading()
running()
}
type Student struct {
Name string "json:name"
Age int "json:age"
}
func (self Student) runing() {
fmt.Printf("%s is running\n", self.Name)
}
func (self Student) reading() {
fmt.Printf("%s is reading\n", self.Name)
}
func main() {
stu1 := Student{Name: "dar", Age: 34}
stu_type := reflect.TypeOf(stu1)
fmt.Println(stu_type.NumField()) //2
fmt.Println(stu_type.Field(0)) //{Name string 0 [0] false}
fmt.Println(stu_type.FieldByName("Name")) //{{Age int 16 [1] false} true
fmt.Println(stu_type.Field(1)) //{Name string 0 [0] false}
fmt.Println(stu_type.FieldByName("Age")) //{{Age int 16 [1] false} true
}
Go
package main
import (
"fmt"
"reflect"
)
func main() {
str := "dar"
val := reflect.ValueOf(str).Kind()
fmt.Println(val) //string
}
3 reflect反射-利弊
反射的好处
-
为了降低多写代码造成的bug率,做更好的归约和抽象
-
为了灵活、好用、方便,做动态解析、调用和处理
-
为了代码好看、易读、提高开发效率,补足与动态语言之间的一些差别
反射的弊端
-
与反射相关的代码,经常是难以阅读的。在软件工程中,代码可读性也是一个非常重要的指标。
-
Go 语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于反射代码是无能为力的。所以包含反射相关的代码,很可能会运行很久,才会出错,这时候经常是直接 panic,可能会造成严重的后果。
-
反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。所以,对于一个项目中处于运行效率关键位置的代码,尽量避免使用反射特性。
3.1 reflect反射-Type
Type:Type类型用来表示一个go类型。
不是所有go类型的Type值都能使用所有方法。请参见每个方法的文档获取使用限制。在调用有分
类限定的方法时,应先使用Kind方法获知类型的分类。调用该分类不支持的方法会导致运行时的
panic。
获取Type对象的方法:
func TypeOf(i interface{}) Type
Go
str := "dar"
res_type := reflect.TypeOf(str)
fmt.Println(res_type) //string
int1 := 1
res_type2 := reflect.TypeOf(int1)
fmt.Println(res_type2) //int
3.1 reflect反射-Type续-reflect.Type通用方法
Go
func (t *rtype) String() string // 获取 t 类型的字符串描述,不要通过 String 来判断两种类型是否一致。
func (t *rtype) Name() string // 获取 t 类型在其包中定义的名称,未命名类型则返回空字符串。
func (t *rtype) PkgPath() string // 获取 t 类型所在包的名称,未命名类型则返回空字符串。
func (t *rtype) Kind() reflect.Kind // 获取 t 类型的类别。
func (t *rtype) Size() uintptr // 获取 t 类型的值在分配内存时的大小,功能和 unsafe.SizeOf 一样。
func (t *rtype) Align() int // 获取 t 类型的值在分配内存时的字节对齐值。
func (t *rtype) FieldAlign() int // 获取 t 类型的值作为结构体字段时的字节对齐值。
func (t *rtype) NumMethod() int // 获取 t 类型的方法数量。
func (t *rtype) Method() reflect.Method // 根据索引获取 t 类型的方法,如果方法不存在,则 panic。
// 如果 t 是一个实际的类型,则返回值的 Type 和 Func 字段会列出接收者。
// 如果 t 只是一个接口,则返回值的 Type 不列出接收者,Func 为空值。
func (t *rtype) MethodByName(string) (reflect.Method, bool) // 根据名称获取 t 类型的方法。
func (t *rtype) Implements(u reflect.Type) bool // 判断 t 类型是否实现了 u 接口。
func (t *rtype) ConvertibleTo(u reflect.Type) bool // 判断 t 类型的值可否转换为 u 类型。
func (t *rtype) AssignableTo(u reflect.Type) bool // 判断 t 类型的值可否赋值给 u 类型。
func (t *rtype) Comparable() bool // 判断 t 类型的值可否进行比较操作
//注意对于:数组、切片、映射、通道、指针、接口
func (t *rtype) Elem() reflect.Type // 获取元素类型、获取指针所指对象类型,获取接口的动态类型
3.1 reflect反射-Type续-reflect.Type其他方法
// 数值
func (t *rtype) Bits() int // 获取数值类型的位宽,t 必须是整型、浮点型、复数型
// 数组
func (t *rtype) Len() int // 获取数组的元素个数
// 映射
func (t *rtype) Key() reflect.Type // 获取映射的键类型
// 通道
func (t *rtype) ChanDir() reflect.ChanDir // 获取通道的方向
// 结构体
func (t *rtype) NumField() int // 获取字段数量
func (t *rtype) Field(int) reflect.StructField // 根据索引获取字段
func (t *rtype) FieldByName(string) (reflect.StructField, bool) // 根据名称获取字段
func (t *rtype) FieldByNameFunc(match func(string) bool) (reflect.StructField, bool) // 根据指定的匹配函数 math 获取字段
func (t *rtype) FieldByIndex(index []int) reflect.StructField // 根据索引链获取嵌套字段
// 函数
func (t *rtype) NumIn() int // 获取函数的参数数量
func (t *rtype) In(int) reflect.Type // 根据索引获取函数的参数信息
func (t *rtype) NumOut() int // 获取函数的返回值数量
func (t *rtype) Out(int) reflect.Type // 根据索引获取函数的返回值信息
func (t *rtype) IsVariadic() bool // 判断函数是否具有可变参数
3.1 reflect反射-Type结构
,rtype 实现了 Type 接口的所有方法。剩下的不同的部分信
息各种特殊类型结构体都不一样。可以将 rtype 理解成父类,
特殊类型的结构体是子类,会有一些不一样的字段信息。
Go
// 基础类型 rtype 实现了 Type 接口
type rtype struct {
size uintptr // 占用字节数
ptrdata uintptr
hash uint32 // 类型的hash值
...
kind uint8 // 元类型
...
}
// 切片类型
type sliceType struct {
rtype
elem *rtype // 元素类型
}
// 结构体类型
type structType struct {
rtype
pkgPath name // 所在包名
fields []structField // 字段列表
}
Go
// 获取和设置普通类型的值
package main
import (
"fmt"
"reflect"
)
func main() {
str := "dar"
age := 11
fmt.Println(reflect.ValueOf(str).String()) //获取str的值,结果dar
fmt.Println(reflect.ValueOf(age).Int()) //获取age的值,结果age
str2 := reflect.ValueOf(&str) //获取Value类型
str2.Elem().SetString("ki") //设置值
fmt.Println(str2.Elem(), age) //ki 11
age2 := reflect.ValueOf(&age) //获取Value类型
fmt.Println("age2:", age2)
age2.Elem().SetInt(40) //设置值
fmt.Println("age:", age)
fmt.Println("reflect.ValueOf(age):", reflect.ValueOf(age))
}
3.1 reflect反射- reflect.Value方法
Go
reflect.Value.Kind():获取变量类别,返回常量
const (
Invalid Kind = iota //不存在的类型
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr // 指针的整数类型
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)
str := "dar"
val := reflect.ValueOf(str).Kind()
fmt.Println(val) //string
3.2 reflect反射- reflect.Value方法
获取值方法:
func (v Value) Int() int64 // 获取int类型值,如果 v 值不是有符号整型,则 panic。
func (v Value) Uint() uint64 // 获取unit类型的值,如果 v 值不是无符号整型(包括 uintptr),则 panic。
func (v Value) Float() float64 // 获取float类型的值,如果 v 值不是浮点型,则 panic。
func (v Value) Complex() complex128 // 获取复数类型的值,如果 v 值不是复数型,则 panic。
func (v Value) Bool() bool // 获取布尔类型的值,如果 v 值不是布尔型,则 panic。
func (v Value) Len() int // 获取 v 值的长度,v 值必须是字符串、数组、切片、映射、通道。
func (v Value) Cap() int // 获取 v 值的容量,v 值必须是数值、切片、通道。
func (v Value) Index(i int) reflect.Value // 获取 v 值的第 i 个元素,v 值必须是字符串、数组、切片,i 不能超出范围。
func (v Value) Bytes() []byte // 获取字节类型的值,如果 v 值不是字节切片,则 panic。
func (v Value) Slice(i, j int) reflect.Value // 获取 v 值的切片,切片长度 = j - i,切片容量 = v.Cap() - i。
// v 必须是字符串、数值、切片,如果是数组则必须可寻址。i 不能超出范围。
func (v Value) Slice3(i, j, k int) reflect.Value // 获取 v 值的切片,切片长度 = j - i,切片容量 = k - i。
// i、j、k 不能超出 v 的容量。i <= j <= k。
// v 必须是字符串、数值、切片,如果是数组则必须可寻址。i 不能超出范围。
func (v Value) MapIndex(key Value) reflect.Value // 根据 key 键获取 v 值的内容,v 值必须是映射。
// 如果指定的元素不存在,或 v 值是未初始化的映射,则返回零值(reflect.ValueOf(nil))
func (v Value) MapKeys() []reflect.Value // 获取 v 值的所有键的无序列表,v 值必须是映射。
// 如果 v 值是未初始化的映射,则返回空列表。
func (v Value) OverflowInt(x int64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是有符号整型。
func (v Value) OverflowUint(x uint64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是无符号整型。
func (v Value) OverflowFloat(x float64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是浮点型。
func (v Value) OverflowComplex(x complex128) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是复数型。
Go
//简单结构体操作
package main
import (
"fmt"
"reflect"
)
type Skills interface {
reading()
running()
}
type Student struct {
Name string
Age int
}
func (self Student) runing() {
fmt.Printf("%s is running\n", self.Name)
}
func (self Student) reading() {
fmt.Printf("%s is reading\n", self.Name)
}
func main() {
stu1 := Student{Name: "dar", Age: 18}
stu_val := reflect.ValueOf(stu1) //获取Value类型
fmt.Println(stu_val.NumField()) //2
fmt.Println(stu_val.Field(0), stu_val.Field(1)) //dar 18
fmt.Println(stu_val.FieldByName("Age")) //18
stu_val2 := reflect.ValueOf(&stu1).Elem() // 要修改传引用或者指针
stu_val2.FieldByName("Age").SetInt(33) //设置字段值 ,结果33
fmt.Println(stu1.Age)
}
3.2 reflect反射- reflect.Value方法
设置值方法:
func (v Value) SetInt(x int64) //设置int类型的值
func (v Value) SetUint(x uint64) // 设置无符号整型的值
func (v Value) SetFloat(x float64) // 设置浮点类型的值
func (v Value) SetComplex(x complex128) //设置复数类型的值
func (v Value) SetBool(x bool) //设置布尔类型的值
func (v Value) SetString(x string) //设置字符串类型的值
func (v Value) SetLen(n int) // 设置切片的长度,n 不能超出范围,不能为负数。
func (v Value) SetCap(n int) //设置切片的容量
func (v Value) SetBytes(x []byte) //设置字节类型的值
func (v Value) SetMapIndex(key, val reflect.Value) //设置map的key和value,前提必须是初始化以后,存在覆盖、不存在
添加
Go
//通过反射调用结构体中的方法,通过reflect.Value.Method(i int).Call()
//或者reflect.Value.MethodByName(name string).Call()实现
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name string
Age int
}
func (this *Student) SetName(name string) {
this.Name = name
fmt.Printf("set name %s\n", this.Name)
}
func (this *Student) SetAge(age int) {
this.Age = age
fmt.Printf("set age %d\n", age)
}
func (this *Student) String() string {
fmt.Printf("this is %s\n", this.Name)
return this.Name
}
func (this *Student) SetAgeAndName(age int, name string) {
this.Age = age
fmt.Printf("set age %d, name:%s\n", age, name)
}
func main() {
stu1 := &Student{Name: "dar", Age: 18}
val := reflect.ValueOf(stu1) //获取Value类型,也可以使用reflect.ValueOf(&stu1).Elem()
val.MethodByName("String").Call(nil) //调用String方法
params := make([]reflect.Value, 1)
params[0] = reflect.ValueOf(18)
val.MethodByName("SetAge").Call(params) //通过名称调用方法
params[0] = reflect.ValueOf("ki")
// val.Method(1).Call(params) //通过方法索引调用
val.Method(2).Call(params) //通过方法索引调用 通过索引的方式拿到函数不安全
fmt.Println(stu1.Name, stu1.Age)
params = make([]reflect.Value, 2)
params[0] = reflect.ValueOf(18)
params[1] = reflect.ValueOf("老师")
val.MethodByName("SetAgeAndName").Call(params)
}
//this is dar
//set age 18
//set name ki
//ki 18
3.2 reflect反射- reflect.Value方法
其他方法:
//结构体相关:
func (v Value) NumField() int // 获取结构体字段(成员)数量
func (v Value) Field(i int) reflect.Value //根据索引获取结构体字段
func (v Value) FieldByIndex(index []int) reflect.Value // 根据索引链获取结构体嵌套字段
func (v Value) FieldByName(string) reflect.Value // 根据名称获取结构体的字段,不存在返回reflect.ValueOf(nil)
func (v Value) FieldByNameFunc(match func(string) bool) Value // 根据匹配函数 match 获取字段,如果没有匹配的字段,则返回
零值(reflect.ValueOf(nil))
//通道相关:
func (v Value) Send(x reflect.Value)// 发送数据(会阻塞),v 值必须是可写通道。
func (v Value) Recv() (x reflect.Value, ok bool) // 接收数据(会阻塞),v 值必须是可读通道。
func (v Value) TrySend(x reflect.Value) bool // 尝试发送数据(不会阻塞),v 值必须是可写通道。
func (v Value) TryRecv() (x reflect.Value, ok bool) // 尝试接收数据(不会阻塞),v 值必须是可读通道。
func (v Value) Close() // 关闭通道
//函数相关
func (v Value) Call(in []Value) (r []Value) // 通过参数列表 in 调用 v 值所代表的函数(或方法)。函数的返回值存入 r 中返回。
// 要传入多少参数就在 in 中存入多少元素。
// Call 即可以调用定参函数(参数数量固定),也可以调用变参函数(参数数量可变)。
func (v Value) CallSlice(in []Value) []Value // 调用变参函数
3.2 reflect反射- reflect.Value 结构体
type Value struct {
typ *rtype // 变量的类型结构体
ptr unsafe.Pointer // 数据指针
flag uintptr // 标志位
}
3.3 Go 语言官方的反射三大定律1
官方对 Go 语言的反射功能做了一个抽象的描述,总结出了三大定律
-
Reflection goes from interface value to reflection object.
-
Reflection goes from reflection object to interface value.
-
To modify a reflection object, the value must be settable.
第一个定律的意思是反射将接口变量转换成反射对象 Type 和 Value
func TypeOf(v interface{}) Type
func ValueOf(v interface{}) Value
第二个定律的意思是反射可以通过反射对象 Value 还原成原先的接口变量,这个指的就是 Value
结构体提供的 Interface() 方法。
func (v Value) Interface() interface{}
第三个定律的功能不是很好理解,它的意思是想用反射功能来修改一个变量的值,前提是这个
值可以被修改。
Go
package main
import (
"fmt"
"reflect"
)
func test1() {
var s int = 42
var v = reflect.ValueOf(s)
v.SetInt(43)
fmt.Println(s)
}
func test2() {
var s int = 42
// 反射指针类型
var v = reflect.ValueOf(&s)
// 要拿出指针指向的元素进行修改
v.Elem().SetInt(43)
fmt.Println(s)
}
func main() {
test1()
}
3.3 Go 语言官方的反射三大定律2
值类型的变量是不可以通过反射来修改,因为在反射之前,传参的时候需要将值变量转
换成接口变量,值内容会被浅拷贝,反射对象 Value 指向的数据内存地址不是原变量的内
存地址,而是拷贝后的内存地址。这意味着如果值类型变量可以通过反射功能来修改,
那么修改操作根本不会影响到原变量的值,那就白白修改了。所以 reflect 包就直接禁止
了通过反射来修改值类型的变量。

Go
package main
import (
"fmt"
"reflect"
)
type Rect struct {
Width int
Height int
Name string
}
// 通过统一的接口去实现属性设置的
func SetRectAttr(r *Rect, name string, value int) {
var v = reflect.ValueOf(r)
var field = v.Elem().FieldByName(name)
field.SetInt(int64(value))
}
// 结构体也是值类型,也必须通过指针类型来修改。
func main() {
var r = Rect{50, 100}
SetRectAttr(&r, "Width", 100) // 修改属性的接口
SetRectAttr(&r, "Height", 200)
SetRectAttr(&r, "Name", "正方形")
fmt.Println(r)
}
Go
package main
import (
"fmt"
"reflect"
"time"
)
const N = 1000000
func test1() {
var sum = 0
t0 := time.Now()
for i := 0; i < (N); i++ {
var s int = 42
// 反射指针类型
var v = reflect.ValueOf(&s)
// 要拿出指针指向的元素进行修改
v.Elem().SetInt(43)
sum += s
}
elapsed := time.Since(t0)
fmt.Println("反射赋值 ", "N:", N, "time:", elapsed, ", sum:", sum)
}
func test2() {
var sum = 0
t0 := time.Now()
for i := 0; i < (N); i++ {
var s int = 42
s = 43
sum += s
}
elapsed := time.Since(t0)
fmt.Println("直接赋值 ", "N:", N, "time:", elapsed, ", sum:", sum)
}
func main() {
test1()
test2()
}
Go
// 3 reflect反射 基础
package main
import (
"fmt"
)
func main() {
newValue := make(map[interface{}]interface{}, 0)
newValue[11] = "dar"
newValue["age"] = 18
fmt.Println(newValue)
}
4 Go map实战
◼ go中的map是hash表的一个引用,类型写为:map[key]value,其中的key, value分别对
应一种数据类型,如map[string]string
◼ 要求所有的key的数据类型相同,所有value数据类型相同(注:key与value可以有不同的
数据类型,如果想不同则使用interface作为value)
map中的key的数据类型
◼ map中的每个key在keys的集合中是唯一的,而且需要支持 == or != 操作
◼ key的常用类型:int, rune, string, 结构体(每个元素需要支持 == or != 操作), 指针, 基于这
些类型自定义的类型
float32/64 类型从语法上可以作为key类型,但是实际一般不作为key,因为其类型有误差
4.1 Go map实战-key的几种数据类型举例
Go
package main
import "fmt"
func main() {
// m0 可以, key类型为string, 支持 == 比较操作
{
fmt.Println("---- m0 ----")
var m0 map[string]string // 定义map类型变量m0,key的类型为string,value的类型string
fmt.Println(m0)
}
// m1 不可以, []byte是slice,不支持 == != 操作,不可以作为map key的数据类型
{
// fmt.Println("---- m1 ----");
//var m1 map[[]byte]string // 报错: invalid map key type []byte
//fmt.Println(m1)
// 准确说slice类型只能与nil比较,其他的都不可以,可以通过如下测试:
// var b1,b2 []byte
// fmt.Println(b1==b2) // 报错: invalid operation: b1 == b2 (slice can only be compared to nil)
}
// m2 可以, interface{}类型可以作为key,但是需要加入的key的类型是可以比较的
{
fmt.Println("---- m2 ----")
var m2 map[interface{}]string
m2 = make(map[interface{}]string)
//m2[[]byte("k2")]="v2" // panic: runtime error: hash of unhashable type []uint8
m2[123] = "123"
m2[12.3] = "123"
fmt.Println(m2)
var str string = m2[12.3]
fmt.Println(str)
}
// m3 可以, 数组支持比较
{
fmt.Println("---- m3 ----")
a3 := [3]int{1, 2, 3}
var m3 map[[3]int]string
m3 = make(map[[3]int]string)
m3[a3] = "m3"
fmt.Println(m3)
}
// m4 可以,book1里面的元素都是支持== !=
{
fmt.Println("---- m4 ----")
type book1 struct {
name string
}
var m4 map[book1]string
fmt.Println(m4)
}
// m5 不可以, text元素类型为[]byte, 不满足key的要求
{
fmt.Println("---- m5 ----")
// type book2 struct {
// name string
// text []byte //没有这个就可以
// }
//var m5 map[book2]string //invalid map key type book2
//fmt.Println(m5)
}
}
4.2 map基本操作
map创建
两种创建的方式:一是通过字面值;二是通过make函数
map增删改查
map遍历
•遍历的顺序是随机的
•使用for range遍历的时候,k,v使用的同一块内存,这也是容易出现错误的地
方
增加,修改: m["c"] = "11"
查: v1 := m["x"]
v2, ok2 := m["x"]
删: delete(m, "x")
for k, v := range m { fmt.Printf("k:[%v].v:[%v]\n", k, v) //
输出k,v值 }
5 Go string字符串
字符串通常有两种设计,一种是「字符」串,一种是「字节」串。「字符」串中的每个
字都是定长的,而「字节」串中每个字是不定长的。Go 语言里的字符串是「字节」串,
英文字符占用 1 个字节,非英文字符占多个字节。
其中 codepoint 是每个「字」的其实偏移量。 Go 语言的字符串采用 utf8 编码,中文汉字通常需要
占用 3 个字节,英文只需要 1 个字节。 len() 函数得到的是字节的数量,通过下标来访问字符串得
到的是「字节」。
Go
// 4-2 map基本操作
package main
import "fmt"
func create() {
fmt.Println("map创建方式:")
// 1 字面值
{
m1 := map[string]string{
"m1": "v1", // 定义时指定的初始key/value, 后面可以继续添加
}
_ = m1
}
// 2 使用make函数
{
m2 := make(map[string]string) // 创建时,里面不含元素,元素都需要后续添加
m2["m2"] = "v2" // 添加元素
_ = m2
}
// 定义一个空的map
{
m3 := map[string]string{}
m4 := make(map[string]string)
_ = m3
_ = m4
}
}
func curd() {
fmt.Println("map增删改查:")
// 创建
fmt.Println("建:")
m := map[string]string{
"a": "va",
"b": "vb",
}
fmt.Println(len(m)) // len(m) 获得m中key/value对的个数
// 增加,修改
fmt.Println("增改:")
{
// k不存在为增加,k存在为修改
m["c"] = ""
m["c"] = "11" // 重复增加(key相同),使用新的值覆盖
fmt.Printf("%#v %#v\n", m, len(m)) // map[string]string{"a":"va", "b":"vb", "c":"11"} 3
}
// 查
fmt.Println("查:")
{
// v := m[k] // 从m中取键k对应的值给v,如果k在m中不存在,则将value类型的零值赋值给v
// v, ok := m[k] // 从m中取键k对应的值给v,如果k存在,ok=true,如果k不存在,将value类型的零值赋值给v同时ok=false
// 查1 - 元素不存在
v1 := m["x"] //
v2, ok2 := m["x"]
fmt.Printf("%#v %#v %#v\n", v1, v2, ok2) // "" "" false
// 查2 - 元素存在
v3 := m["a"]
v4, ok4 := m["a"]
fmt.Printf("%#v %#v %#v\n", v3, v4, ok4) //"va" "va" true
}
fmt.Println("删:")
// 删, 使用内置函数删除k/v对
{
// delete(m, k) 将k以及k对应的v从m中删掉;如果k不在m中,不执行任何操作
delete(m, "x") // 删除不存在的key,原m不影响
delete(m, "a") // 删除存在的key
fmt.Printf("%#v %#v\n", m, len(m)) // map[string]string{"b":"vb", "c":"11"} 2
delete(m, "a") // 重复删除不报错,m无影响
fmt.Printf("%#v %#v\n", m, len(m)) /// map[string]string{"b":"vb", "c":"11"} 2
}
}
func travel() {
fmt.Println("map遍历:")
m := map[string]int{
"a": 1,
"b": 2,
}
for k, v := range m {
fmt.Printf("k:[%v].v:[%v]\n", k, v) // 输出k,v值
}
var fruits = map[string]int{
"apple": 2,
"banana": 5,
"orange": 8,
}
for name, score := range fruits {
fmt.Println(name, score)
}
for name := range fruits {
fmt.Println(name)
}
}
func main() {
create()
curd()
travel()
}
5.1 Go string字符串-遍历

package main
import "fmt"
func main() {
var s = "嘻哈china"
for i := 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
}
e5 98 bb e5 93 88 63 68 69 6e 61
func main() {
var s = "嘻哈china"
for codepoint, runeValue := range s {
fmt.Printf("[%d]: %x", codepoint, int32(runeValue))
}
}
PS D:\Workspace\Go\src\projects\demo> go run main.go
0\]: 563b\[3\]: 54c8\[6\]: 63\[7\]: 68\[8\]: 69\[9\]: 6e\[10\]: 61
5-2 Go string字节串的内存表示和操作

```Go
// 按字符 rune 遍历
package main
import "fmt"
func splice() {
var s1 = "hello" // 静态字面量
var s2 = ""
for i := 0; i < 10; i++ {
s2 += s1 // 动态构造
}
fmt.Println(len(s1))
fmt.Println(len(s2))
}
// 字符串是只读的
func onlyread() {
var s = "hello"
s[0] = 'H'
}
// 切割切割
func cut() {
var s1 = "hello world"
var s2 = s1[3:8]
fmt.Println(s2)
}
// 字节切片和字符串的相互转换
func string2bytes() {
var s1 = "hello world"
var b = []byte(s1) // 字符串转字节切片
var s2 = string(b) // 字节切片转字符串
fmt.Println(b)
fmt.Println(s2)
}
func main() {
splice()
onlyread()
cut()
string2bytes()
}
```
# 1.3 Go语言并发编程
## 1. Goroutine
1 Go协程 Goroutine
1.1 Goroutine 使用
1.2 Goroutine 原理
1.1 如何使用Goroutine
在函数或方法调用前面加上关键字go,您将会同时运行一个新的Goroutine

1.2 子协程异常退出的影响
在使用子协程时一定要特别注意保护好每个子协程,确保它们正常安全的运行。因为子协程
的异常退出会将异常传播到主协程,直接会导致主协程也跟着挂掉,然后整个程序就崩溃了。

1.3 协程异常处理-recover
recover 是一个Go语言的内建函数,可以让进入宕机流程中的 goroutine 恢复过来,recover 仅
在延迟函数 defer 中有效。
如果当前的 goroutine 陷入恐慌,调用 recover 可以捕获到 panic 的输入值,并且恢复正常的
执行。

```Go
// 1.3 协程异常处理-recover
package main
import (
"fmt"
"runtime"
)
// 崩溃时需要传递的上下文信息
type panicContext struct {
function string // 所在函数
}
// 保护方式允许一个函数
func ProtectRun(entry func()) {
// 延迟处理的函数
defer func() {
// 发生宕机时,获取panic传递的上下文并打印
err := recover()
switch err.(type) {
case runtime.Error: // 运行时错误
fmt.Println("runtime error:", err)
default: // 非运行时错误
fmt.Println("error:", err)
}
}()
entry()
}
func main() {
fmt.Println("运行前")
// 允许一段手动触发的错误
ProtectRun(func() {
fmt.Println("手动宕机前")
// 使用panic传递上下文
panic(&panicContext{
"手动触发panic",
})
fmt.Println("手动宕机后")
})
// 故意造成空指针访问错误
ProtectRun(func() {
fmt.Println("赋值宕机前")
var a *int
*a = 1
fmt.Println("赋值宕机后")
})
fmt.Println("运行后")
}
```
```Go
// 1.3 协程异常处理-recover
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("run in main goroutine")
go func() {
fmt.Println("run in child goroutine")
defer func() { // 要在对应的协程里执行
fmt.Println("执行defer:")
if err := recover(); err != nil {
fmt.Println("捕获error:", err)
}
}()
fmt.Println("run in grand grand child goroutine")
var ptr *int
*ptr = 0x12345 // 故意制造崩溃 ,该协程运行到这里结束
go func() {
fmt.Println("子子run in grand child goroutine") // 这里也不会运行
go func() {
}()
}()
// time.Sleep(time.Second * 1)
fmt.Println("离开: run in child goroutine leave") // 这里能否执行到
}()
time.Sleep(2 * time.Second)
fmt.Println("main goroutine will quit")
}
```
### 1-4 启动百万协程
Go 语言能同时管理上百万的协程
```Go
// 1-4 启动百万协程
package main
import (
"fmt"
"runtime"
"time"
)
const N = 1000000
func main() {
fmt.Println("run in main goroutine")
i := 1
for {
go func() {
for {
time.Sleep(time.Second)
}
}()
if i%10000 == 0 {
fmt.Printf("%d goroutine started\n", i)
}
i++
if i == N {
break
}
}
fmt.Println("NumGoroutine:", runtime.NumGoroutine())
time.Sleep(time.Second * 15)
}
```
### 1-5 死循环
如果有个别协程死循环了会导致其它协程饥饿得到不运行么?
```Go
package main
import (
"fmt"
"runtime"
"syscall"
"time"
)
// 获取的是线程ID,不是协程ID
func GetCurrentThreadId() int {
var user32 *syscall.DLL
var GetCurrentThreadId *syscall.Proc
var err error
user32, err = syscall.LoadDLL("Kernel32.dll") // Windows用的
if err != nil {
fmt.Printf("syscall.LoadDLL fail: %v\n", err.Error())
return 0
}
GetCurrentThreadId, err = user32.FindProc("GetCurrentThreadId")
if err != nil {
fmt.Printf("user32.FindProc fail: %v\n", err.Error())
return 0
}
var pid uintptr
pid, _, err = GetCurrentThreadId.Call()
return int(pid)
}
func main() {
// runtime.GOMAXPROCS(1)
// 读取当前的线程数
fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0)) // 本身电脑物理核心是4核 支持超线程 8核
fmt.Println("run in main goroutine")
n := 5
for i := 0; i < n; i++ {
go func() {
fmt.Println("dead loop goroutine start, threadId:", GetCurrentThreadId())
for {
} // 死循环
fmt.Println("dead loop goroutine stop")
}()
}
go func() {
var count = 0
for {
time.Sleep(time.Second)
count++
fmt.Println("for goroutine running:", count, "threadId:", GetCurrentThreadId())
}
}()
fmt.Println("NumGoroutine: ", runtime.NumGoroutine())
var count = 0
for {
time.Sleep(time.Second)
count++
fmt.Println("main goroutine running:", count, "threadId:", GetCurrentThreadId())
}
}
```
### 1.6 设置线程数
Go的调度器使用了一个叫做GOMAXPROCS的变量来决定会有多少个操作系统的线程同时执
行Go的代码。其默认的值是运行机器上的CPU的核心数,所以在一个有8个核心的机器上
时,调度器一次会在8个OS线程上去调度GO代码。
```Go
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Println("runtime.NumCPU():", runtime.NumCPU())
// 读取默认的线程数
fmt.Println(runtime.GOMAXPROCS(0))
// 设置线程数为 10
runtime.GOMAXPROCS(10)
// 读取当前的线程数
fmt.Println(runtime.GOMAXPROCS(0))
}
```
### 1-7 G-P-M模型-为什么引入协程?
核心原因为goroutine的轻量级,无论是从进程到线程,还是从线程到协程,其核心都是为了使得
我们的调度单元更加轻量级。可以轻易得创建几万几十万的goroutine而不用担心内存耗尽等问题。

### 1-7 G-P-M模型-系统调用
调用system call陷入内核没有返回之前,为保证调度的并发性,golang 调度器在进入系统调用之前
从线程池拿一个线程或者新建一个线程,当前P交给新的线程M1执行。
G0 返回之后,需要找一个可用的 P 继续运行,
如果没有则将其放在全局队列等待调度。 M0
待 G0 返回后退出或放回线程池。

### 1-7 G-P-M模型-工作流窃取
在P队列上的goroutine全部调度完了之后,对应的M首先会尝试从global runqueue中获取
goroutine进行调度。如果golbal runqueue中没有goroutine,当前M会从别的M对应P的local
runqueue中抢一半的goroutine放入自己的P中进行调度。具体要看C代码去了。

## 2. Channel
2 通道channel
如果说goroutine是Go语音程序的并发体的话,那么channels它们之间的通信机制。
作为协程的输出,通道是一个容器,它可以容纳数据。
作为协程的输入,通道是一个生产者,它可以向协程提供数据。
通道作为容器是有限定大小的,满了就写不进去,空了就读不出来。
通道有它自己的类型,它可以限定进入通道的数据的类型。

### 2.1 创建通道
创建通道只有一种语法,使用make 函数
有两种通道类型:
「缓冲型通道」 var bufferedChannel = make(chan int(这里是类型,什么类型都
行), 1024)
「非缓冲型通道」 var unbufferedChannel = make(chan int)
两个相同类型的channel可以使用==运算符比较。如果两个channel引用的是相通的对象,那
么比较的结果为真。一个channel也可以和nil进行比较。

```Go
package main
import "fmt"
func send(ch chan int) {
i := 0
for {
i++
ch <- i
}
}
func recv(ch chan int) {
value := <-ch
fmt.Println(value)
value = <-ch
fmt.Println(value)
close(ch)
}
// 向一个已经关闭的通道执行写操作会抛出异常,这意味着我们在写通道时一定要确保通道没有被关闭。
func main() {
var ch = make(chan int, 4)
go recv(ch)
send(ch)
}
```
### 2.2 读写通道
Go 语言为通道的读写设计了特殊的箭头语法糖 \<-,让我们使用通道时非常方便。把箭头写在
通道变量的右边就是写通道,把箭头写在通道的左边就是读通道。一次只能读写一个元素
```Go
// 2.2 读写通道
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan float32, 4)
for i := 0; i < cap(ch); i++ {
ch <- 1.0 // 写通道
}
for len(ch) > 0 {
value := <-ch // 读通道
fmt.Println(value)
}
// ch1 := make(chan int, 1) // 这里是缓存 有一个1元素
ch1 := make(chan int) // 非缓存的,实际是0个,并不是1个
go func() {
time.Sleep(1 * time.Second)
ch1 <- 1
ch1 <- 1 // 这里已经阻塞
fmt.Println("写入ch1") //这里没打印
}()
value1 := <-ch1
value1 = <-ch1
time.Sleep(5 * time.Second)
fmt.Println("退出, value1:", value1)
}
```
通道作为容器,它可以像切片一样,使用 cap() 和 len() 全局函数获得通道的容量和当前内
部的元素个数。
### 2-3 读写阻塞
通道满了,写操作就会阻塞,协程就会进入休眠,直到有其它协程读通道挪出了空间,协程
才会被唤醒。如果有多个协程的写操作都阻塞了,一个读操作只会唤醒一个协程。
```Go
// 2-3 读写阻塞
package main
import (
"fmt"
"math/rand"
"time"
)
func send(ch chan int) {
for {
var value = rand.Intn(100)
ch <- value
fmt.Printf("send %d\n", value) // 这里没有延时
}
}
func recv(ch chan int) {
for {
value := <-ch
fmt.Printf("recv %d\n", value)
time.Sleep(time.Second)
}
}
func main() {
var ch = make(chan int, 1)
// 子协程循环读
go recv(ch)
// 主协程循环写
send(ch)
}
```
### 2.4 关闭通道
Go 语言的通道有点像文件,不但支持读写操作, 还支持关闭。读取一个已经关闭的通道会立
即返回通道类型的「零值」,而写一个已经关闭的通道会抛异常。如果通道里的元素是整型的,
读操作是不能通过返回值来确定通道是否关闭的。
```Go
// 2.4 关闭通道
/*
Go 语言的通道有点像文件,不但支持读写操作, 还支持关闭。
读取一个已经关闭的通道会立即返回通道类型的「零值」,而写一个已经关闭的通道会抛异常。
如果通道里的元素是整型的,读操作是不能通过返回值来确定通道是否关闭的。
*/
package main
import "fmt"
func main() {
var ch = make(chan int, 4)
ch <- 1
ch <- 2
fmt.Println("len(ch):", len(ch), "cap(ch):", cap(ch))
close(ch)
value := <-ch
fmt.Println(value)
value = <-ch
fmt.Println(value)
value = <-ch
fmt.Println(value)
value = <-ch
fmt.Println(value)
ch <- 3
}
```
### 2-5 通道写安全
向一个已经关闭的通道执行写操作会抛出异常,这意味着我们在写通道时一定要确保通道没
有被关闭
多人写入怎么办?
```Go
// 2-5 通道写安全
package main
import "fmt"
func send(ch chan int) { // 在写入端关闭, 没有太多的通用性
ch <- 1
ch <- 2
ch <- 3
ch <- 4
close(ch)
}
func recv(ch chan int) {
for v := range ch {
fmt.Println(v)
}
value := <-ch // 判别不了是否已经读取完毕
fmt.Println("value:", value)
}
// 确保通道写安全的最好方式是由负责写通道的协程自己来关闭通道,读通道的协程不要去关闭通道。
func main() {
var ch = make(chan int, 1)
go send(ch)
recv(ch)
}
```
### 2-6 WaitGroup
在写端关闭channel对单写的程序有效,但是多写的时候呢?
使用到内置 sync 包提供的 WaitGroup 对象,它使用计数来等待指定事件完成。
```Go
// 2-6 WaitGroup 在写端关闭channel对单写的程序有效,但是多写的时候呢?
package main
import (
"fmt"
"sync"
"time"
)
func send(ch chan int, wg *sync.WaitGroup) {
defer wg.Done() // 计数值减一
i := 0
for i < 4 {
i++
ch <- i
}
}
func recv(ch chan int) {
for v := range ch {
fmt.Println(v)
}
}
// 只要一个值能做界定符 比如nil, 比如0xfffe
func main() {
var ch = make(chan int, 4)
var wg = new(sync.WaitGroup)
wg.Add(2) // 增加计数值
go send(ch, wg) // 写
go send(ch, wg) // 写
go recv(ch)
// Wait() 阻塞等待所有的写通道协程结束
// 待计数值变成零,Wait() 才会返回
wg.Wait()
// 关闭通道
close(ch)
time.Sleep(time.Second)
}
```

### 2-7 多路通道
在真实的世界中,还有一种消息传递场景,那就是消费者有多个消费来源,只要有一个
来源生产了数据,消费者就可以读这个数据进行消费。这时候可以将多个来源通道的数
据汇聚到目标通道,然后统一在目标通道进行消费
```Go
/*
2-7 多路通道
在真实的世界中,还有一种消息传递场景,那就是消费者有多个消费来源,只要有一个来源生产了数据,
消费者就可以读这个数据进行消费。这时候可以将多个来源通道的数据汇聚到目标通道,然后统一在目标通道进行消费。
*/
package main
import (
"fmt"
"time"
)
// 每隔一会生产一个数
func send(ch chan int, gap time.Duration) {
i := 0
for {
i++
ch <- i
time.Sleep(gap)
}
}
// 将多个原通道内容拷贝到单一的目标通道
func collect(source chan int, target chan int) {
for v := range source {
target <- v // ch3 <- ch2 ; ch3 <- ch1
}
}
func collect2(ch1 chan int, ch2 chan int, target chan int) {
for {
select {
case v := <-ch1:
target <- v
case v := <-ch2:
target <- v
default: // 非阻塞
fmt.Println("collect2")
}
}
}
// 从目标通道消费数据
func recv(ch chan int) {
for v := range ch {
fmt.Printf("receive %d\n", v)
}
}
func main() {
var ch1 = make(chan int)
var ch2 = make(chan int)
var ch3 = make(chan int)
go send(ch1, time.Second)
go send(ch2, 2*time.Second)
// go collect(ch1, ch3)
// go collect(ch2, ch3)
go collect2(ch1, ch2, ch3)
recv(ch3)
}
```
### 2-8 多路复用select
```Go
// 2-8 多路复用select
package main
import (
"fmt"
"time"
)
func send(ch chan int, gap time.Duration) {
i := 0
for {
i++
ch <- i
time.Sleep(gap)
}
}
func recv(ch1 chan int, ch2 chan int) {
for {
select {
case v := <-ch1:
fmt.Printf("recv %d from ch1\n", v)
case v := <-ch2:
fmt.Printf("recv %d from ch2\n", v)
}
}
}
func main() {
var ch1 = make(chan int)
var ch2 = make(chan int)
go send(ch1, time.Second)
go send(ch2, 2*time.Second)
recv(ch1, ch2)
}
```
### 2-9 非阻塞读写
通道的非阻塞读写。当通道空时,读操作不会阻塞,当通道满时,写操作也不会阻塞。非
阻塞读写需要依靠 select 语句的 default 分支。当 select 语句所有通道都不可读写时,如果
定义了 default 分支,那就会执行 default 分支逻辑,这样就起到了不阻塞的效果。

```Go
// 2-9 非阻塞读写
package main
import (
"fmt"
"time"
)
func send(ch1 chan int, ch2 chan int) {
i := 0
for {
i++
select {
case ch1 <- i:
fmt.Printf("send ch1 %d\n", i)
case ch2 <- i:
fmt.Printf("send ch2 %d\n", i)
default:
fmt.Printf("ch block\n")
time.Sleep(2 * time.Second) // 这里只是为了演示
}
}
}
func recv(ch chan int, gap time.Duration, name string) {
for v := range ch {
fmt.Printf("receive %s %d\n", name, v)
time.Sleep(gap)
}
}
func main() {
// 无缓冲通道
var ch1 = make(chan int)
var ch2 = make(chan int)
// 两个消费者的休眠时间不一样,名称不一样
go recv(ch1, time.Second, "ch1")
go recv(ch2, 2*time.Second, "ch2")
send(ch1, ch2)
}
```
### 2-10 生产者、消费者模型
生产者消费模型
```Go
// 2-10 生产者、消费者模型
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
)
// 生产者
func Producer(factor int, out chan<- int) {
for i := 0; ; i++ {
out <- i * factor
time.Sleep(5 * time.Second)
}
}
// 消费者
func Consumer(in <-chan int) {
for v := range in {
fmt.Println(v)
}
}
func main() {
ch := make(chan int, 64)
go Producer(3, ch) // 生成3的倍数序列
go Producer(5, ch) // 生成5的倍数序列
go Consumer(ch)
//Ctrl +C 退出
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
fmt.Printf("wait Ctrl +C")
fmt.Printf("quit (%v)\n", <-sig)
}
```
## 3. 线程安全
### 3-1 线程安全-互斥锁
竞态检查工具是基于运行时代码检查,而不是通过代码静态分析来完成的。这意味着那些没
有机会运行到的代码逻辑中如果存在安全隐患,它是检查不出来的。
需要加上-race 执行

```Go
package main
import "fmt"
// go多协程 是有竞态,不像以前的ntyco,libco没有竞态的
func write(d map[string]int) {
d["fruit"] = 2
}
func read(d map[string]int) {
fmt.Println(d["fruit"])
}
// go run -race 3-1-unsafe.go
func main() {
d := map[string]int{}
go read(d)
write(d)
}
```
### 3-2 避免锁复制
sync.Mutex 是一个结构体对象,这个对象在使用的过程中要避免被复制 ------ 浅拷贝。复制会导致
锁被「分裂」了,也就起不到保护的作用。所以在平时的使用中要尽量使用它的指针类型。读者可
以尝试将上面的类型换成非指针类型,然后运行一下竞态检查工具,会看到警告信息再次布满整个
屏幕。锁复制存在于结构体变量的赋值、函数参数传递、方法参数传递中,都需要注意。

```Go
package main
import (
"fmt"
"sync"
)
type SafeDict struct {
data map[string]int
mutex *sync.Mutex
}
func NewSafeDict(data map[string]int) *SafeDict {
return &SafeDict{
data: data,
mutex: &sync.Mutex{},
}
}
// defer 语句总是要推迟到函数尾部运行,所以如果函数逻辑运行时间比较长,
// 这会导致锁持有的时间较长,这时使用 defer 语句来释放锁未必是一个好注意。
func (d *SafeDict) Len() int {
d.mutex.Lock()
defer d.mutex.Unlock()
return len(d.data)
}
// func (d *SafeDict) Test() int {
// d.mutex.Lock()
// length := len(d.data)
// d.mutex.Unlock() // 手动解锁 减少粒度 // 这种情况就不要用 defer d.mutex.Unlock()
// fmt.Println("length: ", length)
// // 这里还有耗时处理 耗时1000ms
// }
func (d *SafeDict) Put(key string, value int) (int, bool) {
d.mutex.Lock()
defer d.mutex.Unlock()
old_value, ok := d.data[key]
d.data[key] = value
return old_value, ok
}
func (d *SafeDict) Get(key string) (int, bool) {
d.mutex.Lock()
defer d.mutex.Unlock()
old_value, ok := d.data[key]
return old_value, ok
}
func (d *SafeDict) Delete(key string) (int, bool) {
d.mutex.Lock()
defer d.mutex.Unlock()
old_value, ok := d.data[key]
if ok {
delete(d.data, key)
}
return old_value, ok
}
func write(d *SafeDict) {
d.Put("banana", 5)
}
func read(d *SafeDict) {
fmt.Println(d.Get("banana"))
}
// go run -race 3-2-lock.go
func main() {
d := NewSafeDict(map[string]int{
"apple": 2,
"pear": 3,
})
go read(d)
write(d)
}
```
### 3-3 使用匿名锁字段
在结构体章节,我们知道外部结构体可以自动继承匿名内部结构体的所有方法。如果将上面的
SafeDict 结构体进行改造,将锁字段匿名,就可以稍微简化一下代码。

```Go
package main
import (
"fmt"
"sync"
)
type SafeDict struct {
data map[string]int
*sync.Mutex
}
func NewSafeDict(data map[string]int) *SafeDict {
return &SafeDict{
data,
&sync.Mutex{}, // 一样是要初始化的
}
}
func (d *SafeDict) Len() int {
d.Lock()
defer d.Unlock()
return len(d.data)
}
func (d *SafeDict) Put(key string, value int) (int, bool) {
d.Lock()
defer d.Unlock()
old_value, ok := d.data[key]
d.data[key] = value
return old_value, ok
}
func (d *SafeDict) Get(key string) (int, bool) {
d.Lock()
defer d.Unlock()
old_value, ok := d.data[key]
return old_value, ok
}
func (d *SafeDict) Delete(key string) (int, bool) {
d.Lock()
defer d.Unlock()
old_value, ok := d.data[key]
if ok {
delete(d.data, key)
}
return old_value, ok
}
func write(d *SafeDict) {
d.Put("banana", 5)
}
func read(d *SafeDict) {
fmt.Println(d.Get("banana"))
}
func main() {
d := NewSafeDict(map[string]int{
"apple": 2,
"pear": 3,
})
go read(d)
write(d)
}
```
### 3-4 使用读写锁
日常应用中,大多数并发数据结构都是读多写少的,对于读多写少的场合,可以将互斥锁换
成读写锁,可以有效提升性能。sync 包也提供了读写锁对象 RWMutex,不同于互斥锁只有两
个常用方法 Lock() 和 Unlock(),读写锁提供了四个常用方法,分别是写加锁 Lock()、写释放锁
Unlock()、读加锁 RLock() 和读释放锁 RUnlock()。写锁是排他锁,加写锁时会阻塞其它协程再
加读锁和写锁,读锁是共享锁,加读锁还可以允许其它协程再加读锁,但是会阻塞加写锁。

```Go
// 3-4 使用读写锁
package main
import (
"fmt"
"sync"
)
type SafeDict struct {
data map[string]int
*sync.RWMutex // sync.Mutex API也有点不一样
}
func NewSafeDict(data map[string]int) *SafeDict {
return &SafeDict{data, &sync.RWMutex{}}
}
func (d *SafeDict) Len() int {
d.RLock()
defer d.RUnlock()
return len(d.data)
}
func (d *SafeDict) Put(key string, value int) (int, bool) {
d.Lock()
defer d.Unlock()
old_value, ok := d.data[key]
d.data[key] = value
return old_value, ok
}
func (d *SafeDict) Get(key string) (int, bool) {
d.RLock()
defer d.RUnlock()
old_value, ok := d.data[key]
return old_value, ok
}
func (d *SafeDict) Delete(key string) (int, bool) {
d.Lock()
defer d.Unlock()
old_value, ok := d.data[key]
if ok {
delete(d.data, key)
}
return old_value, ok
}
func write(d *SafeDict) {
d.Put("banana", 5)
}
func read(d *SafeDict) {
fmt.Println(d.Get("banana"))
}
func main() {
d := NewSafeDict(map[string]int{
"apple": 2,
"pear": 3,
})
go read(d)
write(d)
}
```
3.5 发布订阅模型
综合前面学的
支持过滤器设置主题

```Go
// 3.5 发布订阅模型
package main
import (
"fmt"
"strings"
"sync"
"time"
)
type (
subscriber chan interface{} // 订阅者为一个通道
topicFunc func(v interface{}) bool // 主题为一个过滤器
)
// 发布者对象
type Publisher struct {
m sync.RWMutex //读写锁
buffer int // 订阅队列的缓存大小
timeout time.Duration // 发布超时时间
subscribers map[subscriber]topicFunc // 订阅者信息
}
// 构建一个发布者对象,可以设置发布超时时间和缓存队列的长度
func NewPublisher(publishTimeout time.Duration, buffer int) *Publisher {
return &Publisher{
buffer: buffer,
timeout: publishTimeout,
subscribers: make(map[subscriber]topicFunc),
}
}
// 关闭发布者对象,同时关闭所有的订阅通道
func (p *Publisher) Close() {
p.m.Lock()
defer p.m.Unlock()
for sub := range p.subscribers {
delete(p.subscribers, sub)
close(sub)
}
}
// 添加一个新的订阅者,订阅过滤器筛选后的主题
func (p *Publisher) SubscribeTopic(topic topicFunc) chan interface{} {
ch := make(chan interface{}, p.buffer)
p.m.Lock()
p.subscribers[ch] = topic
p.m.Unlock()
return ch
}
// 添加一个新的订阅者,订阅全部主题
func (p *Publisher) Subscribe() chan interface{} {
return p.SubscribeTopic(nil)
}
// 退出订阅
func (p *Publisher) Evict(sub chan interface{}) {
p.m.Lock()
defer p.m.Unlock()
delete(p.subscribers, sub)
close(sub)
}
// 发送主题,可以容忍一定的超时
func (p *Publisher) sendTopic(
sub subscriber, topic topicFunc, v interface{}, wg *sync.WaitGroup,
) {
defer wg.Done()
if topic != nil && !topic(v) { // 过滤信息
return
}
select {
case sub <- v:
case <-time.After(p.timeout): // 超时
}
}
// 发布一个主题
func (p *Publisher) Publish(v interface{}) {
p.m.Lock()
defer p.m.Unlock()
var wg sync.WaitGroup
for sub, topic := range p.subscribers {
wg.Add(1)
go p.sendTopic(sub, topic, v, &wg)
}
wg.Wait()
}
func main() {
p := NewPublisher(100*time.Millisecond, 10)
defer p.Close()
all := p.Subscribe()
golang := p.SubscribeTopic(func(v interface{}) bool {
if s, ok := v.(string); ok {
return strings.Contains(s, "golang")
}
return false
})
p.Publish("hello world")
p.Publish("hello, golang")
go func() {
for msg := range all {
fmt.Println("all:", msg)
}
}()
go func() {
for msg := range golang {
fmt.Println("golang:", msg)
}
}()
// 运行一段时间后退出
time.Sleep(3 * time.Second)
}
```
### 3.6 sync.Once初始化
sync.Once.Do(f func())是一个挺有趣的东西,能保证once只执行一次,无论你是否更换once.Do(xx)
这里的方法,这个sync.Once块只会执行一次。

```Go
package main
import (
"fmt"
"sync"
"time"
)
var once sync.Once
func main() {
for i, v := range make([]string, 10) {
once.Do(onces)
fmt.Println("count:", v, "---", i)
}
for i := 0; i < 5; i++ {
go func() {
once.Do(onced)
fmt.Println("213")
}()
}
time.Sleep(4000)
}
func onces() {
fmt.Println("执行onces")
}
func onced() {
fmt.Println("执行onced")
}
```
## 4. context
4 Go语言Context
为什么需要 Context
•每一个处理都应该有个超时限制
•需要在调用中传递这个超时
• 比如开始处理请求的时候我们说是 3 秒钟超时
• 那么在函数调用中间,这个超时还剩多少时间了?
• 需要在什么地方存储这个信息,这样请求处理中间
可以停止
Context是协程安全的。代码中可以将单个Context传递给任意数量的goroutine,并在取
消该Context时可以将信号传递给所有的goroutine。
### 4.1 Context接口
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() \<-chan struct{}
Err() error
Value(key interface{}) interface{}
}
◼ Deadline方法是获取设置的截止时间的意思,第一个返回式是截止时间,到了这个时间点,Context
会自动发起取消请求;第二个返回值ok==false时表示没有设置截止时间,如果需要取消的话,需要调用取消函数进行取消
◼ Done方法返回一个只读的chan,类型为struct{},我们在goroutine中,如果该方法返回的chan可以
读取,则意味着parent context已经发起了取消请求,我们通过Done方法收到这个信号后,就应该
做清理操作,然后退出goroutine,释放资源
◼ Err方法返回取消的错误原因,因为什么Context被取消。
◼ Value方法获取该Context上绑定的值,是一个键值对,所以要通过一个Key才可以获取对应的值,这个值一般是线程安全的
```Go
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel1 := context.WithCancel(context.Background())
go func(ctx context.Context) {
for {
select {
case v := <-ctx.Done():
fmt.Println("监控退出,停止了..., v: ", v, ", err:", ctx.Err())
return
default:
time.Sleep(2 * time.Second)
fmt.Println("goroutine监控中...")
// time.Sleep(2 * time.Second)
}
}
}(ctx)
time.Sleep(5 * time.Second)
fmt.Println("可以了,通知监控停止")
cancel1()
//为了检测监控过是否停止,如果没有监控输出,就表示停止了
time.Sleep(5 * time.Second)
}
```
### 4.1 Background()和TODO()
◼ Go语言内置两个函数:Background() 和 TODO(),这两个函数分别返回一个实现了 Context 接口的background 和 todo。
◼ Background() 主要用于 main 函数、初始化以及测试代码中,作为 Context 这个树结构的最顶层的
Context,也就是根 Context。
◼ TODO(),它目前还不知道具体的使用场景,在不知道该使用什么 Context 的时候,可以使用这个。
◼ background 和 todo 本质上都是 emptyCtx 结构体类型,是一个不可取消,没有设置截止时间,没有携带任何值的 Context。
```Go
package main
import (
"context"
"fmt"
"time"
)
func main() {
// ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(3*time.Second))
// var wg sync.WaitGroup
go func(ctx context.Context) {
// wg.Add(1)
// defer wg.Done()
for {
select {
case <-ctx.Done():
fmt.Println("监控退出,停止了..., err:", ctx.Err())
return
default:
time.Sleep(2 * time.Second)
fmt.Println("goroutine监控中...")
// time.Sleep(2 * time.Second)
}
}
}(ctx)
// cancel()
time.Sleep(5 * time.Second)
fmt.Println("可以了,通知监控停止")
cancel()
// wg.Wait() // 等待协程退出
//为了检测监控过是否停止,如果没有监控输出,就表示停止了
time.Sleep(5 * time.Second)
}
```
### 4.2 Context的继承衍生
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key interface{}, val interface{}) Context
四个With函数,接收的都有一个partent参数,就是父Context,我们要基于这个父Context创建出子
Context的意思
◼ WithCancel函数,传递一个父Context作为参数,返回子Context,以及一个取消函数用来取消Context
◼ WithDeadline函数,和WithCancel差不多,它会多传递一个截止时间参数,意味着到了这个时间点,会自动取消
Context,当然我们也可以不等到这个时候,可以提前通过取消函数进行取消
◼ WithTimeout和WithDeadline基本上一样,这个表示是超时自动取消,是多少时间后自动取消Context的意思,只是传参数不一样。
◼ WithValue函数和取消Context无关,它是为了生成一个绑定了一个键值对数据的Context,这个绑定的数据可以通过Context.Value方法访问到

```Go
package main
import (
"context"
"fmt"
"sync"
"time"
)
func work(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case <-ctx.Done():
fmt.Println("监控退出,停止了...")
return
default:
fmt.Println("hello")
time.Sleep(time.Second)
}
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() // 在建立之后,立即 defer cancel() 是一个好习惯。
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go work(ctx, &wg)
}
time.Sleep(time.Second)
wg.Wait()
}
```
### 4.3 Context使用原则
◼ 不要把Context放在结构体中,要以参数的方式进行传递
◼ 以 Context 作为参数的函数方法,应该把 Context 作为第一个参数
◼ 给一个函数方法传递Context的时候,不要传递nil,如果不知道传递什么,就使用
context.TODO
◼ Context 的 Value 相关方法应该传递请求域的必要数据,不应该用于传递可选参数;
◼ Context 是线程安全的,可以放心的在多个 Goroutine 中传递。
```Go
package main
import (
"context"
"fmt"
"time"
)
var key string = "name"
var key2 string = "name1"
func main() {
ctx, cancel := context.WithCancel(context.Background())
//附加值
valueCtx := context.WithValue(ctx, key, "key【监控1】") // 是否可以有多个key
valueCtx2 := context.WithValue(valueCtx, key2, "key【监控2】")
go watch(valueCtx2)
time.Sleep(5 * time.Second)
fmt.Println("可以了,通知监控停止")
cancel()
//为了检测监控过是否停止,如果没有监控输出,就表示停止了
time.Sleep(5 * time.Second)
}
func watch(ctx context.Context) {
for {
select {
case <-ctx.Done():
//取出值
fmt.Println(ctx.Value(key), "监控退出,停止了...")
fmt.Println(ctx.Value(key2), "监控退出,停止了...")
return
default:
//取出值
fmt.Println(ctx.Value(key), "goroutine监控中...")
time.Sleep(2 * time.Second)
}
}
}
```
### 4.4 Derived contexts派生上下文
Context包提供了从现有Context值派生新Context值的函数。这些值形成一个树:当一个
Context被取消时,从它派生的所有Context也被取消。
```Go
package main
import (
"context"
"fmt"
"time"
)
func work(ctx context.Context, str string) {
for {
select {
case <-ctx.Done():
fmt.Println("退出 ", str)
return
}
}
}
func main() {
ctx1 := context.Background()
ctx2, cancel2 := context.WithCancel(ctx1)
ctx3, cancel3 := context.WithTimeout(ctx2, time.Second*5)
ctx4, cancel4 := context.WithTimeout(ctx3, time.Second*3)
ctx5, cancel5 := context.WithTimeout(ctx4, time.Second*6)
ctx6 := context.WithValue(ctx5, "userID", 12)
go work(ctx1, "ctx1")
go work(ctx2, "ctx2")
go work(ctx3, "ctx3")
go work(ctx4, "ctx4")
go work(ctx5, "ctx5")
go work(ctx6, "ctx6")
time.Sleep(1 * time.Second)
cancel5()
time.Sleep(5 * time.Second)
cancel3()
cancel4()
cancel5()
cancel2()
}
```
推荐教程
https://geektutu.com/post/geecache-day1.html
Golang 如何正确使用 Context
https://studygolang.com/articles/23247?fr=sidebar
## cgo go和c混编
```bash
#include /test1 或者/test2
")
}
func SimpleServer(w http.ResponseWriter, request *http.Request) {
io.WriteString(w, "hello, world
")
}
func FormServer(w http.ResponseWriter, request *http.Request) {
w.Header().Set("Content-Type", "text/html")
switch request.Method {
case "GET":
io.WriteString(w, form)
case "POST":
request.ParseForm()
fmt.Println("request.Form[in]:", request.Form["in"])
io.WriteString(w, request.Form["in"][0])
io.WriteString(w, "\n")
io.WriteString(w, request.Form["in"][1]) // go web开发
// var ptr *int
// *ptr = 0x123445 // 模拟异常
}
}
func main() {
http.HandleFunc("/", HomeServer)
http.HandleFunc("/test1", SimpleServer)
http.HandleFunc("/test2", FormServer)
err := http.ListenAndServe(":9000", nil)
if err != nil {
fmt.Printf("http.ListenAndServe()函数执行错误,错误为:%v\n", err)
return
}
}
```
### 2.6 panic处理
2-6-panic-server.go
```Go
package main
import (
"fmt"
"io"
"log"
"net/http"
)
const form = ``
func HomeServer(w http.ResponseWriter, request *http.Request) {
io.WriteString(w, "/test1 或者/test2
")
}
func SimpleServer(w http.ResponseWriter, request *http.Request) {
io.WriteString(w, "hello, world
")
}
func FormServer(w http.ResponseWriter, request *http.Request) {
w.Header().Set("Content-Type", "text/html")
switch request.Method {
case "GET":
io.WriteString(w, form)
case "POST":
request.ParseForm()
fmt.Println("request.Form[in]:", request.Form["in"])
io.WriteString(w, request.Form["in"][0])
io.WriteString(w, "\n")
io.WriteString(w, request.Form["in"][1])
// var ptr *int
// *ptr = 0x123445 // 模拟异常 注意协程的异常处理
var ptr *int
var a int
ptr = &a
*ptr = 0x123445 // 也是可以取地址写入的
}
}
func main() {
http.HandleFunc("/", HomeServer)
http.HandleFunc("/test1", logPanics(SimpleServer))
http.HandleFunc("/test2", logPanics(FormServer))
err := http.ListenAndServe(":9000", nil)
if err != nil {
fmt.Printf("http.ListenAndServe()函数执行错误,错误为:%v\n", err)
return
}
}
func logPanics(handle http.HandlerFunc) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
defer func() {
if x := recover(); x != nil {
log.Printf("[%v] caught panic: %v", request.RemoteAddr, x)
}
}()
handle(writer, request)
}
}
```

## 3 模板
3 模板
1)替换 {{.字段名}}
3-1-template.go
```Go
package main
import (
"fmt"
"html/template"
"io"
"net/http"
)
var myTemplate *template.Template
type Result struct {
output string
}
func (p *Result) Write(b []byte) (n int, err error) {
fmt.Println("called by template")
p.output += string(b)
return len(b), nil
}
type Person struct {
Name string
Title string
Age int
}
func userInfo(w http.ResponseWriter, r *http.Request) {
fmt.Println("handle hello")
//fmt.Fprintf(w, "hello ")
var arr []Person
p := Person{Name: "Dar", Age: 18, Title: "个人网站"}
p1 := Person{Name: "Ki", Age: 19, Title: "个人网站"}
p2 := Person{Name: "子", Age: 20, Title: "个人网站"}
arr = append(arr, p)
arr = append(arr, p1)
arr = append(arr, p2)
fmt.Println("arr:", arr)
resultWriter := &Result{}
io.WriteString(resultWriter, "hello 模板")
err := myTemplate.Execute(w, arr) // 模板替换, 执行完后, html模板和参数arr就写入 w http.ResponseWriter
if err != nil {
fmt.Println(err)
}
fmt.Println("template render data:", resultWriter.output)
//myTemplate.Execute(w, p)
//myTemplate.Execute(os.Stdout, p)
//file, err := os.OpenFile("C:/test.log", os.O_CREATE|os.O_WRONLY, 0755)
//if err != nil {
// fmt.Println("open failed err:", err)
// return
//}
}
func initTemplate(filename string) (err error) {
myTemplate, err = template.ParseFiles(filename)
if err != nil {
fmt.Println("parse file err:", err)
return
}
return
}
func main() {
initTemplate("./index.html")
http.HandleFunc("/user/info", userInfo)
err := http.ListenAndServe("0.0.0.0:9000", nil)
if err != nil {
fmt.Println("http listen failed")
}
}
```
### 3.1 模板-替换 {{.字段名}}
## 4 Mysql
建库建表
在MySQL中创建一个名为go_test的数据库
CREATE DATABASE go_test;
进入该数据库:
use go_test;
创建一张用于测试的数据表:
CREATE TABLE \`user\` (
\`id\` BIGINT(20) NOT NULL AUTO_INCREMENT,
\`name\` VARCHAR(20) DEFAULT '',
\`age\` INT(11) DEFAULT '0',
PRIMARY KEY(\`id\`)
)ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT
CHARSET=utf8mb4;
### 4.0 连接mysql
Open函数:
db, err := sql.Open("mysql", "用户名:密码@tcp(IP:端口)/数据库?charset=utf8")
例如:db, err := sql.Open("mysql", "root:1234@tcp(127.0.0.1:3306)/test?charset=utf8")
```Go
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql" // 注释掉后异常 _ 调用初始化函数
)
// https://github.com/go-sql-driver/mysql#usage
func main() {
db, err := sql.Open("mysql", "root:1234@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
fmt.Println("err:", err) // err: