重慶分公司,新征程啟航
為企業提供網站建設、域名注冊、服務器等服務
為企業提供網站建設、域名注冊、服務器等服務
隊列的概念在 順序隊列 中,而使用循環隊列的目的主要是規避假溢出造成的空間浪費,在使用循環隊列處理假溢出時,主要有三種解決方案
公司主營業務:網站制作、網站建設、移動網站開發等業務。幫助企業客戶真正實現互聯網宣傳,提高企業的競爭能力。創新互聯建站是一支青春激揚、勤奮敬業、活力青春激揚、勤奮敬業、活力澎湃、和諧高效的團隊。公司秉承以“開放、自由、嚴謹、自律”為核心的企業文化,感謝他們對我們的高要求,感謝他們從不同領域給我們帶來的挑戰,讓我們激情的團隊有機會用頭腦與智慧不斷的給客戶帶來驚喜。創新互聯建站推出麥蓋提免費做網站回饋大家。
本文提供后兩種解決方案。
順序隊和循環隊列是一種特殊的線性表,與順序棧類似,都是使用一組地址連續的存儲單元依次存放自隊頭到隊尾的數據元素,同時附設隊頭(front)和隊尾(rear)兩個指針,但我們要明白一點,這個指針并不是指針變量,而是用來表示數組當中元素下標的位置。
本文使用切片來完成的循環隊列,由于一開始使用三個參數的make關鍵字創建切片,在輸出的結果中不包含nil值(看起來很舒服),而且在驗證的過程中發現使用append()函數時切片內置的cap會發生變化,在消除了種種障礙后得到了一個四不像的循環隊列,即設置的指針是順序隊列的指針,但實際上進行的操作是順序隊列的操作。最后是對make()函數和append()函數的一些使用體驗和小結,隊列的應用放在鏈隊好了。
官方描述(片段)
即切片是一個抽象層,底層是對數組的引用。
當我們使用
構建出來的切片的每個位置的值都被賦為interface類型的初始值nil,但是nil值也是有大小的。
而使用
來進行初始化時,雖然生成的切片中不包含nil值,但是無法通過設置的指針變量來完成入隊和出隊的操作,只能使用append()函數來進行操作
在go語言中,切片是一片連續的內存空間加上長度與容量的標識,比數組更為常用。使用 append 關鍵字向切片中追加元素也是常見的切片操作
正是基于此,在使用go語言完成循環隊列時,首先想到的就是使用make(type, len, cap)關鍵字方式完成切片初始化,然后使用append()函數來操作該切片,但這一方式出現了很多問題。在使用append()函數時,切片的cap可能會發生變化,用不好就會發生擴容或收縮。最終造成的結果是一個四不像的結果,入隊和出隊操作變得與指針變量無關,失去了作為循環隊列的意義,用在順序隊列還算合適。
參考博客:
Go語言中的Nil
Golang之nil
Go 語言設計與實現
數組
數組是內置(build-in)類型,是一組同類型數據的集合。
數組的初始化有多種形式
長度為5的數組,其元素值依次為:1,2,3,4,5
長度為5的數組,其元素值依次為:1,2,0,0,0 。在初始化時沒有指定初值的元素將會賦值為其元素類型int的默認值0,string的默認值是 ""
長度為5的數組,其長度是根據初始化時指定的元素個數決定的
長度為5的數組,key:value,其元素值依次為:0,0,1,2,3。在初始化時指定了2,3,4索引中對應的值:1,2,3
長度為5的數組,起元素值依次為:0,0,1,0,3。由于指定了最大索引4對應的值3,根據初始化的元素個數確定其長度為5
切片
數組的長度不可改變,在特定場景中這樣的集合就不太適用,Go中提供了一種靈活,功能強悍的內置類型 Slices 切片。
切片可以通過數組來初始化,也可以通過內置函數make()初始化。初始化時len=cap,在追加元素時如果容量cap不足時將按len的 2 倍擴容。
直接初始化切片, [] 表示是切片類型, {1,2,3} 初始化值依次是1,2,3.其cap=len=3
初始化切片s,是數組arr的引用
將arr中從下標startIndex到endIndex-1 下的元素 創建為一個新的切片
缺省endIndex時將表示一直到arr的最后一個元素
缺省startIndex時將表示從arr的第一個元素開始
通過切片s初始化切片s1
通過內置函數make()初始化切片s,[]int 標識為其元素類型為int的切片
定義一個切片,然后讓切片去引用一個已經創建好的數組。基本語法如下:
索引1:切片引用的起始元素位
索引2:切片只引用該元素位之前的元素
例程如下:
在該方法中,我們未指定容量cap,這里的值為5是系統定義的。
在方法一中,可以用arr數組名來操控數組中的元素,也可以通過slice切片來操控數組中的元素。切片是直接引用數組,數組是事先存在的,程序員是可見的。
通過 make 來創建切片,基本語法如下:
make函數第三個參數cap即容量是可選的,如果一定要自己注明的話,要注意保證cap≥len。
用該方法可以 指定切片的大小(len)和容量(cap)
例程如下:
由于未賦值系統默認將元素值置為0,即:
數值類型數組:????默認值為 0
字符串數組:? ? ? ?默認值為 ""
bool數組:? ? ? ? ? ?默認值為 false
在方法二中,通過make方式創建的切片對應的數組是由make底層維護,對外不可見,即只能通過slice去訪問各個元素。
定義一個切片,直接就指定具體數組,使用原理類似于make的方式。
例程如下:
Go 中的分片數組,實際上有點類似于Java中的ArrayList,是一個可以擴展的數組,但是Go中的切片由比較靈活,它和數組很像,也是基于數組,所以在了解Go切片前我們先了解下數組。
數組簡單描述就由相同類型元素組成的數據結構, 在創建初期就確定了長度,是不可變的。
但是Go的數組類型又和C與Java的數組類型不一樣, NewArray 用于創建一個數組,從源碼中可以看出最后返回的是 Array{}的指針,并不是第一個元素的指針,在Go中數組屬于值類型,在進行傳遞時,采取的是值傳遞,通過拷貝整個數組。Go語言的數組是一種有序的struct。
Go 語言的數組有兩種不同的創建方式,一種是顯示的初始化,一種是隱式的初始化。
注意一定是使用 [...]T 進行創建,使用三個點的隱式創建,編譯器會對數組的大小進行推導,只是Go提供的一種語法糖。
其次,Go中數組的類型,是由數值類型和長度兩個一起確定的。[2]int 和 [3]int 不是同一個類型,不能進行傳參和比較,把數組理解為類型和長度兩個屬性的結構體,其實就一目了然了。
Go中的數組屬于值類型,通常應該存儲于棧中,局部變量依然會根據逃逸分析確定存儲棧還是堆中。
編譯器對數組函數中做兩種不同的優化:
在靜態區完成賦值后復制到棧中。
總結起來,在不考慮逃逸分析的情況下,如果數組中元素的個數小于或者等于 4 個,那么所有的變量會直接在棧上初始化,如果數組元素大于 4 個,變量就會在靜態存儲區初始化然后拷貝到棧上。
由于數組是值類型,那么賦值和函數傳參操作都會復制整個數組數據。
不管是賦值或函數傳參,地址都不一致,發生了拷貝。如果數組的數據較大,則會消耗掉大量內存。那么為了減少拷貝我們可以主動的傳遞指針呀。
地址是一樣的,不過傳指針會有一個弊端,從打印結果可以看到,指針地址都是同一個,萬一原數組的指針指向更改了,那么函數里面的指針指向都會跟著更改。
同樣的我們將數組轉換為切片,通過傳遞切片,地址是不一樣的,數組值相同。
切片是引用傳遞,所以它們不需要使用額外的內存并且比使用數組更有效率。
所以,切片屬于引用類型。
通過這種方式可以將數組轉換為切片。
中間不加三個點就是切片,使用這種方式創建切片,實際上是先創建數組,然后再通過第一種方式創建。
使用make創建切片,就不光編譯期了,make創建切片會涉及到運行期。1. 切片的大小和容量是否足夠小;
切片是否發生了逃逸,最終在堆上初始化。如果切片小的話會先在棧或靜態區進行創建。
切片有一個數組的指針,len是指切片的長度, cap指的是切片的容量。
cap是在初始化切片是生成的容量。
發現切片的結構體是數組的地址指針array unsafe.Pointer,而Go中數組的地址代表數組結構體的地址。
slice 中得到一塊內存地址,array[0]或者unsafe.Pointer(array[0])。
也可以通過地址構造切片
nil切片:指的unsafe.Pointer 為nil
空切片:
創建的指針不為空,len和cap為空
當一個切片的容量滿了,就需要擴容了。怎么擴,策略是什么?
如果原來數組切片的容量已經達到了最大值,再想擴容, Go 默認會先開一片內存區域,把原來的值拷貝過來,然后再執行 append() 操作。這種情況對現數組的地址和原數組地址不相同。
從上面結果我們可以看到,如果用 range 的方式去遍歷一個切片,拿到的 Value 其實是切片里面的值拷貝,即淺拷貝。所以每次打印 Value 的地址都不變。
由于 Value 是值拷貝的,并非引用傳遞,所以直接改 Value 是達不到更改原切片值的目的的,需要通過 slice[index] 獲取真實的地址。
new 主要用于結構體的初始化
make用于數組array,切片slice,協程chnnel的初始化
例如: users:=make([10]int);
msg:=make(chan int);
new會分配結構空間,并初始化為清空為零,不進一步初始化
new之后需要一個指針來指向這個結構
make會分配結構空間及其附屬空間,并完成其間的指針初始化
make返回這個結構空間,不另外分配一個指針
例子new:
var p *[]int = new([]int)
或
p := new([]int)
以上分配了一個slice結構,但是結構中的應該指向底層數組的ptr指針為空,故實際不能往這個slice里面存取數據
同時分配了一個指針p,也即(在32位系統中)占4個字節并存放slice結構的地址
例子make:
var v []int = make([]int, 0)
v := make([]int, 0)
以上分配了一個slice結構,且結構中的應該指向底層數組的ptr指針已經指向了某個底層數組,這個底層數組應該已經分配了,故這個slice已經可以使用了
注意v就是這個slice結構,而不是一個指向slice的指針
上述僅是示例,一般使用時都會明確長度和容量:v := make([]int, 10, 50)
結論:
由上可見,用new來分配slice的意義不大,因為沒有恰當的初始化,無法直接使用
有附帶空間的結構,使用make來初始化,可以完成內部指針初始化,其后可以立即使用