前言
go是静态类型的语言
1.Go基础
1.1 Go变量声明及赋值
var name string // 声明 name = "zyj" // 赋值 var name = "zyj" // 声明并赋值 name := "zyj" // 声明并赋值对于字符串,go中有[]byte和string两种类型,可以互相转化。
b := []byte("zyj") str := string(b)特别注意:变量只能声明一次,即 var 和 := 均只能使用一次,而且对于同一变量二者只能选择其一。
在循环中使用 var 和 := 声明并赋值变量时,会执行多次,因为作用域是块作用域。
如果函数返回两个值,只有其中有一个未定义,就可以使用 := 已定义的会默认使用赋值。
go ""表示字符串string '' 表示字节类型 byte
1.2 循环
go语言的循环用range可以对string,array, 等进行迭代
// e.g. map的复制
for key, value := range oldMap { newMap[key] = value }for循环这里有个小坑,例如:
for i := 0; i < len(list); i++ { // 循环退出的判断应使用定值 l.append(list, t) }这时候,由于list数组在不断追加,所以它的长度len(list)也会一直增加,则产生与预想不同的结果。
1.3 数组
var arr [3]int // 声明 arr[0] = 1 // 设置元素值,此时数组元素为 [1, 0, 0]声明并初始化
var arr = [3]int{1,2,3}
或
arr := []int{1,2,3} // 切片 或 arr := [...]int{1,2,3}
1.4 切片:TODO
arr := []int{1:1, 0:2} // {索引:值} 其余元素设置为0
注意,如果先定义再赋值,则一定要保证数组元素相等:
即
var a [5]int 一定对应 a = [5]int{1,2,3} var a []int 一定对应 a = []int{1,2,3} // 好像这种不定长就叫切片也就是说go语言里面一旦定义了类型,比如数组长度,就无法在后面修改了。如果定义了一个数组后面可能会改它的长度,则使用var a[] int 来定义
1.5 切片 Slice
var arr []int // 默认定义的切片,len=0, cap=0 arr := make([]int, len, cap) // 也可以用make的方式定义切片,长度为len,容量为cap arr = append(arr, 3) // 往切片中动态添加元素 arr = append(arr, 3,5,6) // 一次增加多个Go语言的数组在初始化后,长度和元素类型是固定的。
而切片的本质并不是数组,实际上是一个结构体,包含 指向底层数组的指针(引用),长度,和 容量
当追加操作时,切片中元素个数大于cap时,实际会重新创建一个更大的切片(该切片的cap 会比len多1),然后将原来的元素拷贝过去
1.6 结构体struct
type Boy struct { Name string Age int }此时Boy是一个类型,可以有属于该类型的类方法:
func (b * Boy) Say() {}
创建类型实例:
var boy Boy // 声明 var boy = Boy{"zyj", 18} // 声明并赋值 b := &Boy{"zyj", 18} // 创建结构体指针 b := new(Boy) // 创建结构体指针 var p * Boy p = new(Boy) 或 p = &boy // 给指针赋值注:如果结构体字段名开头不大写,则无法进行Marshal和Unmarshal,因为要使用外部json包,小写表示不可导出。
- 即使Unmarshal的json序列字段小写,也可以反序列化到结构体成功。
- 如果需要Marshal生成的json字段小写,则需要加上tag json:"name"
1.7 json序列化和反序列化:
func Unmarshal(data []byte, v interface{}) error func Marshal(v interface{}) ([]byte, error)Unmarshal到结构体时第二个参数 需要为 结构体指针
1.8 Map
var mymap map[string]string // 声明
此时mymap是一个空(nil)的map结构,不能往其内增加元素,否则会报错
使用make函数初始化:
mymap := make(map[string]string) // 初始化一个空的map
虽然此时map也是空,但是可以对其操作赋值, 如mymap["name"] = "zyj"
json 和 map互相转化
这样可以不需要提前定义结构体来解析json字符串
json -> map str := []byte(`{"name":"zyj","age":13}`) j(str, &mymap) // 如果map中有值,反序列化过程会在其中追加 map -> json bytes, err := j(&mymap) // 这里参数使用map或map的指针都可(map定义过即可,无须初始化)特别的,当json字符串 Unmarshal 到 map[string]interface{} 时,会自动做以下转换:
- 所有数值(Number)全都转换为 -> float64
- boolean -> bool
- String -> string
- Array -> []interface{}
- Object -> map[string]interface{}
- null -> nil
同样,对于 map[string]interface{} 中的值 也需要断言后才能使用
但是如果一个json序列 Unmarshal到一个结构体,只要结构正确,再复杂也能匹配,并且数据类型无须做转化,可以直接使用。
1.9 接口 interface
type XXX interface{}
接口是什么?一个接口包含两层意思:它是一个方法的集合,同样是一个类型。让我们首先关注接口作为方法的集合这一方面。
所有实现了该接口中方法的类型(struct) 可以一同被视为属于这个接口类型
interface{} 可以接收任何类型参数,因为其内方法为空,所有类型都可以视为实现了一个空接口,所以都属于 interface{}
1.10 断言(Type Assertion)
如果函数接受interface{} 参数, 传进来一个float,此时想要对该变量进行运算,则会报错:
mismatched types interface {} and int
需要对该变量进行类型断言后才能使用:
var num interface{} num = 2.5 // 如果断言成功 val 等于 num, ok 为 true // 如果失败则 val 等于 0, ok 为 false val, ok := num.(float64) if ok { (val+1) // 输出3.5 }精度问题:
t := 1.235
t = t+1
(t) // 打印结果 2.2359999999999998
为什么?目前只发现 1.235 和 1.236 是这样
已解决,Println不精确
应使用格式化输出: ("%f",t) // 结果:2.236000
json,map,struct转换
json Unmarshal到 interface{} 的转换
查看go语言变量类型
1.11 函数
Go 语言不像其它面向对象语言一样可以写个类,然后在类里面写一堆方法,但其实Go语言的方法很巧妙的实现了这种效果:我们只需要在普通函数前面加个接受者(receiver,写在函数名前面的括号里面),这样编译器就知道这个函数(方法)属于哪个struct了。例如:
type A struct { Name string } func (a A)foo() { //接收者写在函数名前面的括号里面 ("foo") } func main() { a := A{} a.foo() //foo }1.12 go关键字
用于轻松开启高并发
2. Go解析json
导包: import "encoding/json"
函数:
func Marshal(v interface{}) ([]byte, error)
func Unmarshal(data []byte, v interface{}) error
注:Go中任何类型都是接口,interface{} 类似于C语言的void * ,用于指代任何类型
interface{} 是empty interface 任何类型都可以视为实现了空接口
注意:Go的特性,首字母为大写的字段才是可导出的字段,由于要调用json包中的函数,如果字段名小写,就无法赋值传参成功
struct结构体 和 []byte的转换:
结构体 ——> JSON byte数组
type Boy struct { Name string Age int } func main() { boy := Boy{"zyj", 23} str, _ := j(boy) (string(str)) //string() 将[]byte转化为字符串 }输出: {"Name":"zyj","Age":23}
JSON ——> 结构体
func main() { var boy Boy str := []byte(`{"Name":"zyj","Age":23}`) err := j(str, &boy); if err != nil { (err) } (boy) }输出: {zyj 23}
- 结构体字段名小写也无法调用Mashal函数序列化
- 如果实际需要的 JSON字符串 键名称是小写,最好需要在结构体中指定(使用tag的方式),这样使用结构体序列化 Mashal 的时候字段名可以转化为小写。
- 但是如果 JSON字符串 字段名为小写,需要反序列化到结构体时,即使机构体字段大写也可以Unmashal成功,这里不区分大小写。
使用标签使序列化后字段小写
type Boy struct { Name string `json:"name"` Age int `json:"age"` }3. go的安装编译
一般而言,go语言packge的名称和目录名保持一致。
go install用于创建应用包,生成.a文件,用于安装应用。默认是$GOPATH/pkg/darwin_amd64
go build用于编译程序,生成一个可执行文件,默认是当前目录。
go run编译并运行,注意,packge只可以是main。
可以使用go get 从开源社区,如Github 、 Googlecode 、 bitbucket 、 Launchpad获取远程包,如go get gi
go get -u 参数可以自动更新包,而且 go get 会自动获取该包依赖的其他第三方包。
go get 可以理解为以下2步:第一步是通过源码工具 clone 从互联网下载或更新指定的代码包及其依赖包到src下面;第2步执行go install对代码包及其依赖包进行编译和安装。
4. Go进阶
4.1 Go里的锁
4.1.1 Mutex 互斥锁
其中Mutex为互斥锁,Lock()加锁,Unlock()解锁,使用Lock()加锁后,便不能再次对其进行加锁,直到利用Unlock()解锁对其解锁后,才能再次加锁.适用于读写不确定场景,即读写次数没有明显的区别,并且只允许只有一个读或者写的场景,所以该锁也叫做全局锁。
package main 2 import ( 3 "sync" 4 "fmt" 5 "time" 6 ) 7 type safeCounter struct { 8 number int 9 10 } 11 func (sc *safeCounter) Increment() { 12 () 13 14 () 15 } 16 func (sc *safeCounter) Decrement() { 17 () 18 19 () 20 } 21 func (sc *safeCounter) getNumber() int { 22 () 23 number := 24 () 25 return number 26 } 27 func main() { 28 sc := new(safeCounter) 29 for i := 0; i < 100; i++ { 30 go () 31 go () 32 } 33 (1)*) 34 ()) 35 }注意,(1)*)要加上,否则会出现混乱的结构,因为加减还没执行完毕,就打印了。
4.1.2 RWMutex读写锁
读写锁是针对读写操作的互斥锁。遵循两个原则即可
可以随便地读,并且支持多个goroutine同时读取
写的时候,既不能读也不能写。
RWMutex 提供了四个方法
func (*RWMutex) Lock
func (*RWMutex) Unlock
func (*RWMutex) RLock
func (*RWMutex) RUnlock
package main import ( "fmt" "math/rand" "sync" "time" ) type MapCounter struct { m map[int]int } func (mapCounter *MapCounter) Reader(n int) { for { ma() v := ma[rand.Intn(n)] ma() (v) (1 * ) } } func (mapCounter *MapCounter) Writer(n int) { for i := 0; i < n; i++ { ma() ma[i] = i * 10 ma() (1 * ) } } func main() { mc := MapCounter{m: make(map[int]int)} go mc.Writer(10) go mc.Reader(10) go mc.Reader(10) (15 * ) }4.2 defer
Defer is used to ensure that a function call is performed later in a program’s execution, usually for purposes of cleanup. defer is often used where e.g. ensure and finally would be used in other languages.
4.2.1 defer执行顺序
package main import "fmt" func main() { defer goodbye() defer goodnight() ("Hello world.") } func goodnight() { ("GoodNight") } func goodbye() { ("Goodbye") }输出:
Hello world.
GoodNight
Goodbye
defer在函数结束之前执行,后进先出。多个defer的执行顺序: Multiple defers are stacked last-in first-out so that the most recently deferred function is run first.**
4.2.2 那么defer之前就return了呢?
package main import "fmt" func main() { ("Hello world.") return defer goodbye() defer goodnight() } func goodnight() { ("GoodNight") } func goodbye() { ("Goodbye") }输出:
Hello world.
defer是在return之前执行的。
4.2.3 defer用于关闭文件
package main import "fmt" import "os" func main() { f := createFile(".;) defer closeFile(f) writeFile(f) } func createFile(p string) *os.File { ("creating") f, err := os.Create(p) if err != nil { panic(err) } return f } func writeFile(f *os.File) { ("writing") (f, "data") } func closeFile(f *os.File) { ("closing") f.Close() }4.2.4 defer用于释放锁
func Divide(i int) error { mu.Lock() defer mu.Unlock() if i == 0 { return errors.New("Can't divide by zero!") } val /= i return nil }4.2.5 defer中的坑
package main import ( "fmt" ) func main() { (f()) (f1()) (f2()) } func f() (result int) { defer func() { result++ }() return 0 } func f1() (r int) { t := 5 defer func() { t = t + 5 }() return t } func f2() (r int) { defer func(r int) { r = r + 5 }(r) return 1 }输出:
1
5
1
要使用defer时不踩坑,最重要的一点就是要明白,return xxx这一条语句并不是一条原子指令! 函数返回的过程是这样的:先给返回值赋值,然后调用defer表达式,最后才是返回到调用函数中。