GO中的slice使用簡(jiǎn)介(源碼分析slice)
slice表示切片(分片),例如對(duì)一個(gè)數(shù)組進(jìn)行切片,取出數(shù)組中的一部分值。在現(xiàn)代編程語(yǔ)言中,slice(切片)幾乎成為一種必備特性,它可以從一個(gè)數(shù)組(列表)中取出任意長(zhǎng)度的子數(shù)組(列表),為操作數(shù)據(jù)結(jié)構(gòu)帶來(lái)非常大的便利性,如python、perl等都支持對(duì)數(shù)組的slice操作,甚至perl還支持對(duì)hash數(shù)據(jù)結(jié)構(gòu)的slice。
但Go中的slice和這些語(yǔ)言的slice不太一樣,前面所說(shuō)的語(yǔ)言中,slice是一種切片的操作,切片后返回一個(gè)新的數(shù)據(jù)對(duì)象。而Go中的slice不僅僅是一種切片動(dòng)作,還是一種數(shù)據(jù)結(jié)構(gòu)(就像數(shù)組一樣)。
GO-slice詳解
簡(jiǎn)介
slice(切片)是go中常見(jiàn)和強(qiáng)大的類型,這篇文章不是slice使用簡(jiǎn)介,從源碼角度來(lái)分析slice的實(shí)現(xiàn),slice的一些迷惑的使用方式,同時(shí)也講清楚一些問(wèn)題。
slice的底層實(shí)現(xiàn)是數(shù)組,是對(duì)數(shù)組的抽象
官方有slice相關(guān)的博客
https://go.dev/blog/slices-introhttps://go.dev/blog/slices
slice分析
數(shù)據(jù)結(jié)構(gòu)
源碼:https://github.com/golang/go/blob/master/src/runtime/slice.go#L14
slice結(jié)構(gòu)如下:
type slice struct { array unsafe.Pointer // 數(shù)組指針 len int //切片長(zhǎng)度 cap int // 切片容量 }
array:為是底層數(shù)組的指針
len:切片中已有元素的個(gè)數(shù)
cap:底層數(shù)組的長(zhǎng)度
原理概述:
切片的底層實(shí)現(xiàn)是數(shù)組,len是切片的個(gè)數(shù),cap是底層數(shù)組的長(zhǎng)度,當(dāng)往切片中追加元素的時(shí)候,len++,如果len>cap,就會(huì)觸發(fā)切片擴(kuò)容,擴(kuò)容邏輯是算一個(gè)新的cap,并且創(chuàng)建一個(gè)新的底層數(shù)組,將原來(lái)的數(shù)據(jù)copy過(guò)來(lái)。并且創(chuàng)建一個(gè)新的切片(slice)。
可以從一個(gè)切片中創(chuàng)建一個(gè)新的切片,底層數(shù)組是同用的,修改切片元素的時(shí)候會(huì)影響到新的切片。
如果所示:
var s = make([]byte,5)
對(duì)應(yīng)的邏輯是,創(chuàng)建一個(gè)長(zhǎng)度和容量為5的數(shù)組,如果所示
切片的創(chuàng)建方式
先看切片的創(chuàng)建方式,說(shuō)這個(gè)問(wèn)題之前,先看看切片的創(chuàng)建方式
聲明
var vocabList []uint64
聲明了一個(gè)[]uint64類型的切片,vocabList為切片的0值,
這得說(shuō)一下go中nil
值
在 Go 中,nil 是指針、接口、映射、切片、通道和函數(shù)類型的零值,表示未初始化的值。
具體的可以看:https://go101.org/article/nil.html
回到代碼,這表示nil值,它的len和cap都是0,和nil比較結(jié)果為true,這里要說(shuō),nil值對(duì)應(yīng)的具體的類型是在上下文中編譯器推導(dǎo)出來(lái)的
package main func main() { var vocabList []uint64 println(vocabList == nil) } // output: // true
通過(guò)new創(chuàng)建
var vocabList = *new([]uint64)
new是內(nèi)建函數(shù),用來(lái)分配指定類型的內(nèi)容,返回指向內(nèi)存地址的指針,并且給此地址分配此類型的0值。
字面量創(chuàng)建
var vocabList = []uint64{1,2,3,4}
make
var vocabList = make([]uint64,10)
make接受三個(gè)參數(shù),在創(chuàng)建的時(shí)候指定類型,長(zhǎng)度,容量,不指定容量,默認(rèn)和長(zhǎng)度一樣
代碼如下:
func main() { var vocabList = make([]uint64,10) fmt.Printf("slice:%v,len:%d,cap:%d",vocabList,len(vocabList),cap(vocabList)) } // output: // slice:[0 0 0 0 0 0 0 0 0 0],len:10,cap:10
從切片或數(shù)組截取
var vocabList = resList[3:5]
兩個(gè)切片公用一個(gè)底層數(shù)組,但如果新創(chuàng)建的切片擴(kuò)容了,就不共用了。
問(wèn)題分析
主要分析幾個(gè)問(wèn)題
nil切片和空切片的差異
nil切片是通過(guò) new 和聲明方式創(chuàng)建的切片,go會(huì)給他們nil值,如下面的代碼所示:
var vocabList []int vocabList == nil //true
空切片是通過(guò)make,字面量方式創(chuàng)建的長(zhǎng)度為0的切片,
vocabList := make([]int,0) vocabList == nil // false vocabList1 = []int{} vocabList1 == nil //false
具體可以看這篇文章:http://chabaoo.cn/jiaoben/288490px1.htm
我下面的代碼和內(nèi)容來(lái)于這篇文章
通過(guò)unsafe.Pointer
可以將對(duì)應(yīng)地址中的數(shù)據(jù)轉(zhuǎn)換為任何符合go中類型的變量
可以看到,空切片的是有底層數(shù)組的,并且底層數(shù)組都一樣,其實(shí)也可以說(shuō)空切片執(zhí)行了一個(gè)指定的地址空間,
這個(gè)地址空間在源碼中有定義,當(dāng)分配的大小為0的時(shí)候會(huì)返回這個(gè)地址,要說(shuō)明的是這個(gè)地址空間不是固定的,不是寫死的一個(gè)數(shù),在不同的機(jī)器上運(yùn)行會(huì)有不同的值。
源碼:https://github.com/golang/go/blob/master/src/runtime/malloc.go#L948
兩者的差異:
嵌套在結(jié)構(gòu)體中不容易發(fā)現(xiàn)
package main type Word struct { SenseIds []int } func main() { word := Word{} println(word.SenseIds == nil) //true word1 := Word{ SenseIds: make([]int,0), } println(word1.SenseIds == nil) //false }
json序列化
package main import "encoding/json" type Word struct { SenseIds []int `json:"sense_ids" ` } func main() { word := Word{} marshal, err := json.Marshal(word) if err != nil { return } println(string(marshal)) //{"sense_ids":null} word1 := Word{ SenseIds: []int{}, } marshal1, err := json.Marshal(word1) if err != nil { return } println(string(marshal1)) //{"sense_ids":[]} }
這個(gè)問(wèn)題我深有體會(huì)
在做一個(gè)需求的時(shí)候,看到編輯器報(bào)黃色提示,提示我將 var a = []int{}
改為var a []int
,當(dāng)然,go官方也是這么建議的。我就改了,然后一個(gè)接口就報(bào)錯(cuò)了。給我一頓找,發(fā)現(xiàn)json返回了null。
除此之外,沒(méi)有別的區(qū)別。
切片共用底層數(shù)組
在做截取的時(shí)候,會(huì)創(chuàng)建一個(gè)新的slice,截取語(yǔ)法如下
bSlice := aSlice[start:stop:capacityIndex] // satrt <= stop <= capacityIndex //capacityIndex不是必須的,默認(rèn)=原來(lái)切片的cap-startIndex // 如果指定 新切片的容量為 capacityIndex-start
如圖所示:
有了上面的例子,可以看如下代碼
package main import ( "fmt" ) func main() { slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} s1 := slice[2:5] fmt.Println("============= 1 ==============") fmt.Printf("%v len:%d cap:%d \n",slice,len(slice),cap(slice)) fmt.Printf("%v len:%d cap:%d \n",s1,len(s1),cap(s1)) s2 := s1[2:6:7] fmt.Println("============= 2 ==============") fmt.Printf("%v len:%d cap:%d \n",slice,len(slice),cap(slice)) fmt.Printf("%v len:%d cap:%d \n",s1,len(s1),cap(s1)) fmt.Printf("%v len:%d cap:%d \n",s2,len(s2),cap(s2)) // 到這里是正確的切片操作,slice,s1,s2通用底層數(shù)組 s2 = append(s2, 100) //s2追加100,此時(shí)s2中l(wèi)en=cap,還沒(méi)有觸發(fā)擴(kuò)容操作 fmt.Println("============= 3 ==============") fmt.Printf("%v len:%d cap:%d \n",slice,len(slice),cap(slice)) fmt.Printf("%v len:%d cap:%d \n",s1,len(s1),cap(s1)) fmt.Printf("%v len:%d cap:%d \n",s2,len(s2),cap(s2)) s2 = append(s2, 200)// 200加不進(jìn)去了,觸發(fā)擴(kuò)容操作,此時(shí)s2的底層數(shù)組和s1,slice不一樣了 fmt.Println("============= 3 ==============") fmt.Printf("%v len:%d cap:%d \n",slice,len(slice),cap(slice)) fmt.Printf("%v len:%d cap:%d \n",s1,len(s1),cap(s1)) fmt.Printf("%v len:%d cap:%d \n",s2,len(s2),cap(s2)) s1[2] = 20 // s1和slice底層數(shù)組還是一樣的 fmt.Println("============= 3 ==============") fmt.Printf("%v len:%d cap:%d \n",slice,len(slice),cap(slice)) fmt.Printf("%v len:%d cap:%d \n",s1,len(s1),cap(s1)) fmt.Printf("%v len:%d cap:%d \n",s2,len(s2),cap(s2)) } 輸出如下: ============= 1 ============== [0 1 2 3 4 5 6 7 8 9] len:10 cap:10 [2 3 4] len:3 cap:8 ============= 2 ============== [0 1 2 3 4 5 6 7 8 9] len:10 cap:10 [2 3 4] len:3 cap:8 [4 5 6 7] len:4 cap:5 ============= 3 ============== [0 1 2 3 4 5 6 7 100 9] len:10 cap:10 [2 3 4] len:3 cap:8 [4 5 6 7 100] len:5 cap:5 ============= 3 ============== [0 1 2 3 4 5 6 7 100 9] len:10 cap:10 [2 3 4] len:3 cap:8 [4 5 6 7 100 200] len:6 cap:10 ============= 3 ============== [0 1 2 3 20 5 6 7 100 9] len:10 cap:10 [2 3 20] len:3 cap:8 [4 5 6 7 100 200] len:6 cap:10
源碼分析
make創(chuàng)建切片
使用dlv或者go提供的匯編工具可以看到 make調(diào)用了什么函數(shù)
源碼:https://github.com/golang/go/blob/master/src/runtime/slice.go#LL88C18-L88C18
切片的擴(kuò)容規(guī)則
版本不同,擴(kuò)容規(guī)則可能不一樣
,例子中g(shù)o版本為:
代碼如下:
package main import ( "fmt" "unsafe" ) func main() { ints := make([]int, 0) // 創(chuàng)建了一個(gè)長(zhǎng)度為0的切片 i := *(*[3]int)(unsafe.Pointer(&ints)) // 利用Pointer將slice轉(zhuǎn)換為長(zhǎng)度為3的int數(shù)組,此操作可以查看slice結(jié)構(gòu)體中各個(gè)字段的數(shù)值 fmt.Printf("slice1:%v \n",i) fmt.Printf("slice:%v \n",ints) var ints1 = append(ints, 1) // 追加一個(gè)元素 i2 := *(*[3]int)(unsafe.Pointer(&ints1)) fmt.Printf("slice2 %v \n",i2) // slice對(duì)應(yīng)的底層數(shù)組發(fā)生了擴(kuò)容操作,底層數(shù)組已經(jīng)變了 fmt.Printf("slice2:%v \n",ints) }
用dlv 查看它的匯編代碼,看擴(kuò)容操作調(diào)用了那些函數(shù)
源碼鏈接:https://github.com/golang/go/blob/master/src/runtime/slice.go#LL157C10-L157C10
// 函數(shù)入?yún)⒄f(shuō)明如下 //1. et 類型 //2. old 老切片 //3. cap 需要分配的指定容量,為了方便期間,調(diào)用這個(gè)函數(shù)的時(shí)候cap傳遞的都是老的slice的cap func growslice(et *_type, old slice, cap int) slice { if raceenabled { // 是否啟動(dòng)競(jìng)爭(zhēng)檢測(cè) callerpc := getcallerpc() racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, abi.FuncPCABIInternal(growslice)) } if msanenabled { //內(nèi)存檢查,確保沒(méi)有未初始化的內(nèi)存被使用 msanread(old.array, uintptr(old.len*int(et.size))) } if asanenabled { // 檢查內(nèi)存訪問(wèn)是否越界 asanread(old.array, uintptr(old.len*int(et.size))) } if cap < old.cap { panic(errorString("growslice: cap out of range")) } if et.size == 0 { // 正常是不會(huì)這樣的,但為了安全還是處理了0的情況 return slice{unsafe.Pointer(&zerobase), old.len, cap} } // 開(kāi)始計(jì)算新的cap newcap := old.cap doublecap := newcap + newcap // 2倍 if cap > doublecap { // 新的cap要是老的2倍 newcap = cap } else { const threshold = 256 if old.cap < threshold { // cap小于256,newCap為oldCap的兩倍 newcap = doublecap } else { for 0 < newcap && newcap < cap { // Transition from growing 2x for small slices // to growing 1.25x for large slices. This formula // gives a smooth-ish transition between the two. // 這個(gè)公式可以當(dāng)超過(guò)256之后i,可以實(shí)現(xiàn)1.25到2倍的平滑過(guò)渡 newcap += (newcap + 3*threshold) / 4 // 這個(gè)公式化簡(jiǎn)一下 newCap = oldCap*1.25 + 192(3/4*256) } // Set newcap to the requested cap when // the newcap calculation overflowed. if newcap <= 0 { // 防止溢出 newcap = cap } } } // 下面的邏輯是對(duì)上面計(jì)算出來(lái)的newCap來(lái)做對(duì)齊操作,上面的計(jì)算不是真正的結(jié)果,下面還需要做內(nèi)存對(duì)齊操作。 var overflow bool var lenmem, newlenmem, capmem uintptr switch { case et.size == 1: lenmem = uintptr(old.len) newlenmem = uintptr(cap) capmem = roundupsize(uintptr(newcap)) overflow = uintptr(newcap) > maxAlloc newcap = int(capmem) case et.size == goarch.PtrSize: lenmem = uintptr(old.len) * goarch.PtrSize newlenmem = uintptr(cap) * goarch.PtrSize capmem = roundupsize(uintptr(newcap) * goarch.PtrSize) overflow = uintptr(newcap) > maxAlloc/goarch.PtrSize newcap = int(capmem / goarch.PtrSize) case isPowerOfTwo(et.size): var shift uintptr if goarch.PtrSize == 8 { // Mask shift for better code generation. shift = uintptr(sys.Ctz64(uint64(et.size))) & 63 } else { shift = uintptr(sys.Ctz32(uint32(et.size))) & 31 } lenmem = uintptr(old.len) << shift newlenmem = uintptr(cap) << shift capmem = roundupsize(uintptr(newcap) << shift) overflow = uintptr(newcap) > (maxAlloc >> shift) newcap = int(capmem >> shift) default: lenmem = uintptr(old.len) * et.size newlenmem = uintptr(cap) * et.size capmem, overflow = math.MulUintptr(et.size, uintptr(newcap)) capmem = roundupsize(capmem) newcap = int(capmem / et.size) } // The check of overflow in addition to capmem > maxAlloc is needed // to prevent an overflow which can be used to trigger a segfault // on 32bit architectures with this example program: // // type T [1<<27 + 1]int64 // // var d T // var s []T // // func main() { // s = append(s, d, d, d, d) // print(len(s), "\n") // } if overflow || capmem > maxAlloc { panic(errorString("growslice: cap out of range")) } var p unsafe.Pointer // 下面是分配新的數(shù)組 if et.ptrdata == 0 { // 原slice底層數(shù)組為0,也就是nil切片, p = mallocgc(capmem, nil, false) // The append() that calls growslice is going to overwrite from old.len to cap (which will be the new length). // Only clear the part that will not be overwritten. memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem) // 然后使用memclrNoHeapPointers函數(shù)來(lái)清除新分配的內(nèi)存 } else { // Note: can't use rawmem (which avoids zeroing of memory), because then GC can scan uninitialized memory. p = mallocgc(capmem, et, true) // 分配新的底層數(shù)組 if lenmem > 0 && writeBarrier.enabled { // 之前有數(shù)據(jù),并且寫屏障已經(jīng)開(kāi)啟 // Only shade the pointers in old.array since we know the destination slice p // only contains nil pointers because it has been cleared during alloc. bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(old.array), lenmem-et.size+et.ptrdata) } } // copy元素 memmove(p, old.array, lenmem) // 創(chuàng)建新的切片返回 return slice{p, old.len, newcap} }
總結(jié)如下:
- 確定新容量,cap小于256,直接2倍,大于256,新容量=老容量*1.25 * 3/4 * 256
- 用新容量來(lái)做內(nèi)存對(duì)齊操作
- 分配新數(shù)組
- copy數(shù)組
- 創(chuàng)建切片返回
還有一點(diǎn):
append的操作匯編并沒(méi)有調(diào)用函數(shù),在匯編層面就做了,直接往底層數(shù)組添加元素,只有數(shù)組已經(jīng)滿的情況下才會(huì)觸發(fā)擴(kuò)容操作
內(nèi)存對(duì)齊相關(guān)東西之后在說(shuō)
我們來(lái)一個(gè)例子來(lái)驗(yàn)證一下上面的代碼邏輯:
package main import "fmt" func main() { var s = []int{} oldCap := cap(s) for i := 0; i < 2048; i++ { s = append(s, i) newCap := cap(s) if newCap != oldCap { // 追加元素,當(dāng)容量發(fā)生變化的時(shí)候,打印,擴(kuò)容之前的元素,cap,導(dǎo)致擴(kuò)容的元素,和擴(kuò)容之后的cap fmt.Printf("[%d -> %4d] cap = %-4d after append %-4d cap = %-4d\n", 0, i-1, oldCap, i, newCap) oldCap = newCap } } } // outPut [0 -> -1] cap = 0 | after append 0 cap = 1 [0 -> 0] cap = 1 | after append 1 cap = 2 [0 -> 1] cap = 2 | after append 2 cap = 4 [0 -> 3] cap = 4 | after append 4 cap = 8 [0 -> 7] cap = 8 | after append 8 cap = 16 [0 -> 15] cap = 16 | after append 16 cap = 32 [0 -> 31] cap = 32 | after append 32 cap = 64 [0 -> 63] cap = 64 | after append 64 cap = 128 [0 -> 127] cap = 128 | after append 128 cap = 256 // 256之前都是2倍 [0 -> 255] cap = 256 | after append 256 cap = 512 // 1.25 * 256 + 192 = 512 [0 -> 511] cap = 512 | after append 512 cap = 848 // 1.25 * 512 + 192 = 832 [0 -> 847] cap = 848 | after append 848 cap = 1280 [0 -> 1279] cap = 1280 | after append 1280 cap = 1792 [0 -> 1791] cap = 1792 | after append 1792 cap = 2560
copy函數(shù)的使用
copy函數(shù)底層調(diào)用的是
底層數(shù)組共用,那copy函數(shù)就可以完成一下幾種操作
移動(dòng)slice中的元素
func main() { var vocabs = []int{1,2,3,4,5,6,7,8,9} // 現(xiàn)在將5去除掉,將5之后的移動(dòng)到前面 copy(vocabs[4:],vocabs[5:]) fmt.Printf("%v",vocabs) } //outPut [1 2 3 4 6 7 8 9 9]
slice合并
func main() { ints := make([]int, 10) i1 := make([]int,0, 5) for i := 0; i < 5; i++ { i1 = append(i1, i) } i2 := make([]int,0, 5) for i := 5; i < 10; i++ { i2 = append(i2, i) } copy(ints,i1) // 從i1全部復(fù)制到ints中 copy(ints[len(i1):],i2) // 將i2復(fù)制到ints的len(i1)位置開(kāi)始一直到結(jié)束的數(shù)組中 fmt.Printf("%v\n",i1) fmt.Printf("%v\n",i2) fmt.Printf("%v\n",ints) } // output [0 1 2 3 4] [5 6 7 8 9] [0 1 2 3 4 5 6 7 8 9]
長(zhǎng)度不夠的copy,依dist為準(zhǔn)
package main import "fmt" func main() { ints := make([]int, 3) i1 := make([]int,0, 10) for i := 0; i < 10; i++ { i1 = append(i1, i) } copy(ints,i1) fmt.Printf("%v",ints) } //outPut: [0 1 2]
問(wèn)題解答
nil 切片可以添加元素嗎?
可以
nil切片就是切片聲明,追加的時(shí)候切片長(zhǎng)度為0,會(huì)引發(fā)擴(kuò)容操作,擴(kuò)容的時(shí)候會(huì)給分配一個(gè)新的數(shù)組。
nil切片和空切片有區(qū)別嗎?
nil切片有兩種方式
聲明new創(chuàng)建
空切片有兩種:
字面量創(chuàng)建但沒(méi)有任何的元素make創(chuàng)建長(zhǎng)度指定為0
使用方式除了下面兩點(diǎn)沒(méi)有別的區(qū)別:
嵌套結(jié)構(gòu)體,不顯性創(chuàng)建為niljson序列化會(huì)為null
slice擴(kuò)容規(guī)則
說(shuō)到前面:它在確定cap之后有內(nèi)存對(duì)齊操作
小于256,是原cap的2倍大于256,是原來(lái)的1.25倍+3/4*256
到這里就結(jié)束了。
到此這篇關(guān)于GO中的slice詳解的文章就介紹到這了,更多相關(guān)go slice詳解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 詳解Golang如何比較兩個(gè)slice是否相等
- GO語(yǔ)言基本類型String和Slice,Map操作詳解
- Go基礎(chǔ)系列:Go切片(分片)slice詳解
- golang?使用sort.slice包實(shí)現(xiàn)對(duì)象list排序
- 淺談Golang?Slice切片如何擴(kuò)容的實(shí)現(xiàn)
- 淺談Golang 切片(slice)擴(kuò)容機(jī)制的原理
- golang slice元素去重操作
- golang語(yǔ)言如何將interface轉(zhuǎn)為int, string,slice,struct等類型
- Golang中的Slice與數(shù)組及區(qū)別詳解
- Go 中 slice 的 In 功能實(shí)現(xiàn)探索
- Golang slice切片操作之切片的追加、刪除、插入等
相關(guān)文章
Go語(yǔ)言基礎(chǔ)學(xué)習(xí)之?dāng)?shù)組的使用詳解
數(shù)組相必大家都很熟悉,各大語(yǔ)言也都有數(shù)組的身影。Go 語(yǔ)言也提供了數(shù)組類型的數(shù)據(jù)結(jié)構(gòu)。本文就來(lái)通過(guò)一些簡(jiǎn)單的示例帶大家了解一下Go語(yǔ)言中數(shù)組的使用,希望對(duì)大家有所幫助2022-12-12利用systemd部署golang項(xiàng)目的實(shí)現(xiàn)方法
這篇文章主要介紹了利用systemd部署golang項(xiàng)目的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11解決golang編譯提示dial tcp 172.217.160.113:443: con
這篇文章主要介紹了解決golang編譯提示dial tcp 172.217.160.113:443: connectex: A connection attempt failed,此問(wèn)題完美解決,需要的朋友可以參考下2023-02-02go slice 數(shù)組和切片使用區(qū)別示例解析
這篇文章主要為大家介紹了go slice 數(shù)組和切片使用區(qū)別示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01Golang中interface轉(zhuǎn)string輸出打印方法
這篇文章主要給大家介紹了關(guān)于Golang中interface轉(zhuǎn)string輸出打印的相關(guān)資料,在go語(yǔ)言中interface轉(zhuǎn)string可以直接使用fmt提供的fmt函數(shù),文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-02-02Golang異常處理之defer,panic,recover的使用詳解
這篇文章主要為大家介紹了Go語(yǔ)言異常處理機(jī)制中defer、panic和recover三者的使用方法,文中示例代碼講解詳細(xì),需要的朋友可以參考下2022-05-05go語(yǔ)言實(shí)現(xiàn)markdown解析庫(kù)的方法示例
這篇文章主要介紹了go語(yǔ)言實(shí)現(xiàn)markdown解析庫(kù)的方法示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02