数组
切片(Slice)
make([]T,len,cap)
变长的序列,序列中元素类型相同
组成部分:指针、长度和容量
多个slice之间可以共享底层的数据,并且引用可以重叠
切片操作超出cap的上限会导致一个panic,超出len意味扩展了slice
slice之间不能直接比较,但对于字节型可以使用bytes.Equal函数判断
append函数(对于理解slice底层如何工作十分重要)
Map
结构体
Json
文本和HTML模板
函数可以把一个大工作分解为小的任务,可以多次调用
函数声明
递归
多返回值
错误
函数值
匿名函数
拥有函数名的函数只能在包级语法块中被声明
函数字面量可以在使用函数时,再定义它
在函数中定义的内部函数可以引用该函数的变量
// squares返回一个匿名函数。
// 该匿名函数每次被调用时都会返回下一个数的平方。
func squares() func() int {
var x int
return func() int {
x++
return x * x
}
}
func main() {
f := squares()
fmt.Println(f()) // "1"
fmt.Println(f()) // "4"
fmt.Println(f()) // "9"
fmt.Println(f()) // "16"
}
可变参数
func sum(vals...int) int {
total := 0
for _,val := range vals {
total += val
}
return total
}
fmt.Println(sum())
fmt.Println(sum(1,2))
方法声明
package main
import "fmt"
type rect struct {
width, height int
}
func (r *rect) area() int {
return r.width * r.height
}
func (r rect) perim() int {
return 2*r.width + 2*r.height
}
func main() {
r := rect{width: 10, height: 5}
fmt.Println("area: ", r.area())
fmt.Println("perim:", r.perim())
rp := &r
fmt.Println("area: ", rp.area())
fmt.Println("perim:", rp.perim())
}
基于指针对象的方法
当调用一个函数时,会对其每一个参数值进行拷贝,如果一个函数需要更新一个变量,或者函数的其中一个参数过大我们希望可以避免这种默认拷贝,这种情况需要指针
类型本身是指针不可以做为接收器
如果接收器p是一个Point类型的变量,并且其方法需要一个Point指针作为接收器
p := Point{1,2}
//两种一样,编译器隐式帮我们用&p调用方法
--(&p).ScaleBy(2)
--p.ScaleBy(2)
//false,为指针时不可以用临时变量,无地址
--Point{1,2}.ScaleBy(2)
il也是一个合法的接收器类型
方法值和方法表达式
distanceFromP := p.Distance
fmt.Println(distanceFromP(q))
封装
Go语言的接口满足隐式实现
一个类型如果拥有一个接口需要的全部方法,那么这个类型就实现了这个接口
类型断言(x.(T))
转化类型
var t int
var x interface{}
x = t
//转成int
y, ok = x.(int)
判断一个变量是否实现了指定的接口
type test interface {
run()
}
type tet struct{}
func (t tet) run() {
fmt.Println("go go go")
}
func main() {
var t interface{}
t = tet{}
if test, ok := t.(test); ok {
//输出go go go
test.run()
}
}
go语句会使其语句中的函数在一个单独的goroutine中运行。而go语句本身会迅速的完成
f() //wait for it return
go g() //don't wait return
go语言内置了调度和上下文切换的机制,智能的将goroutine中的任务合理地分配给每个CPU
一个goroutine必定对应一个函数,可以创建多个goroutine去执行相同的函数
程序启动时,Go程序会为main()函数创建一个默认的goroutine,当main()函数返回的时候其他的goroutine都会结束,可以使用time.Sleep(time.Second)等待
func hello() {
fmt.Println("hello")
}
func main() {
go hello()
fmt.Println("world")
//time.Sleep(time.Second)
}
goroutine之间的通信机制,每个channel都有一个特殊的类型,也就是channels可发送数据的类型。一个可以发送int类型数据的channel一般写为chan int
创建channel(零值为nil)
//创建无缓存的channel
ch := make(chan int)
ch := make(chan int, 0)
//创建有缓存的channel
ch := make(chan int, 3)
两个相同类型的channel可以使用==运算符比较。如果两个channel引用的是相通的对象,那么比较的结果为真。一个channel也可以和nil进行比较
channel的基本使用
ch <- x
x = <-ch
<-ch
close(ch)
无缓存的channel(同步Channels)
串联的Channels(Pipeline)
版本1
func main() {
naturals := make(chan int)
squares := make(chan int)
//Counter
go func() {
for x := 0; ; x++ {
naturals <- x
}
}()
//Squarer
go func() {
defer close(squares)
for {
x, ok := <-naturals
if !ok {
break
}
squares <- x * x
}
}()
for {
fmt.Println(<-squares)
}
}
版本2
func main() {
naturals := make(chan int)
squares := make(chan int)
//Counter
go func() {
defer close(naturals)
for x := 0; x < 100; x++ {
naturals <- x
}
}()
//Squarer
go func() {
defer close(squares)
for x := range naturals {
squares <- x * x
}
}()
for x := range squares {
fmt.Println(x)
}
}
单向的channel
当一个channel作为一个函数参数是,它一般总是被专门用于只发送或者只接收
chan<-int:表示一个只发送int的channel
<-chan int:表示
因为关闭操作只用于断言不再向channel发送新的数据,所以只有在发送者所在的goroutine才会调用close函数,因此对一个只接收的channel调用close将是一个编译错误
双向型channel可以隐式的转为单向的channel,不可反向转换
func counter(out chan<- int) {
for x := 0; x < 24; x++ {
out <- x
}
close(out)
}
func squarer(out chan<- int, in <-chan int) {
for x := range in {
out <- x * x
}
close(out)
}
func print(in <-chan int) {
for x := range in {
fmt.Println(x)
}
}
func main() {
naturals := make(chan int)
squares := make(chan int)
go counter(naturals)
go squarer(squares, naturals)
print(squares)
}
带缓存的Channels
带缓存的Channel内部持有一个元素队列。队列的最大容量是在调用make函数创建channel时通过第二个参数指定的
ch = make(chan string, 3)
向缓存Channel的发送操作就是向内部缓存队列的尾部插入原因,接收操作则是从队列的头部删除元素
func mirroredQuery() string {
responses := make(chan string, 3)
go func() { responses <- request("asia.gopl.io") }()
go func() { responses <- request("europe.gopl.io") }()
go func() { responses <- request("americas.gopl.io") }()
return <-responses // return the quickest response
}
func request(hostname string) (response string) { /* ... */ }
select多路复用(类似switch)
可以同时从多个通道并行读取
func main() {
//定义一个管道10个数据int
intChan := make(chan int, 10)
for i := 0; i < 10; i++ {
intChan <- i
}
//定义一个管道5个数据string
stringChan := make(chan string, 5)
for i := 0; i < 5; i++ {
stringChan <- "hello" + fmt.Sprintf("%d", i)
}
for {
select {
case v := <-intChan:
fmt.Printf("从 intChan 读取的数据%d\n", v)
case v := <-stringChan:
fmt.Printf("从 stringChan 读取的数据%v\n", v)
default:
fmt.Printf("数据获取完毕")
return
}
}
}
银行存储款并发安全版(不加锁),使用通信来共享数据
var ch1 = make(chan int)
var ch2 = make(chan int)
func deposity(amount int) {
ch1 <- amount
}
func balance() int {
return <-ch2
}
func teller() {
var balance int
for {
select {
case v := <-ch1:
balance += v
case ch2 <- balance:
}
}
}
func main() {
go deposity(123)
go deposity(123)
go teller()
time.Sleep(time.Second)
fmt.Println(balance())
}
sync.Mutex互斥锁(一个只能为1和0的信号量叫做二元信号量sema)
模拟信号量
var (
sema = make(chan struct{}, 1)
balance int
)
func Deposit(amount int) {
sema <- struct{}{}
balance = balance + amount
<-sema
}
func Balance() int {
sema <- struct{}{}
b := balance
<-sema
return b
}
func main() {
go Deposit(123)
go Deposit(123)
time.Sleep(time.Second)
fmt.Println(Balance())
}
sync包使用
var (
mu sync.Mutex
balance int
)
func Deposit(amount int) {
mu.Lock()
defer mu.Unlock()
balance = balance + amount
}
func Balance() int {
mu.Lock()
defer mu.Unlock()//return后被调用
return balance
}
func main() {
go Deposit(123)
go Deposit(123)
time.Sleep(time.Second)
fmt.Println(Balance())
}
go语言为什么不支持可重入锁?
互斥量的目的是为了确保共享变量在程序执行时的关键点上能够保证不变性。不变性的其中之一是“没有goroutine访问共享变量”。但实际上对于mutex保护的变量来说,不变性还包括其它方面。当一个goroutine获得了一个互斥锁时,它会断定这种不变性能够被保持。其获取并保持锁期间,可能会去更新共享变量,这样不变性只是短暂地被破坏。然而当其释放锁之后,它必须保证不变性已经恢复原样。尽管一个可以重入的mutex也可以保证没有其它的goroutine在访问共享变量,但这种方式没法保证这些变量额外的不变性
func Withdraw(amount int) bool {
mu.Lock()
defer mu.Unlock()
Deposit(-amount)//Deposit内部会再次去尝试给mu加锁,错误
if Balance() < 0 {
Deposit(amount)
return false // insufficient funds
}
return true
}
解决方法
var (
mu sync.Mutex
balance = 456
)
func Withdraw(amount int) bool {
mu.Lock()
defer mu.Unlock()
deposit(-amount)
if Balance() < 0 {
deposit(amount)
return false // insufficient funds
}
return true
}
func Deposit(amount int) {
mu.Lock()
defer mu.Unlock()
deposit(amount)
}
func Balance() int {
return balance
}
func deposit(amount int) {
balance = balance + amount
}
func main() {
go Withdraw(123)
time.Sleep(time.Second)
fmt.Println(Balance())
}
sync.RWMutex读写锁(多个读并发,写操作互斥)
RLock和RUnlock方法
var mu sync.RWMutex
var balance int
func Balance() int {
mu.RLock() // readers lock
defer mu.RUnlock()
return balance
}
内存同步
sync.Once初始化
初始化代码(初次初始化用排他锁,初始化后用读写锁)
var mu sync.RWMutex // guards icons
var icons map[string]string
// Concurrency-safe.
func Icon(name string) string{
mu.RLock()
if icons != nil {
icon := icons[name]
mu.RUnlock()
return icon
}
mu.RUnlock()
// acquire an exclusive lock
mu.Lock()
if icons == nil { // NOTE: must recheck for nil
loadIcons()
}
icon := icons[name]
mu.Unlock()
return icon
}
go语言提供了专门的方案来解决一次性初始化的问题
var loadIconsOnce sync.Once
var icons map[string]image.Image
// Concurrency-safe.
func Icon(name string) image.Image {
loadIconsOnce.Do(loadIcons)
return icons[name]
}
竞争条件检测
Goroutines和线程
动态栈
Goroutine调度
GOMAXPROCS
Go的调度器使用了一个叫做GOMAXPROCS的变量来决定会有多少个操作系统的线程同时执行Go的代码。其默认的值是运行机器上的CPU的核心数
在休眠中的或者在通信中被阻塞的goroutine是不需要一个对应的线程来做调度的。在I/O中或系统调用中或调用非Go语言函数时,是需要一个对应的操作系统线程的,但是GOMAXPROCS并不需要将这几种情况计数在内
在第一次执行时,最多同时只能有一个goroutine被执行。初始情况下只有main goroutine被执行,所以会打印很多1。过了一段时间后,GO调度器会将其置为休眠,并唤醒另一个goroutine,这时候就开始打印很多0了,在打印的时候,goroutine是被调度到操作系统线程上的。在第二次执行时,我们使用了两个操作系统线程,所以两个goroutine可以一起被执行,以同样的频率交替打印0和1
for {
go fmt.Print(0)
fmt.Print(1)
}
$ GOMAXPROCS=1 go run main.go
111111111111111111110000000000000000000011111...
$ GOMAXPROCS=2 go run main.go
010101010101010101011001100101011010010100110...
Goroutine没有ID号
Go提供了一种机制在运行时更新变量和检查它们的值, 调用它们的方法, 和它们支持的内在操作, 但是在编译时并不知道这些变量的类型. 这种机制被称为反射
为什么需要反射?
reflect.Type和reflect.Value
函数 reflect.TypeOf 接受任意的 interface{} 类型, 并返回对应动态类型的reflect.Type
t := reflect.TypeOf(3) // a reflect.Type
fmt.Println(t.String()) // "int"
fmt.Println(t) // "int"
fmt.Printf("%T\n", 3) // "int"
reflect 包中另一个重要的类型是 Value. 一个 reflect.Value 可以持有一个任意类型的值. 函数reflect.ValueOf 接受任意的 interface{} 类型, 并返回对应动态类型的reflect.Value. 和reflect.TypeOf 类似,reflect.ValueOf 返回的结果也是对于具体的类型, 但是 reflect.Value 也可以持有一个接口值
v := reflect.ValueOf(3) // a reflect.Value
fmt.Println(v) // "3"
fmt.Printf("%v\n", v) // "3"
fmt.Println(v.String()) // ""
v1 := reflect.ValueOf("字符串")
fmt.Println(v1) //字符串
fmt.Printf("%v\n", v1) //字符串
fmt.Println(v1.String()) //字符串
调用 Value 的 Type 方法将返回具体类型所对应的 reflect.Type
t := v.Type() // a reflect.Type
fmt.Println(t.String()) // "int"
逆操作是调用 reflect.ValueOf 对应的 reflect.Value.Interface 方法. 它返回一个 interface{} 类型表示 reflect.Value 对应类型的具体值
v := reflect.ValueOf(3) // a reflect.Value
x := v.Interface() // an interface{}
i := x.(int) // an int
fmt.Printf("%d\n", i) // "3"
返回v持有的值的字符串表示。因为go的String方法的惯例,Value的String方法比较特别。和其他获取v持有值的方法不同:v的Kind是String时,返回该字符串;v的Kind不是String时也不会panic而是返回格式为""的字符串,其中T是v持有值的类型
reflect.Value 的 Kind 方法来替代之前的类型 switch. 虽然还是有无穷多的类型, 但是它们的kinds类型却是有限的: Bool, String 和 所有数字类型的基础类型; Array 和 Struct 对应的聚合类型; Chan, Func, Ptr, Slice, 和 Map 对应的引用类似; 接口类型; 还有表示空值的无效类型. (空的 reflect.Value 对应 Invalid 无效类型.)
通过reflect.Value修改值
case
x := 2 // value type variable?
a := reflect.ValueOf(2) // 2 int no
b := reflect.ValueOf(x) // 2 int no
c := reflect.ValueOf(&x) // &x *int no
d := c.Elem() // 2 int yes (x)
其中a对应的变量不可取地址。因为a中的值仅仅是整数2的拷贝副本。b中的值也同样不可取地址。c中的值还是不可取地址,它只是一个指针&x的拷贝。实际上,所有通过reflect.ValueOf(x)返回的reflect.Value都是不可取地址的。但是对于d,它是c的解引用方式生成的,指向另一个变量,因此是可取地址的。我们可以通过调用reflect.ValueOf(&x).Elem(),来获取任意变量x对应的可取地址的Value
可以通过调用reflect.Value的CanAddr方法来判断其是否可以被取地址
fmt.Println(a.CanAddr()) //"false"
fmt.Println(b.CanAddr()) //"false"
fmt.Println(c.CanAddr()) //"false"
fmt.Println(d.CanAddr()) //"true"
从变量对应的可取地址的reflect.Value来访问变量
第一步是调用Addr()方法,它返回一个Value,里面保存了指向变量的指针
第二步是在Value上调用Interface()方法,也就是返回一个interface{},里面通用包含指向变量的指针
第三步如果我们知道变量的类型,我们可以使用类型的断言机制将得到的interface{}类型的接口强制环为普通的类型指针。这样我们就可以通过这个普通指针来更新变量了
x := 2
d := reflect.ValueOf(&x).Elem() // d refers to the variable x
px := d.Addr().Interface().(*int) // px := &x
*px = 3 // x = 3
fmt.Println(x) // 3
//也可以不使用指针,而是通过调用可取地址的reflect.Value的reflect.Value.Set方法更新值
d.Set(reflect.ValueOf(4))
fmt.Println(x) // 4
d.SetInt(5)
fmt.Println(x) // 5
Set方法将在运行时执行和编译时类似的可赋值性约束的检查。以上代码,变量和值都是int类型,但是如果变量是int64类型,那么程序将抛出一个panic异常
d.Set(reflect.ValueOf(int64(5))) // panic: int64 is not assignable to int
通用对一个不可取地址的reflect.Value调用Set方法也会导致panic异常
x := 2
b := reflect.ValueOf(x)
b.Set(reflect.ValueOf(3)) // panic: Set using unaddressable value
reflect.TypeOf和reflect.ValueOf来获取任意对象的Type和Value
type myInt int
type Person struct {
Name string
Age int
}
//反射获取任意变量的类型
func reflectFn(x interface{}) {
v := reflect.TypeOf(x)
fmt.Printf("类型:%v 类型名称:%v 类型种类:%v \n", v, v.Name(), v.Kind())
}
func main() {
a := true
b := "golang"
reflectFn(a)
reflectFn(b)
var c myInt = 34
var d = Person{
Name: "张三",
Age: 12,
}
reflectFn(c)
reflectFn(d)
var e = 10
reflectFn(&e)
}
反射中关于类型划分为两种:类型(Name())和种类(Kind():底层类型)
通过反射设置变量的值
func reflectSetValue(x interface{}) {
v := reflect.ValueOf(x)
if v.Elem().Kind() == reflect.Int64 {
v.Elem().SetInt(123)
}
}
func main() {
var a int64 = 100
reflectSetValue(&a)
fmt.Println(a)
}
结构体反射
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
Score int `json:"score"`
}
func (s Student) GetInfo() string {
var str = fmt.Sprintf("姓名:%v 年龄:%v 成绩:%v", s.Name, s.Age, s.Score)
return str
}
func (s *Student) SetInfo(name string, age int, score int) {
s.Name = name
s.Age = age
s.Score = score
}
func (s Student) Print() {
fmt.Println("打印方法")
}
//打印字段
func PrintStructField(s interface{}) {
t := reflect.TypeOf(s)
v := reflect.ValueOf(s)
if t.Kind() != reflect.Struct && t.Elem().Kind() != reflect.Struct {
fmt.Println("不是结构体")
return
}
//通过Field获取结构体字段
field0 := t.Field(0)
fmt.Printf("%#v\n", field0)
fmt.Println("字段名称:", field0.Name)
fmt.Println("字段类型:", field0.Type)
fmt.Println("字段Tag:", field0.Tag.Get("json"))
//通过FieldByName获取结构体字段
field1, ok := t.FieldByName("Age")
if ok {
fmt.Println("字段名称:", field1.Name)
fmt.Println("字段类型:", field1.Type)
fmt.Println("字段Tag:", field1.Tag.Get("json"))
}
//通过NumField获取结构体有几个字段
var fieldCount = t.NumField()
fmt.Println(fieldCount)
//通过值变量获取结构体属性对应的值
fmt.Println(v.FieldByName("Name"))
}
//打印执行方法
func PrintStructFn(s interface{}) {
t := reflect.TypeOf(s)
v := reflect.ValueOf(s)
if t.Kind() != reflect.Struct && t.Elem().Kind() != reflect.Struct {
fmt.Println("不是结构体")
return
}
//获取结构体方法
method0 := t.Method(0) //和结构体方法的ASCII有关
fmt.Println(method0.Name)
fmt.Println(method0.Type)
//获取结构体方法
method1, ok := t.MethodByName("Print")
if ok {
fmt.Println(method1.Name)
fmt.Println(method1.Type)
}
//通过值变量执行方法
v.Method(1).Call(nil)
v.MethodByName("Print").Call(nil)
//执行方法传入参数
var params []reflect.Value
params = append(params, reflect.ValueOf("李四"))
params = append(params, reflect.ValueOf(23))
params = append(params, reflect.ValueOf(99))
v.MethodByName("SetInfo").Call(params)
info := v.MethodByName("GetInfo").Call(nil)
fmt.Println(info)
//获取方法数量
fmt.Println(t.NumMethod())
}
//反射修改结构体属性
func reflectChangeStruct(s interface{}) {
t := reflect.TypeOf(s)
v := reflect.ValueOf(s)
if t.Kind() != reflect.Ptr {
fmt.Println("不是结构体指针类型")
return
} else if t.Elem().Kind() != reflect.Struct {
fmt.Println("不是结构体指针类型")
return
}
//修改结构体属性的值
age := v.Elem().FieldByName("Age")
age.SetInt(24)
}
palindrome.go
package word
func IsPalindrome(s string) bool {
for i := range s {
if s[i] != s[len(s)-1-i] {
return false
}
}
return true
}
word_test.go
package word
import "testing"
func TestPalindrome(t *testing.T) {
if !IsPalindrome("detartrated") {
t.Error(`IsPalindrome("detartrated") = false`)
}
if !IsPalindrome("kayak") {
t.Error(`IsPalindrome("kayak") = false`)
}
}
func TestNonPalindrome(t *testing.T) {
if IsPalindrome("palindrome") {
t.Error(`IsPalindrome("palindrome") = true`)
}
}