一、總體內容
1、內置函數、遞歸函數、閉包
2、數組和切片
3、map數據結構
4、package介紹
一、內置函數
注意:值類型用new來分配內存,引用類型用make來分配內存
1、close:主要用來關閉channel
2、len:用來求長度,比如string、array、slice、map、channel
3、new:用來分配內存,主要用來分配值類型,比如int、struct、浮點型。返回的是指針
代碼案例
package main import( "fmt" ) func main(){ var i int fmt.Println(i) //0 j:=new(int) //返回的是地址,也就是指針 *j=100 //因為是指針,所以要這樣賦值 fmt.Println(*j) //100 } 4、make:用來分配內存,主要用來分配引用類型。比如chan、map、slice 5、append:用來追加元素到數組、slice中 package main import( "fmt" ) func main(){ var a [] int a=append(a,10,20,30) fmt.Println(a) //[10 20 30] } 合並兩個slice package main import( "fmt" ) func main(){ var a [] int a=append(a,10,20,30) a=append(a, a...) //后面的三個點,是展開,這個append是合並 fmt.Println(a) } 注意這里的三個點的作用是展開
6、panic和recover:用來做錯誤處理
panic可以快速定位到哪里出錯了
捕獲異常的原因是因為上線項目之后不能夠隨便的停止,所以要捕獲
package main import( "fmt" "time" ) func test(){ defer func(){ if err:=recover();err !=nil{ //這里捕獲下面的系統的異常 fmt.Println(err) //這里的錯誤沒有指定哪一行出錯了。這里可以把堆棧打印出來 //捕獲了異常之后下面可以繼續寫上報警的接口或者寫到日志里面 } }() b:=0 a:=100/b //這里系統拋了一個異常,上面來捕獲 fmt.Println(a) return } func main(){ for { test() time.Sleep(time.Second) //這里是休息1秒,參數是一個常量 } var a [] int a=append(a,10,20,30) a=append(a, a...) fmt.Println(a) //[10 20 30] } D:\project>go build go_dev/day4/example/example2 D:\project>example2.exe runtime error: integer divide by zero runtime error: integer divide by zero。
還可以自己手動的通過pinic來捕獲異常
package main import( "fmt" "errors" ) func initConfig()(err error){ //這里是命名返回值 return errors.New("init config failed") //初始化error這個實例 } func test(){ err :=initConfig() if err !=nil{ panic(err) //panic手動的捕獲這個異常,這樣就能知道程序的哪里出錯了 } return } func main(){ test() var a [] int a=append(a,10,20,30) a=append(a, a...) fmt.Println(a) //[10 20 30] } D:\project>go build go_dev/day4/example/example2 D:\project>example2.exe panic: init config failed goroutine 1 [running]: main.test() D:/project/src/go_dev/day4/example/example2/main.go:17 +0xb2 main.main() D:/project/src/go_dev/day4/example/example2/main.go:24 +0x3b
數組:
var a [1]int :這個是確定的長度,為1
var a [] int :這個是切片
new和make的區別
new之后如果是slice等引用類型必須要用make初始化一下才可以用如下:
package main import( "fmt" ) func test(){ s1:=new([] int) //new了這個指針,必須要用make來初始化一下 fmt.Println(s1) //這個返回來一個指針&[] s2:=make([] int, 2) //這個后面的2是指定容量 fmt.Println(s2) //這個返回來這個數據類型 [0 0 ] *s1=make([] int,5) //因為s1是一個指針(地址),要想用這個指針必須初始化一下 (*s1)[0]=100 //初始化之后給這個值賦值 fmt.Println(s1) //&[100 0 0 0 0] s2[0]=100 fmt.Println(s2) //[100 0] } func main(){ test() }
遞歸函數
一個函數調用自己,就叫做遞歸
package main package main import ( "fmt" "time" ) func recusive(n int){ fmt.Println("hello") time.Sleep(time.Second) if n>10{ //這個是遞歸退出的條件 return } recusive(n+1) //這個就是遞歸的自己調用自己 } func main(){ recusive(0) }
例子一:計算階乘

package main import ( "fmt" ) func factor(n int) int { if n==1{ return 1 } return factor(n-1)*n } func main(){ a:=factor(5) fmt.Println(a) } D:\project>go build go_dev/day4/example/example5 D:\project>example5.exe 120
斐波那契數列

package main import( "fmt" ) func fabnaqi(n int) int{ if n<=1 { return 1 } return fabnaqi(n-1)+fabnaqi(n-2) } func main(){ for i:=1;i<10;i++{ fmt.Println(fabnaqi(i)) } } D:\project>go build go_dev/day4/example/example6 D:\project>example6.exe 1 2 3 5 8 13 21 34 55
閉包:
閉包:是一個函數和與其相關作用域的結合體
下面的匿名函數中的變量和x綁定了

package main import ( "fmt" ) //閉包 func Adder() func(int)int{ //這里定義的返回值要和下面的匿名函數一樣 //下面的整體才是閉包 var x int //默認為0 return func(d int) int{ //這里匿名函數要和上面返回值類型是一樣 x+=d return x } } func main(){ f:=Adder() fmt.Println(f(1)) //這里的f就是執行Adder(),然后f(1),這個就是執行匿名函數,並且傳入參數 fmt.Println(f(100)) //101 fmt.Println(f(1000)) //1101 }
例2

package main import ( "fmt" "strings" ) func makeSuffixFunc(suffix string) func (string )string{ return func (name string) string{ //這里的閉包的環境變量是和suffix綁定的 if strings.HasPrefix(name,suffix)==false{ return name+suffix } return name } } func main(){ func1:=makeSuffixFunc(".bmp") func2:=makeSuffixFunc(".jpg") fmt.Println(func1("test")) fmt.Println(func2("test")) }
數組
1、數組:是同一種數據類型的固定長度的序列
2、數組定義:var a [len]int 比如:var a [5] int 一旦定義長度就不會變了
3、長度是數組類型的一部分,因此var a[5]int和var a[10]int 是不同的類型
4、數組可以通過下標進行訪問,下標是從零開始的,最后一個元素下標是len-1
遍歷的方法如下:
下面是遍歷數組的兩種方法

package main import ( "fmt" ) func main(){ var a[10] int a[0]=100 for i:=0;i<len(a);i++{ fmt.Println(a[i]) } for _,value:=range a{ fmt.Println(value) } }
5、訪問越界,如果下標在數組合法范圍之外,則觸發越界,會panic
如下,就會在編譯的時候報錯,也就是pinic了

package main import ( "fmt" ) func main(){ var a[10] int j:=10 a[0]=100 a[j]=200 fmt.Println(a) }
6、數組是值類型,因此改變副本的值,不會改變本身的值
在函數里面改變不會改變外部的值,在函數中改變的知識數組的副本
如果要改變函數中的值,需要通過指針的方式來修改

package main import( "fmt" ) func test(arr *[5] int){ (*arr)[0]=1000 } func main(){ var a [5] int test(&a) fmt.Println(a) // [1000 0 0 0 0] }
數組和切片:
1、練習,使用非遞歸的方式實現斐波那契數列,打印前100個數
package main import ( "fmt" ) func fab(n int){ var a[]uint64 a=make([]uint64 ,n) a[0]=1 a[1]=1 for i:=2;i<n;i++{ a[i]=a[i-1]+a[i-2] } for _,v:=range a{ fmt.Println(v) } } func main(){ fab(10) }
2、數組初始化
a、var age0 [5]int =[5]int {1,2,3,4,5]
b、var age1=[5] int {1,2,3,4,5]
c、var age2=[…]int {1,2,3,4,5}
d、var str=[5]string{3:”hello word”,4:”tom”}
3、多維數組
a、var age[5][3] int //5行 3 列,第一個行,第二個列
b、var f[2][3] int=[…][3]int {{1,2,3},{7,8,9}}
遍歷多維數組

package main import ( "fmt" ) func testArry(){ var a[2][5]int=[...][5]int{{1,2,3,4,5},{6,7,8,9,10}} //前面是行,后面是列 for row,v:=range a{ //首先便利梅行 for col,v1:=range v{ //然后遍歷每列 fmt.Printf("(%d,%d)=%d ",row,col,v1) //這里的v1是多維數組的遍歷對象 } fmt.Println() } } func main(){ testArry() }
注意這里的切片和數組的區別:
a、聲明的時候切片沒有長度,數組有長度在中括號中,因為切片是變長的,數組的是長度固定的,切片是引用類型,數組是值類型
1、切片:切片是數組的一個引用,因此切片是引用類型
2、切片的長度可以改變,因此切片是一個可變的數組
3、切片遍歷方式和數組一樣,可以用len()求長度
4、cap可以求出slice最大的容量,0<= len (slice)<=cap (array),其中array是slice引用的數組
5、切片的定義:var變量名[]切片類型,比如var str []string var arr[] int
package main import "fmt" func testSlice(){ var slice [] int var arr[5]int=[...]int{1,2,3,4,5} slice=arr[2:5] //數組的切片 fmt.Println(slice) fmt.Println(len(slice)) //求切片的長度 fmt.Println(cap(slice)) //求切片的容量 slice =slice[0:1] //這個切片引用了另一個切片 fmt.Println(len(slice)) //求新切片的長度 fmt.Println(cap(slice)) //這個還是原來切片的長度 } func main(){ testSlice() } D:\project>go build go_dev/day4/example/slicea D:\project>slicea.exe [3 4 5] 3 3 1 3
方法: 切片初始化只能通過切片的方式
1、切片初始化:var slice [] int=arr[start”end].包含start到end之間的元素名不包含end
2、var slice []int=arr[0:end]可以簡寫為var slice []int=arr[:end]
3、var slice []int=arr[start:len(arr)] 可以簡寫為var slice[]int = arr[start:]
4、var slice []int=arr[0,len(arr)] 可以簡寫為var slice[]int=arr[:]
5、如果要切片最后一個元素去掉,可以這樣寫
slice=slice[:len(slice)-1]
數組和切片定義之后必須要初始化
下面是切片的內存布局 第一個是x的切片布局指針方式指針數組,
上面的第三個切片的地址指向的是數組的第一個元素的地址,如下:

func testslice1(){ var a=[10]int{1,2,3,4,5} b:=a[1:5] //這是一個切片 fmt.Printf("%p\n",b) //打印地址0xc042084008 fmt.Println(&a[2]) //打印地址0xc042084008 } func main(){ testslice1() } 從上面地址可以看到切片地址和第一個元素的地址相同
3、通過make來創建切片
4、用append內置函數操作切片
slice=append(slice,10)
var a=[]int{1,2,3}
var b=[]int{4,5,6}
a=append(a,b…) //這里注意,如果是一個切片的話,append需要后面是…
切片是引用,如果切片的append的大小超過了原來的數組的大小,這個時候就會擴容,可以通過切片和數組的第一個值的地址是否相同來比較

package main import ( "fmt" ) func test(){ var a [2]int=[...]int{1,2} s:=a[1:] fmt.Println(s,&a[1]) s=append(s,10) s=append(s,11) fmt.Printf("%p\n",s) //0xc04200e2a0 fmt.Println(s,&a[1]) //0xc0420080d8 } func main(){ test() }
上面切片的地址和數組的第一個元素的地址不同可以看到,這個切片進行了擴容的方式,新開了一塊內存和原來的內存不一樣了,這個是內部的算法決定的
5、切片遍歷
for index,val :range slice{
}
6、切片resize,切片之后可以再進行切片
var a=[] int{1,2,3}
b:=a[1:2]
b=b[0:3]
7、切片的拷貝
s1:=[]int{1,2,3,4}
s2:=make([]int ,10)
copy(s2,s1)
s3:=[]int{1,2,3}
s3=append(s3,s2…)
s3=append(s3,4,5,6)
8、stringhe slice
string底層就是一個byte的數組,因此,也可以進行切片操作

package main import ( "fmt" ) func test(){ s:="hello world" s1:=s[0:5] fmt.Println(s1) //hello } func main(){ test() }
10、如何改變string中的字符串?
string本身是不可變的,因此要改變string中字符,需要如下操作
下面是針對沒有中文的情況下:

func test2(){ str:="hello world" s:=[]byte(str) s[0]='o' str=string(s) fmt.Println(str) // oello world } func main(){ test2() } 下面是可以針對中文的情況 func testModify(){ str:="oello world" s:=[]rune(str) s[0]='h' str=string(s) fmt.Println(str) //hello world } func main(){ testModify() }
11、排序和查找操作
排序操作主要在sort包中,導入就可以使用了
import(“sort”)
sort.Ints對整數進行排序,sort.String對字符串進行排序,sort.Float64s對浮點數進行排序
sort.SearchInts(a []int,b int)從數組a中查找b,前提是a必須有序
sort.SearchFloats(a []float64 ,b float64)從數組a中查找b,前提是a必須有序
sort.SearchStrings(a []string,bstring)從數組a中查找b,請安提是a必須有序
下面是對數組進行排序

package main import( "fmt" "sort" ) func test(){ var a=[...]int{1,3,2,21,11,14} //因為這個是數組,所以不能直接排序 sort.Ints(a[:]) //所以這里要針對切片進行排序,因為切片實際引用類型 fmt.Println(a) } func main(){ test() } /* D:\project>go build go_dev/day4/example/example15 D:\project>example15 [1 2 3 11 14 21] */ 下面是對字符串進行排序 func testString(){ var a=[...]string{"abc","efg","b","A","eee"} sort.Strings(a[:]) fmt.Println(a) //[A abc b eee efg] } func main(){ testString() } 下面是對浮點型進行排序 func testFloat(){ var a=[...]float64{1.1,9.7,2.1} sort.Float64s(a[:]) fmt.Println(a) //[1.1 2.1 9.7] } func main(){ testFloat() } 下面是排序的方法 func testIntsearch(){ var a=[...]int{1,8,38,2,348,4} sort.Ints(a[:]) index:=sort.SearchInts(a[:],2) fmt.Println(index) //得到下標為1 } func main(){ testIntsearch() }
map數據結構
一、簡介
key-value的數據結構
a、聲明,聲明是不會分配內存的,初始化需要make
var map1 map[key type]value type
var a map[string]string
var a map[string]int
var a map[int]string
var a map[string]map[string]string

package main import ( "fmt" ) func test(){ var a map[string]string //聲明map,但是這里沒有申請內存空間,所以必須初始化 a=make(map[string]string,10) //這里初始化,申請內存空間 a["abc"]="efg" fmt.Println(a) //map[abc:efg] } func main(){ test() } 方式二 package main import ( "fmt" ) func test(){ a:=make(map[string]string,10) //這里聲明加上初始化,聲明然后分配內存空間 a["abc"]="efg" fmt.Println(a) //map[abc:efg] } func main(){ test() } 方法三:不推薦 package main import ( "fmt" ) func test(){ var a map[string]string=map[string]string{ "key":"value", } //var a map[string]string //聲明map,但是這里沒有申請內存空間,所以必須初始化 //a:=make(map[string]string,10) //這里初始化,申請內存空間 a["abc"]="efg" fmt.Println(a) //map[abc:efg] } func main(){ test() } 多層map嵌套 func testMap2(){ a:=make(map[string]map[string]string,100) //聲明 a["key1"]=make(map[string]string) //初始化 a["key1"]["key2"]="abc" a["key1"]["key3"]="abc" fmt.Println(a) } func main(){ testMap2() test() }
map相關的操作
a[“hello”]=”world” 插入和更新
val,ok:=a[“hello”] 查找
for k,v:=range a { 遍歷
}
delete (a,”hello”) 刪除
len(a) 長度

func test3() { var a map[string]string = map[string]string{"hello": "world"} a = make(map[string]string, 10) a["hello"] = "world" //插入和更新 val,ok:=a["hello"] //查找 if ok{ fmt.Println(val) } for k,v :=range a { //遍歷 fmt.Println(k,v) } } func main(){ test3() }
排序:
map排序,map的排序是無序的:
a、先獲取所有key,把key進行排序
b、按照排好的key,進行遍歷

package main import( "fmt" "sort" ) func test(){ var a map[int]int //聲明 a=make(map[int]int,5) //初始化,加入內存 a[8]=10 a[3]=11 a[2]=10 a[1]=10 a[18]=10 var keys []int //創建一個切片 for k,_:=range a{ keys=append(keys,k) } sort.Ints(keys) for _,v:=range keys{ fmt.Println(v,a[v]) } } func main(){ test() } //D:\project>go build go_dev/day4/example/example17 // //D:\project>example17.exe //1 10 //2 10 //3 11 //8 10 //18 10 上面思路,由於map是無序的,這里創建一個切片,然后根據切片的方法進行排序
map反轉
初始化另外一個map把key、value呼喚即可
package main import ( "fmt" ) func test(){ var a map[string] int var b map[int] string a=make(map[string]int,5) b=make(map[int]string,5) a["abc"]=1 a["efg"]=2 for k,v:=range a{ b[v]=k } fmt.Println(a) //map[abc:1 efg:2] fmt.Println(b) //map[1:abc 2:efg] } func main(){ test() } 原理:這里首先聲明兩個map,並且初始化,然后進行遍歷第一個map,然后第二個map直接添加第一個map的v和k即可
包
1、golang中的包
a、golang目前有150個標准的包,覆蓋了幾乎所有的基礎庫
b、golang.org有所有包的文檔,沒事就翻翻
2、線程同步
a、import(“sync”)
b、互斥鎖 var mu sync.Mutex,同一時間只能有一個goroute能進去
c、讀寫鎖,var mu sync.RWMutex
線程和協程,只有讀操作的時候不用加鎖
1、如果有寫操作,需要加鎖
2、如果有讀寫的操作,需要加鎖
3、如果只有讀的操作,不需要加鎖
編譯的時候—race可以查看是否有競爭
下面是互斥鎖的程序

import ( "fmt" "sync" "math/rand" "time" ) //因為這是一個讀寫的操作,所以讀的時候也要加鎖 var lock sync.Mutex func test(){ var a map[int] int a=make(map[int]int,5) a[8]=10 for i :=0;i<2;i++{ go func(b map [int]int){ //下面是寫的操作,所以要加鎖 lock.Lock() //加鎖 b[8]=rand.Intn(100) lock.Unlock() //解鎖 }(a) } lock.Lock() //這是讀操作,因為程序中有讀寫,所以加鎖 fmt.Println(a) time.Sleep(time.Second) lock.Unlock() //解鎖 } func main(){ test() } D:\project>go build --race go_dev/day4/example/packagea 這里—race是檢測是否有競爭 D:\project>packagea.exe //因為有race所以下面檢測沒有競爭 map[8:81] 上面是互斥鎖 應用場景: 寫比較多,有少量的讀。(寫多讀少) 加鎖后,任何其他試圖再次加鎖的線程會被阻塞,直到當前進程解鎖 如果解鎖時有一個以上的線程阻塞,那么所有該鎖上的線程都被編程就緒狀態, 第一個變為就緒狀態的線程又執行加鎖操作,那么其他的線程又會進入等待。 在這種方式下,只有一個線程能夠訪問被互斥鎖保護的資源。 互斥鎖無論讀還是寫同一時間只有一個協程在執行
讀寫鎖:
實際就是一種特殊的自旋鎖,它把共享資源的訪問者划分為讀者和寫者,讀者只對共享資源進行讀訪問,寫者則需要對共享資源進行寫操作
使用場景:
寫比較少,有大量的讀 (讀多寫少)
寫的時候只有一個goroute去寫,但是讀的時候所有的goroute去讀
讀寫鎖有三種狀態:讀加鎖狀態、寫加鎖狀態和不加鎖狀態
一次只有一個線程可以占有寫模式的讀寫鎖,但是多個線程可以同時占有讀模式的讀寫鎖。(這也是它能夠實現高並發的一種手段)
當讀寫鎖在寫加鎖模式下,任何試圖對這個鎖進行加鎖的線程都會被阻塞,直到寫進程對其解鎖。
當讀寫鎖在讀加鎖模式先,任何線程都可以對其進行讀加鎖操作,但是所有試圖進行寫加鎖操作的線程都會被阻塞,直到所有的讀線程都解鎖。
所以讀寫鎖非常適合對數據結構讀的次數遠遠大於寫的情況

package main import ( "fmt" "sync" "math/rand" "time" ) var rwlock sync.RWMutex //讀寫鎖的定義 func test(){ var a map[int] int a=make(map[int]int,5) //var count int32 a[8]=10 for i:=0;i<2;i++{ go func(b map[int]int){ rwlock.Lock() b[8]=rand.Intn(100) rwlock.Unlock() }(a) } for i:=0;i<100;i++{ go func(b map[int]int){ rwlock.RLock() //加讀寫鎖 fmt.Println(a) rwlock.RUnlock() //解鎖 }(a) } time.Sleep(time.Second*3) } func main(){ test() }
原子操作:atomic 包 在sync下面
https://studygolang.com/pkgdoc
這是串行的操作

package main import ( "fmt" "sync" "math/rand" "time" "sync/atomic" ) var rwlock sync.RWMutex //讀寫鎖的定義 func test(){ var a map[int] int a=make(map[int]int,5) var count int32 a[8]=10 for i:=0;i<2;i++{ go func(b map[int]int){ rwlock.Lock() b[8]=rand.Intn(100) rwlock.Unlock() }(a) } for i:=0;i<100;i++{ go func(b map[int]int){ for { rwlock.RLock() //加讀寫鎖 fmt.Println(a) rwlock.RUnlock() //解鎖 atomic.AddInt32(&count, 1) //原子操作進行計數 } }(a) } time.Sleep(time.Second*3) fmt.Println(atomic.LoadInt32(&count)) //原子操作進行讀出來529500 次 } func main(){ test() }
比較讀寫鎖和互斥鎖的性能

下面是讀寫鎖 package main import ( "fmt" "sync" "math/rand" "time" "sync/atomic" ) var rwlock sync.RWMutex //讀寫鎖的定義 func test(){ var a map[int] int a=make(map[int]int,5) var count int32 a[8]=10 for i:=0;i<2;i++{ go func(b map[int]int){ rwlock.Lock() b[8]=rand.Intn(100) time.Sleep(time.Microsecond*10) rwlock.Unlock() }(a) } for i:=0;i<100;i++{ go func(b map[int]int){ for { rwlock.RLock() //加讀寫鎖 time.Sleep(time.Microsecond) rwlock.RUnlock() //解鎖 atomic.AddInt32(&count, 1) //原子操作進行計數 } }(a) } time.Sleep(time.Second*3) fmt.Println(atomic.LoadInt32(&count)) //251196 } func main(){ test() } 下面是互斥鎖 package main import ( "fmt" "sync" "math/rand" "time" "sync/atomic" ) var lock sync.Mutex //讀寫鎖的定義 func test(){ var a map[int] int a=make(map[int]int,5) var count int32 a[8]=10 for i:=0;i<2;i++{ go func(b map[int]int){ lock.Lock() b[8]=rand.Intn(100) time.Sleep(time.Microsecond*10) lock.Unlock() }(a) } for i:=0;i<100;i++{ go func(b map[int]int){ for { lock.Lock() //rwlock.RLock() //加讀寫鎖 time.Sleep(time.Microsecond) //rwlock.RUnlock() //解鎖 lock.Unlock() atomic.AddInt32(&count, 1) //原子操作進行計數 } }(a) } time.Sleep(time.Second*3) fmt.Println(atomic.LoadInt32(&count)) //2460次 } func main(){ test() } 可以看到讀寫鎖的性能是互斥鎖的性能的100倍! 讀寫鎖:讀的時候可以有多個協程在操作 互斥鎖:無論讀還是寫,只有一個協程在執行
go get安裝第三方包
go get后面跟上github地址