GO进阶学习2
(1)、 函数的内置函数
go的main包里自带的一些函数,可以直接调用,不引用其他的包
- ### 1.1、close
主要用来关闭channel等
- ### 1.2、len
用来求某些数据结构的长度,比如string、array、slice、map、channel
var li_1 = [2]int{1,2}
len(li_1)
- ### 1.3、new
用来分配内存,主要用来分配值类型,比如int、struct。 返回的是指针
//new分配内存
j := new(int)
*j = 100
- ### 1.4、make
用来分配内存,主要用来分配引用类型,比如chan、map、slice
//make分配内存
var my_pipe chan int
my_pipe = make(chan int,10)
//或
my_pipe := make(chan int,10)
- ### 1.5、append
在数组/切片中添加元素(参考python的列表.append方法),append方法内的数组通常一个是切片(特殊的数组)
var a = []int{1,2,3,4,5} //定义切片并初始化
a = append(a,6,7) //向切片a添加元素6,7
a = append(a,a...) //向切片a添加元素,添加的元素是 切片a,a...用来表示整个切片啊
fmt.Println(a)
执行结果
[1 2 3 4 5 6 7 1 2 3 4 5 6 7]
- ### 1.6、panic和recover
2个都是用来做错误处理,panic是go执行中出现错误时的提示,出现panic错误则程序终止。recover则用来捕获异常。(和python内的try相似)。
调用recover()就是异常的捕获,返回值为错误的信息
//使用recover进行异常捕获
func test(){
defer func(){
if err:= recover();err != nil {
fmt.Println(err)
}
}()
b := 0
a := 100/b
fmt.Println(a)
return
}
(2)、递归函数
一个函数调用自己,就叫做递归。递归在学习其他语言中使用的已经十分熟悉了,如python中的二分查找法、经典的斐波那契数列等…。需要注意的是无论何种语言,递归函数必定会有
一个出口条件(通常if判断出口条件),否则就是无限循环
一个简单的递归范例:
package main
import (
"fmt"
)
func calc(n int) int {
if n == 1 { //出口条件
return 1
}
return calc(n-1) * n
}
func main() {
n := calc(5)
fmt.Println(n)
}
斐波那契数列:
package main
import "fmt"
func fab(n int) int {
if n <= 1 {
return 1
}
return fab(n-1) + fab(n-2)
}
func main() {
for i := 0; i < 10; i++ {
n := fab(i)
fmt.Println(n)
}
}
(3)、闭包
一个函数和与其相关的引用环境组合而成的实体。通常情况是函数体内又有函数,而这个内部函数可以使用函数体内的变量。如下面的范例:
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)、数组和切片
4.1 数组
数组的定义
格式: ==var li_1 [n]int==
n表示一个正整数,表示数组的长度,数组一定要有一个长度。一旦定义长度,则长度不能变
注意:数组是值类型
数组的初始化
初始化:
- 完整的初始化:
var age0 [5]int = [5]int{1,2,3}
- 简单的初始化:
var age1 = [5]int{1,2,3}
- 常用的初始化方法:
var age2 = [...]int{1,2,3,4,5,6} //让系统判断数组的大小
- 固定元素位置的初始化:
var str = [5]string{3:”hello world”, 4:”tom”} //初始化结果[,,hell word,tom,]
数组的遍历
使用for循环:
var age2 = [...]int{1,2,3,4,5,6} for i:=0;i<len(age2);i++ { fmt.Println(age2[i]) } //或 for _,k := range(age2) { //不需要小标可以使用的_来代替 fmt.Println(k) }
输出结果
多维数组
多元数组。定义和初始化:
var arry [2][3]int arry = [...][3]int{{1,2,3},{7,8,9}} //2表示行数,即有2个{},3表示列数,即{}内的元素
多维数组遍历
建议使用range:
var arry [3][3]int = [3][3]int{{1,2,3},{4,5,6},{7,8,9}} //arry := [3][3]int{{1,2,3},{4,5,6},{7,8,9}} for _,k := range(arry){ for _,o := range(k) { fmt.Println(o) } }
执行结果
1 2 3 4 5 6 7 8 9
- 完整的初始化:
4.2 切片
切片可以看作一个特殊的数组:
- 切片是数组的一个引用,因此切片是引用类型
- 切片的长度可以改变,因此,切片是一个可变的数组
- 切片遍历方式和数组一样,可以用len()求长度
- cap可以求出slice最大的容量,0 <= len(slice) <= (array),其中array是slice引用的数组
切片的定义
格式:==var slice_1 []int==
[]内不添加整数就是一个切片,int是切片内的数据类型,可以为其他数据类型
切片的赋值和初始化
切片的初始化可以直接自己赋值,也可以是切割定义好的数组
//切片的赋值和初始化 arry_s := [...]int{1,0,0,0,0,0} //数组arry_s slice_1 := []int{1,2,3,4,5} //切片赋值 slice_2 := arry_s[0:4] //切割数组给切片赋值 var slice_3 = []string{"a","b","c"} //切片的初始化 fmt.Println(slice_1) fmt.Println(slice_2) fmt.Println(slice_3)
也可以使用make来对切片进行初始化
格式:==*
(5)、map字典
key-value形式的数据结构,有时也叫关联数组。go语言中,声明字典是不会分配内存的,初始化需要make等方法
python中也有字典的定义,在下面的笔记中随时会把python中的字典和go中的字典来进行比较
5.1、字典的定义
字典定义格式
==var <字典名> map[]==
为数据类型,如int、string等
字典定义范例:
var dict_d1 map[int]string //示例:dict_d1[1]="aa" python:{1:'aa',2:'bb'} var dict_d2 map[string]string //示例:dict_d2["a"]="aa" python:{'a':'aa','b':'bb'} var dict_d2 map[string]int //示例:dict_d2["a"]="aa" python:{'a':1,'b':2} var dict_s map[string]map[string]int //示例:dict_d2["a"]["a1"]=1 python:{'a':{'a1':1,'a2':2},'b':{'b1':3,'b2':4}}
5.2、字典的初始化
声明字典是不会分配内存的,初始化需要make等方法。如果定义了字典后,直接赋值是错误的:
字典的初始化
方法一范例:
var dict_d1 = map[string]string{"a":"aaa","b":"bbb"} //var dict_d1 map[string]string = map[string]string{"a":"aaa","b":"bbb"} fmt.Println(dict_d1)
方法二范例:
var dict_d1 map[string]string dict_d1 = make(map[string]string,<N>) //<N>为字典长度,可以省略。建议写上 dict_d1["a"]="aaa" dict_d1["b"]="bbb" fmt.Println(dict_d1)
方法二用来在函数中存储数据,比较常用
与python的字典初始化对比
比较复杂的初始化比对
python:
dict_d1 = {'a':{'a1':1,'a2':2},'b':{'b1':3,'b2':4}}
golang:
var dict_d1 map[string]map[string]int dict_d1 = make(map[string]map[string]int) dict_d1["a"] = make(map[string]int) dict_d1["a"]["a1"]=1 dict_d1["a"]["a2"]=2 dict_d1["b"] = make(map[string]int) dict_d1["b"]["b1"]=3 dict_d1["b"]["b2"]=4 //或 var dict_d1 map[string]map[string]int = map[string]map[string]int{"a":{"a1":1,"a2":2},"b":{"b1":3,"b2":4}}
综上:静态语言go的字典定义和初始化对比python动态语言的字典初始化来说,比较复杂
5.3、字典的常用操作
插入和更新
字典的插入和更新:
var dict_d1 = map[string]int{"a1":1,"a2":2} //插入 dict_d1["a3"]=3 //更新 dict_d1["a1"]=0 fmt.Println(dict_d1)
执行结果
map[a1:0 a2:2 a3:3]
==查找==
查找字典是否存在某个key,使用类似遍历的方式来判断。语法格式:
value,flage = <字典>[key]
如果key存在,ok为true。val为key对应的value值var dict_d1 = map[string]string{"a":"a1","b":"b2"} value,flage := dict_d1["b"] if flage { fmt.Printf("key is alive , value is %s",value) } else { fmt.Println("key is not exsit") }
执行结果
key is alive , value is b2
字典的key查找常用范例:
'''有一个存储用户信息的字典,格式为{"chen":{"password":"123","ran":"level1","is_vip":"1"},"lin":{"password":"1aa","ran":"level2","is_vip":"0"}}''' //查找并修改或添加用户信息(初始化) func person_manage_initialization(dict map[string]map[string]string,s_name string) { val,fla := dict[s_name] //查找是否有名称为s_name变量值的用户 //修改用户的信息 if fla { val["password"] = "a1234" val["ran"] = "level9" } else { dict[s_name] = make(map[string]string) dict[s_name]["password"] = "a123" dict[s_name]["ran"] = "level2" } } //主函数 func main () { var test_dict = map[string]map[string]string{"chen":{"password":"123","ran":"level1","is_vip":"1"},"lin":{"password":"1aa","ran":"level2","is_vip":"0"}} //初始化用户chen和Wang的信息 person_manage_initialization(test_dict,"chen") person_manage_initialization(test_dict,"Wang") fmt.Println(test_dict["chen"]) fmt.Println(test_dict["Wang"]) }
执行结果
map[password:a1234 ran:level9 is_vip:1] map[password:a123 ran:level2]
遍历
和遍历数组、切片的方法一样,使用range关键字。前面一个值为下标,后面一个值为元素。如下面范例所示
var test_dict = map[string]map[string]string{"chen":{"password":"123","ran":"level1","is_vip":"1"},"lin":{"password":"1aa","ran":"level2","is_vip":"0"}} for key,value := range(test_dict) { fmt.Printf("key is %s ,value is %s\n",key,value) }
执行结果
key is chen ,value is map[password:123 ran:level1 is_vip:1] key is lin ,value is map[password:1aa ran:level2 is_vip:0]
遍历
使用内置函数delete删除字典内的元素,格式:
delete(dict,key)
完全删除字典?直接重写分配字典空间
var dict = map[string]int {"a":1,"b":2} //完全删除dict dict=make(map[string]int) //重写分配字典dict的空间,得到一个空字典
求长度
len(dict)
==综上:==
map的操作表
操作 | 代码范例 |
---|---|
插入或更新 | dict[key]=value |
查找key_s | value,flage := dict[key_s] |
遍历 | key,value := range(dict){…} |
删除 | delete(dict,key) |
完全删除 | dict=make(map[]) |
求长度 | len(dict) |
- ### 5.4、字典的其他操作
字典元素
切片或数组中包含字典
//定义
var li []map[string]string
//初始化
var li = []map[string]string{{"a":"b","c":"d"}}
//使用make初始化
li = make([]map[string]string)
li[0]= make(map[string]string)
字典的排序
go的字典时无序集合,排序只有先排序key,然后根据排序好的key来遍历字典即可
func test_map_snort(){
var test_dict = map[string]string{"a2":"2","a1":"1","a5":"5","a4":"4"}
var li_key = []string{}
//li_key := make([]string,100)
for k,_ := range(test_dict){
li_key=append(li_key,k)
}
sort.Strings(li_key[:]) //排序
//排序后遍历字典
for _,k := range(li_key){
fmt.Println(test_dict[k])
}
fmt.Println(li_key)
}
字典的反转
创建另外一个字典dict2,将字典dict与dict2的key和value互换即可
### ==My Tips==
至此,数组/切片和字典2个数据结构的基本介绍就结束了,由于之前一直用的是python语言开发,对于go在创建字典和数组/切片感觉步骤较多,要使用make分配空间,而且还要先确定空间长度
于是,想到了一个稍微简单点的方法来创建:
var li = []string{}
var dict = map[string]string{}
创建空的切片和字典, 这样和python的
li=[]
dict={}
看起来相近,只是不知道这种方式会有多少坑了–!
Python | Golang |
---|---|
li=[] | var li = []string{} |
dict={} | var dict = map[string]string{} |
(6)、go中的包
6.1、包的基本介绍
- 1.golang目前有150个标准的包,覆盖了几乎所有的基础库
- 2.golang.org有所有包的文档,可以在官网上查看,包括包内的方法,和包方法的使用。中文文档地址:https://studygolang.com/pkgdoc
go的官网为(需要梯子):https://golang.org/
6.2、go的“线程同步”包
由于go语言具有相当好的并发性(一个go关键字就是一个协程) , 那么很容易出现多个线程访问一个资源的情况,当多个线程同时修改一个公共资源时(全局变量或者函数传递的参数)
go会出现数据竞争“data race”的情况,比如多个并发线程共同修改全局变量a,会导致最终变量a的值每次执行程序都不确定。为了解决“data race”的情况,需要使用go的线程锁机制,可以使一个线程修改共享资源时,禁止其他线程修改。
线程锁的定义和使用,在go的 sync 包里面
==线程锁一般在多个并发线程访问公共资源的时候才使用==
go的线程锁分为 “互斥锁” 和 “读写锁”
互斥锁
当有一个线程访问公共资源时,其他线程无法访问,知道访问结束。
互斥锁的声明和使用格式:
import "sync" //线程锁包 var lock sync.Mutex //声明了一个互斥锁 lock.Lock //线程互斥锁 锁定,此时只有当前线程可以执行接下来的语句(通常是资源的访问) lock.Unlock //线程互斥锁 解锁。访问结束后,使用
互斥锁的使用范例
func sync_lock_exa(){ var lock sync.Mutex //互斥锁 rand.Seed(time.Now().Unix()) int_i := rand.Intn(63) var li = []string{"a","6","8","er"} for i:=0;i<3;i++{ go func(a []string){ lock.Lock() a[3]=string(int_i) //线程并发修改切片li,因此需要互斥锁 lock.Unlock() }(li) } lock.Lock() fmt.Println(li) //主线程与其他并发线程同时访问li切片,也需要互斥锁。主线程这里是读取切片,而并发线程是修改 lock.Unlock() }
上述范例中,有2地方需要线程锁,一个是通过for循环加上go关键字,开启了多个线程并发执行匿名函数从而同时修改传入的切片li。另外一个地方是主线程在终端输出时,也访问了
切片li,而同时go开启并发线程正在修改切片li,发生了 data race 也需要线程锁。范例中使用互斥锁来解决主线程和并发线程的数据竞争,当并发线程在修改时,主线程不能读取切片内的数据,同理主线程读取切片数据时,并发线程也不能修改切片数据。这样互斥锁在
数据读取时就显得效率非常的低下:假如有100个线程同时读取切片li,那么一个读的线程需要等待写的并发线程和其他的读并发线程结束才会开始读。互斥锁简而言之就是: 写的时候不能读,写的时候不能写,读的时候不能写,读的时候不能读
读写锁
读写锁作用在于当有一个线程对公共资源进行写操作时,其他线程不能对它的公共资源进行任何访问操作,但是当线程对公共资源进行读操作时,其他并发线程仍然可以对它的公共资源进行读操作。
官方解释:读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。
简而言之:写的时候不能读,写的时候不能写,读的时候不能写, 读的时候可以读
读写锁的声明和使用格式:
import "sync" //线程锁包 var rwlock sync.RWMutex //声明了一个读写锁 //读操作时的使用 rwlock.Lock() <公共资源的写操作> rwlock.Unlock() //写操作时的使用 rwlock.RLock() <公共资源的读操作> rwlock.RUlock()
读写锁的使用范例
func sync_rwlock_exa(){ var rwlock sync.RWMutex //读写锁 rand.Seed(time.Now().Unix()) int_i := rand.Intn(63) var li = []string{"a","6","8","er"} for i:=0;i<3;i++{ go func(a []string){ rwlock.Lock() a[3]=string(int_i) //使用读写锁来锁定切片的写操作 rwlock.Unlock() }(li) } for i:=0;i<300;i++{ go func(a []string){ rwlock.RLock() fmt.Println(a) //使用读写锁来进行切片li的读操作 rwlock.RUnlock() }(li) } }
上述读写锁范例在切片li的读方面比互斥锁性能要高100倍左右,应为读写锁并行线程的读操作可以同时进行,而互斥锁只能一个一个进行。因此: ==读写锁适用于读多写少的公共资源访问情况,互斥锁适用于写多读少的公共资源访问情况==
6.1、go的第三方包的安装
GO语言提供了一个go get的命令用来下载和安装第三方库的功能,命令格式为: *go get
命令中的为代码项目网址(不是项目下载地址),比如 :
github.com/go-sql-driver/mysql
go get 下载的安装包默认安装在 GOPATH 环境变量的src下。路径为:
GOPATH/src/<网址名称>/<项目包名称>/<包名称>
在其他go程序import引用时,需要写上的加载的第三方包的路径为<网址名称>/<项目包名称>/<包名称>