網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
1 Go數(shù)組
Go數(shù)組是值類型,數(shù)組定義的時(shí)候就需要指定大小,不同大小的數(shù)組是不同的類型,數(shù)組大小固定之后不可改變。數(shù)組的賦值和傳參都會(huì)復(fù)制一份。
func main() { arrayA := [2]int{100, 200} var arrayB [2]int arrayB = arrayA fmt.Printf("arrayA : %p , %v\n", &arrayA, arrayA) fmt.Printf("arrayB : %p , %v\n", &arrayB, arrayB) testArray(arrayA) } func testArray(x [2]int) { fmt.Printf("func Array : %p , %v\n", &x, x) }
結(jié)果:
arrayA : 0xc4200bebf0 , [100 200]
arrayB : 0xc4200bec00 , [100 200]
func Array : 0xc4200bec30 , [100 200]
可以看到,三個(gè)內(nèi)存地址都不同,這也就驗(yàn)證了 Go 中數(shù)組賦值和函數(shù)傳參都是值復(fù)制的。尤其是傳參的時(shí)候把數(shù)組復(fù)制一遍,當(dāng)數(shù)組非常大的時(shí)候會(huì)非常消耗內(nèi)存。可以考慮使用指針傳遞。
指針傳遞有個(gè)不好的地方,當(dāng)函數(shù)內(nèi)部改變了數(shù)組的內(nèi)容,則原數(shù)組的內(nèi)容也改變了。
因此一般參數(shù)傳遞的時(shí)候使用slice
2 切片的數(shù)據(jù)結(jié)構(gòu)
切片本身并不是動(dòng)態(tài)數(shù)組或者數(shù)組指針。它內(nèi)部實(shí)現(xiàn)的數(shù)據(jù)結(jié)構(gòu)通過(guò)指針引用底層數(shù)組,設(shè)定相關(guān)屬性將數(shù)據(jù)讀寫操作限定在指定的區(qū)域內(nèi)。切片本身是一個(gè)只讀對(duì)象,其工作機(jī)制類似數(shù)組指針的一種封裝。
切片(slice)是對(duì)數(shù)組一個(gè)連續(xù)片段的引用,所以切片是一個(gè)引用類型。這個(gè)片段可以是整個(gè)數(shù)組,或者是由起始和終止索引標(biāo)識(shí)的一些項(xiàng)的子集。需要注意的是,終止索引標(biāo)識(shí)的項(xiàng)不包括在切片內(nèi)。切片提供了一個(gè)與指向數(shù)組的動(dòng)態(tài)窗口。
給定項(xiàng)的切片索引可能比相關(guān)數(shù)組的相同元素的索引小。和數(shù)組不同的是,切片的長(zhǎng)度可以在運(yùn)行時(shí)修改,最小為 0 最大為相關(guān)數(shù)組的長(zhǎng)度:切片是一個(gè)長(zhǎng)度可變的數(shù)組。
切片數(shù)據(jù)結(jié)構(gòu)定義
type slice struct { array unsafe.Pointer len int cap int }
切片的結(jié)構(gòu)體由3部分構(gòu)成,Pointer 是指向一個(gè)數(shù)組的指針,len 代表當(dāng)前切片的長(zhǎng)度,cap 是當(dāng)前切片的容量。cap 總是大于等于 len 的。
如果想從 slice 中得到一塊內(nèi)存地址,可以這樣做:
s := make([]byte, 200) ptr := unsafe.Pointer(&s[0])
3 創(chuàng)建切片
3.1 方法一:make
使用make函數(shù)創(chuàng)建slice
// 創(chuàng)建一個(gè)初始大小是3,容量是10的切片 s1 := make([]int64,3,10)
底層方法實(shí)現(xiàn):
func makeslice(et *_type, len, cap int) slice { // 根據(jù)切片的數(shù)據(jù)類型,獲取切片的最大容量 maxElements := maxSliceCap(et.size) // 比較切片的長(zhǎng)度,長(zhǎng)度值域應(yīng)該在[0,maxElements]之間 if len < 0 || uintptr(len) > maxElements { panic(errorString("makeslice: len out of range")) } // 比較切片的容量,容量值域應(yīng)該在[len,maxElements]之間 if cap < len || uintptr(cap) > maxElements { panic(errorString("makeslice: cap out of range")) } // 根據(jù)切片的容量申請(qǐng)內(nèi)存 p := mallocgc(et.size*uintptr(cap), et, true) // 返回申請(qǐng)好內(nèi)存的切片的首地址 return slice{p, len, cap} }
3.2 方法二:字面量
利用數(shù)組創(chuàng)建切片
arr := [10]int64{1,2,3,4,5,6,7,8,9,10} s1 := arr[2:4:6] // 以arr[2:4]創(chuàng)建一個(gè)切片,且容量到達(dá)arr[6]的位置,即cap=6-2=4,如果不寫容量則默認(rèn)為數(shù)組最后一個(gè)元素
4 nil和空切片
nil切片的指針指向的是nil
空切片指向的是一個(gè)空數(shù)組
空切片和 nil 切片的區(qū)別在于,空切片指向的地址不是nil,指向的是一個(gè)內(nèi)存地址,但是它沒有分配任何內(nèi)存空間,即底層元素包含0個(gè)元素。
最后需要說(shuō)明的一點(diǎn)是。不管是使用 nil 切片還是空切片,對(duì)其調(diào)用內(nèi)置函數(shù) append,len 和 cap 的效果都是一樣的
5 切片擴(kuò)容
5.1 擴(kuò)容策略
func main() { slice := []int{10, 20, 30, 40} newSlice := append(slice, 50) fmt.Printf("Before slice = %v, Pointer = %p, len = %d, cap = %d\n", slice, &slice, len(slice), cap(slice)) fmt.Printf("Before newSlice = %v, Pointer = %p, len = %d, cap = %d\n", newSlice, &newSlice, len(newSlice), cap(newSlice)) newSlice[1] += 10 fmt.Printf("After slice = %v, Pointer = %p, len = %d, cap = %d\n", slice, &slice, len(slice), cap(slice)) fmt.Printf("After newSlice = %v, Pointer = %p, len = %d, cap = %d\n", newSlice, &newSlice, len(newSlice), cap(newSlice)) }
輸出結(jié)果:
Before slice = [10 20 30 40], Pointer = 0xc4200b0140, len = 4, cap = 4
Before newSlice = [10 20 30 40 50], Pointer = 0xc4200b0180, len = 5, cap = 8
After slice = [10 20 30 40], Pointer = 0xc4200b0140, len = 4, cap = 4
After newSlice = [10 30 30 40 50], Pointer = 0xc4200b0180, len = 5, cap = 8
Go 中切片擴(kuò)容的策略是這樣的:
如果切片的容量小于 1024 個(gè)元素,于是擴(kuò)容的時(shí)候就翻倍增加容量。上面那個(gè)例子也驗(yàn)證了這一情況,總?cè)萘繌脑瓉?lái)的4個(gè)翻倍到現(xiàn)在的8個(gè)。
一旦元素個(gè)數(shù)超過(guò) 1024 個(gè)元素,那么增長(zhǎng)因子就變成 1.25 ,即每次增加原來(lái)容量的四分之一。
5.2 底層數(shù)組是不是新地址
不一定。當(dāng)發(fā)生了擴(kuò)容就肯定是新數(shù)組,沒有發(fā)生擴(kuò)容則是舊地址
不管切片是通過(guò)make創(chuàng)建還是字面量創(chuàng)建,底層都是一樣的,指向的是一個(gè)數(shù)組。當(dāng)使用字面量創(chuàng)建時(shí),切片底層使用的數(shù)組就是創(chuàng)建時(shí)候的數(shù)組。修改切片中的元素或者往切片中添加元素,如果沒有擴(kuò)容,則會(huì)影響原數(shù)組的內(nèi)容,切片底層和原數(shù)組是同一個(gè)數(shù)組;當(dāng)切片擴(kuò)容了之后,則修改切片的元素或者往切片中添加元素,不會(huì)修改數(shù)組內(nèi)容,因?yàn)榍衅瑪U(kuò)容之后,底層數(shù)組不再是原數(shù)組,而是一個(gè)新數(shù)組。
所以盡量避免切片底層數(shù)組與原始數(shù)組相同,盡量使用make創(chuàng)建切片
range遍歷數(shù)組或者切片需要注意
func main() { // slice := []int{10, 20, 30, 40} slice := [4]int{10, 20, 30, 40} for index, value := range slice { fmt.Printf("value = %d , value-addr = %x , slice-addr = %x\n", value, &value, &slice[index]) } }
結(jié)果:
value = 10 , value-addr = c00000a0a8 , slice-addr = c000012360
value = 20 , value-addr = c00000a0a8 , slice-addr = c000012368
value = 30 , value-addr = c00000a0a8 , slice-addr = c000012370
value = 40 , value-addr = c00000a0a8 , slice-addr = c000012378
從上面結(jié)果我們可以看到,如果用 range 的方式去遍歷一個(gè)數(shù)組或者切片,拿到的 Value 其實(shí)是切片里面的值拷貝。所以每次打印 Value 的地址都不變。
由于 Value 是值拷貝的,并非引用傳遞,所以直接改 Value 是達(dá)不到更改原切片值的目的的,需要通過(guò) &slice[index] 獲取真實(shí)的地址
尤其是在for循環(huán)中使用協(xié)程,一定不能直接把index,value傳入?yún)f(xié)程,而應(yīng)該通過(guò)參數(shù)傳進(jìn)去
錯(cuò)誤示例:
func main() { s := []int{10,20,30} for index, value := range s { go func() { time.Sleep(time.Second) fmt.Println(fmt.Sprintf("index:%d,value:%d", index,value)) }() } time.Sleep(time.Second*2) }
結(jié)果:
index:2,value:30
index:2,value:30
index:2,value:30
正確示例:
func main() { s := []int{10,20,30} for index, value := range s { go func(i,v int) { time.Sleep(time.Second) fmt.Println(fmt.Sprintf("index:%d,value:%d", i,v)) }(index,value) } time.Sleep(time.Second*2) }
結(jié)果:
index:0,value:10
index:2,value:30
index:1,value:20
原文鏈接:https://juejin.cn/post/6945837901636698125
- 上一篇:沒有了
- 下一篇:沒有了
相關(guān)推薦
- 2022-09-16 c#解析jobject的數(shù)據(jù)結(jié)構(gòu)_C#教程
- 2022-03-09 C++泛型編程Generic?Programming的使用_C#教程
- 2022-07-19 iptables限制docker端口禁止對(duì)某臺(tái)主機(jī)進(jìn)行提供服務(wù)
- 2022-08-01 MongoDB基礎(chǔ)之查詢文檔_MongoDB
- 2022-08-17 python數(shù)據(jù)可視化matplotlib繪制折線圖示例_python
- 2022-04-15 C#的并發(fā)機(jī)制優(yōu)秀在哪你知道么_C#教程
- 2022-06-10 C語(yǔ)言中sizeof函數(shù)踩過(guò)的坑總結(jié)_C 語(yǔ)言
- 2023-10-24 解決Ubuntu下載速度或更新速度緩慢問(wèn)題
- 欄目分類
-
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支