mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2025-12-19 04:04:20 +08:00
rebuild
This commit is contained in:
261
ch4/ch4-02.html
261
ch4/ch4-02.html
@@ -5,7 +5,7 @@
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>切片 | Go编程语言</title>
|
||||
<title>Slice | Go语言圣经</title>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="description" content="">
|
||||
<meta name="generator" content="GitBook 2.5.2">
|
||||
@@ -48,7 +48,7 @@
|
||||
<body>
|
||||
|
||||
|
||||
<div class="book" data-level="4.2" data-chapter-title="切片" data-filepath="ch4/ch4-02.md" data-basepath=".." data-revision="Mon Dec 28 2015 16:03:52 GMT+0800 (中国标准时间)">
|
||||
<div class="book" data-level="4.2" data-chapter-title="Slice" data-filepath="ch4/ch4-02.md" data-basepath=".." data-revision="Thu Dec 31 2015 16:18:40 GMT+0800 (中国标准时间)">
|
||||
|
||||
|
||||
<div class="book-summary">
|
||||
@@ -575,7 +575,7 @@
|
||||
|
||||
<b>4.2.</b>
|
||||
|
||||
切片
|
||||
Slice
|
||||
</a>
|
||||
|
||||
|
||||
@@ -590,7 +590,7 @@
|
||||
|
||||
<b>4.3.</b>
|
||||
|
||||
字典
|
||||
Map
|
||||
</a>
|
||||
|
||||
|
||||
@@ -2013,7 +2013,7 @@
|
||||
<!-- Title -->
|
||||
<h1>
|
||||
<i class="fa fa-circle-o-notch fa-spin"></i>
|
||||
<a href="../" >Go编程语言</a>
|
||||
<a href="../" >Go语言圣经</a>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
@@ -2023,12 +2023,253 @@
|
||||
|
||||
<section class="normal" id="section-">
|
||||
|
||||
<h2 id="42-切片">4.2. 切片</h2>
|
||||
<p>TODO</p>
|
||||
<h2 id="42-slice">4.2. Slice</h2>
|
||||
<p>Slice(切片)代表變長的序列,序列中每個元素都有相同的類型。一個slice類型一般寫作[]T,其中T代表slice中元素的類型;語法和數組很像隻是沒有長度而已。</p>
|
||||
<p>數組和slice之間有着緊密的聯繫。一個slice是一個輕量級的數據結果,提供了訪問數組子序列(或者全部)元素的功能,因爲slice的底層確實引用一個數組對象。一個slice有三個部分構成:指針、長度和容量。指針指向第一個元素對應的底層數組元素的地址,slice的第一個元素併不一定就是數組的第一個元素。長度對應slice中元素的數目;長度不能超過容量,容量一般是從slice的開始位置到底層數據的結尾位置。內置的len和cap函數分别返迴slice的長度和容量。</p>
|
||||
<p>多個slice之間可以共享底層的數據,併且引用的數組部分區間可能重疊。圖4.1顯示了表示一年中每個月份名字的字符串數組,還有重疊引用了該數組的兩個slice。數組這樣定義</p>
|
||||
<pre><code class="lang-Go">months := [...]<span class="hljs-typename">string</span>{<span class="hljs-number">1</span>: <span class="hljs-string">"January"</span>, <span class="hljs-comment">/* ... */</span>, <span class="hljs-number">12</span>: <span class="hljs-string">"December"</span>}
|
||||
</code></pre>
|
||||
<p>因此一月份是months[1],十二月份是months[12]。通常,數組的第一個元素從索引0開始,但是月份一般是從1開始的,因此我們聲明數組時直接第0個元素,第0個元素會被自動初始化爲空字符串。</p>
|
||||
<p>slice的操作s[i:j],其中0 ≤ i≤ j≤ cap(s),用於創建一個新的slice,引用s的從第i個元素開始到第j-1個元素的子序列。新的slice將隻有j-i個元素。如果i位置的索引被省略的話將使用0代替,如果j位置的索引被省略的話將使用len(s)代替。因此,months[1:13]切片操作將引用全部有效的月份,和months[1:]操作等價;months[:]切片操作則是引用整個數組。讓我們分别定義表示第二季度和北方夏天的slice,它們有重疊部分:</p>
|
||||
<p><img src="../images/ch4-01.png" alt=""></p>
|
||||
<pre><code class="lang-Go">Q2 := months[<span class="hljs-number">4</span>:<span class="hljs-number">7</span>]
|
||||
summer := months[<span class="hljs-number">6</span>:<span class="hljs-number">9</span>]
|
||||
fmt.Println(Q2) <span class="hljs-comment">// ["April" "May" "June"]</span>
|
||||
fmt.Println(summer) <span class="hljs-comment">// ["June" "July" "August"]</span>
|
||||
</code></pre>
|
||||
<p>兩個slice都包含了六月份,下面的代碼是一個包含相同月份的測試(性能較低):</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">for</span> _, s := <span class="hljs-keyword">range</span> summer {
|
||||
<span class="hljs-keyword">for</span> _, q := <span class="hljs-keyword">range</span> Q2 {
|
||||
<span class="hljs-keyword">if</span> s == q {
|
||||
fmt.Printf(<span class="hljs-string">"%s appears in both\n"</span>, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>如果切片操作長處cap(s)的上限將導致一個panic異常,但是超出len(s)則是擴展了slice,因此新slice的長度會變大:</p>
|
||||
<pre><code class="lang-Go">fmt.Println(summer[:<span class="hljs-number">20</span>]) <span class="hljs-comment">// panic: out of range</span>
|
||||
|
||||
endlessSummer := summer[:<span class="hljs-number">5</span>] <span class="hljs-comment">// extend a slice (within capacity)</span>
|
||||
fmt.Println(endlessSummer) <span class="hljs-comment">// "[June July August September October]"</span>
|
||||
</code></pre>
|
||||
<p>另外,字符串的切片操作和[]byte字節類型切片的切片操作是類似的。它們都寫作x[m:n],併且都是返迴一個原始字節繫列的子序列,底層都是共享之前的底層數組,因此切片操作對應常量時間複雜度。x[m:n]切片操作對於字符串則生成一個新字符串,如果x是[]byte的話則生成一個新的[]byte。</p>
|
||||
<p>因爲slice值包含指向第一個元素的指針,因此向函數傳遞slice將運行在函數內部脩改底層數組的元素。換句話説,複雜一個slice隻是對底層的數組創建了一個新的slice别名(§2.3.2)。下面的reverse函數在原內存空間將[]int類型的slice反轉,而且它可以用於任意長度的slice。</p>
|
||||
<pre><code class="lang-Go">gopl.io/ch4/rev
|
||||
|
||||
<span class="hljs-comment">// reverse reverses a slice of ints in place.</span>
|
||||
<span class="hljs-keyword">func</span> reverse(s []<span class="hljs-typename">int</span>) {
|
||||
<span class="hljs-keyword">for</span> i, j := <span class="hljs-number">0</span>, <span class="hljs-built_in">len</span>(s)-<span class="hljs-number">1</span>; i < j; i, j = i+<span class="hljs-number">1</span>, j-<span class="hljs-number">1</span> {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>這里我們反轉數組的應用:</p>
|
||||
<pre><code class="lang-Go">a := [...]<span class="hljs-typename">int</span>{<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>}
|
||||
reverse(a[:])
|
||||
fmt.Println(a) <span class="hljs-comment">// "[5 4 3 2 1 0]"</span>
|
||||
</code></pre>
|
||||
<p>一種將slice元素循環向左镟轉n個元素的方法是三次調用reverse反轉函數,第一次是反轉開頭的n個元素,然後是反轉剩下的元素,最後是反轉整個slice的元素。(如果是向右循環镟轉,則將第三個函數調用移到第一個調用位置就可以了。)</p>
|
||||
<pre><code class="lang-Go">s := []<span class="hljs-typename">int</span>{<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>}
|
||||
<span class="hljs-comment">// Rotate s left by two positions.</span>
|
||||
reverse(s[:<span class="hljs-number">2</span>])
|
||||
reverse(s[<span class="hljs-number">2</span>:])
|
||||
reverse(s)
|
||||
fmt.Println(s) <span class="hljs-comment">// "[2 3 4 5 0 1]"</span>
|
||||
</code></pre>
|
||||
<p>要註意的是slice類型的變量s和數組類型的變量a的初始化語法的差異。slice和數組的字面值語法很類似,它們都是用花括弧包含一繫列的初始化元素,但是對於slice併沒有指明序列的長度。這會隱式地創建一個合適大小的數組,然後slice的指針指向底層的數組。就像數組字面值一樣,slice的字面值也可以按順序指定初始化值序列,或者是通過索引和元素值指定,或者的兩種風格的混合語法初始化。</p>
|
||||
<p>和數組不同的是,slice不能比較,因此我們不能使用==操作符來判斷兩個slice是否有相同的元素。不過標準庫提供了高度優化的bytes.Equal函數來判斷兩個字節型slice是否相等([]byte),但是對於其他類型的slice,我們必鬚自己展開每個元素進行比較:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">func</span> equal(x, y []<span class="hljs-typename">string</span>) <span class="hljs-typename">bool</span> {
|
||||
<span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(x) != <span class="hljs-built_in">len</span>(y) {
|
||||
<span class="hljs-keyword">return</span> <span class="hljs-constant">false</span>
|
||||
}
|
||||
<span class="hljs-keyword">for</span> i := <span class="hljs-keyword">range</span> x {
|
||||
<span class="hljs-keyword">if</span> x[i] != y[i] {
|
||||
<span class="hljs-keyword">return</span> <span class="hljs-constant">false</span>
|
||||
}
|
||||
}
|
||||
<span class="hljs-keyword">return</span> <span class="hljs-constant">true</span>
|
||||
}
|
||||
</code></pre>
|
||||
<p>上面關於兩個slice的深度相等測試,運行的時間併不比支持==操作的數組或字符串更多,但是爲何slice卻不支持比較運算符呢?這方面有兩個原因。第一個原因,一個slice的元素是間接引用的,一個slice甚至可以包含自身。雖然有很多辦法處理這種情形,但是沒有一個是簡單有效的。</p>
|
||||
<p>第二個原因,因爲slice的元素是間接引用的,一個固定值的slice在不同的時間可能包含不同的元素,因爲底層數組的元素可能會被脩改。併且Go語言中map等哈希表之類的數據結構的key隻做簡單的淺拷貝,它要求在整個聲明週期中相等的key必鬚對相同的元素。對於像指針或chan之類的引用類型,==相等測試可以判斷兩個是否是引用相同的對象。一個針對slice的淺相等測試的==操作符可能是有一定用處的,也能臨時解決map類型的key問題,但是slice和數組不同的相等測試行爲會讓人睏惑。因此,安全的做飯是直接禁止slice之間的比較操作。</p>
|
||||
<p>slice唯一合法的比較是和nil比較,例如:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">if</span> summer == <span class="hljs-constant">nil</span> { <span class="hljs-comment">/* ... */</span> }
|
||||
</code></pre>
|
||||
<p>一個零值的slice等於nil。一個nil值的slice併沒有底層數組。一個nil值的slice的長度和容量都是0,但是也有非nil值的slice的長度和容量也是0的,例如[]int{}或make([]int, 3)[3:]。與任意類型的nil值一樣,我們可以用[]int(nil)類型轉換表達式來生成一個對應類型slice的nil值。</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">var</span> s []<span class="hljs-typename">int</span> <span class="hljs-comment">// len(s) == 0, s == nil</span>
|
||||
s = <span class="hljs-constant">nil</span> <span class="hljs-comment">// len(s) == 0, s == nil</span>
|
||||
s = []<span class="hljs-typename">int</span>(<span class="hljs-constant">nil</span>) <span class="hljs-comment">// len(s) == 0, s == nil</span>
|
||||
s = []<span class="hljs-typename">int</span>{} <span class="hljs-comment">// len(s) == 0, s != nil</span>
|
||||
</code></pre>
|
||||
<p>如果你需要測試一個slice是否是空的,使用len(s) == 0來判斷,而不是用s == nil來判斷。除了和nil相等比較外,一個nil值的slice的行爲和其它任意0産長度的slice一樣;例如reverse(nil)也是安全的。除了文檔已經明確説明的地方,所有的Go語言函數應該以相同的方式對待nil值的slice和0長度的slice。</p>
|
||||
<p>內置的make函數創建一個指定元素類型、長度和容量的slice。容量部分可以省略,在這種情況下,容量將等於長度。</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-built_in">make</span>([]T, <span class="hljs-built_in">len</span>)
|
||||
<span class="hljs-built_in">make</span>([]T, <span class="hljs-built_in">len</span>, <span class="hljs-built_in">cap</span>) <span class="hljs-comment">// same as make([]T, cap)[:len]</span>
|
||||
</code></pre>
|
||||
<p>在底層,make創建了一個匿名的數組變量,然後返迴一個slice;隻有通過返迴的slice才能引用底層匿名的數組變量。在第一種語句中,slice是整個數組的view。在第二個語句中,slice隻引用了底層數組的前len個元素,但是容量將包含整個的數組。額外的元素是留給未來的增長用的。</p>
|
||||
<h3 id="421-append函數">4.2.1. append函數</h3>
|
||||
<p>TODO</p>
|
||||
<p>內置的append函數用於向slice追加元素:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">var</span> runes []<span class="hljs-typename">rune</span>
|
||||
<span class="hljs-keyword">for</span> _, r := <span class="hljs-keyword">range</span> <span class="hljs-string">"Hello, 世界"</span> {
|
||||
runes = <span class="hljs-built_in">append</span>(runes, r)
|
||||
}
|
||||
fmt.Printf(<span class="hljs-string">"%q\n"</span>, runes) <span class="hljs-comment">// "['H' 'e' 'l' 'l' 'o' ',' ' ' '世' '界']"</span>
|
||||
</code></pre>
|
||||
<p>在循環中使用append函數構建一個有九個rune字符構成的slice,當然對應這個特殊的問題我們可以通過Go語言內置的[]rune("Hello, 世界")轉換操作完成。</p>
|
||||
<p>append函數對於理解slice底層是如何工作的非常重要,所以讓我們仔細査看究竟是發生了什麽。下面是第一個版本的appendInt函數,專門用於處理[]int類型的slice:</p>
|
||||
<pre><code class="lang-Go">gopl.io/ch4/<span class="hljs-built_in">append</span>
|
||||
|
||||
<span class="hljs-keyword">func</span> appendInt(x []<span class="hljs-typename">int</span>, y <span class="hljs-typename">int</span>) []<span class="hljs-typename">int</span> {
|
||||
<span class="hljs-keyword">var</span> z []<span class="hljs-typename">int</span>
|
||||
zlen := <span class="hljs-built_in">len</span>(x) + <span class="hljs-number">1</span>
|
||||
<span class="hljs-keyword">if</span> zlen <= <span class="hljs-built_in">cap</span>(x) {
|
||||
<span class="hljs-comment">// There is room to grow. Extend the slice.</span>
|
||||
z = x[:zlen]
|
||||
} <span class="hljs-keyword">else</span> {
|
||||
<span class="hljs-comment">// There is insufficient space. Allocate a new array.</span>
|
||||
<span class="hljs-comment">// Grow by doubling, for amortized linear complexity.</span>
|
||||
zcap := zlen
|
||||
<span class="hljs-keyword">if</span> zcap < <span class="hljs-number">2</span>*<span class="hljs-built_in">len</span>(x) {
|
||||
zcap = <span class="hljs-number">2</span> * <span class="hljs-built_in">len</span>(x)
|
||||
}
|
||||
z = <span class="hljs-built_in">make</span>([]<span class="hljs-typename">int</span>, zlen, zcap)
|
||||
<span class="hljs-built_in">copy</span>(z, x) <span class="hljs-comment">// a built-in function; see text</span>
|
||||
}
|
||||
z[<span class="hljs-built_in">len</span>(x)] = y
|
||||
<span class="hljs-keyword">return</span> z
|
||||
}
|
||||
</code></pre>
|
||||
<p>每次調用appendInt函數,必鬚先檢測slice底層數組是否有足夠的容量來保存新添加的元素。如果有足夠空間的話,直接擴展slice(依然在原有的底層數組之上),將新添加的y元素複製到新擴展的空間,併返迴slice。因此,輸入的x和輸出的z共享相同的底層數組。</p>
|
||||
<p>如果沒有足夠的增長空間的話,appendInt函數則會先分配一個足夠大的slice用於保存新的結果,先將輸入的x複製到新的空間,然後添加y元素。結果z和輸入的x引用的將是不同的底層數組。</p>
|
||||
<p>雖然通過循環複製元素更直接,不過內置的copy函數可以方便地將一個slice複製另一個相同類型的slice。copy函數的第一個參數是要複製的目標slice,第二個參數是源slice,目標和源的位置順序和dst = src賦值語句是一致的。兩個slice可以共享同一個底層數組,甚至有重疊也沒有問題。copy函數將返迴成功複製的元素的個數(我們這里沒有用到),等於兩個slice中較小的長度,所以我們不用擔心覆蓋會超出目的slice的范圍。</p>
|
||||
<p>爲了效率,新分配的數組一般略大於保存x和y所需要的最低大小。通過在每次擴展數組時直接將長度翻倍從而避免了多次內存分配,也確保了添加單個元素操的平均時間是一個常數時間。這個程序演示了效果:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">func</span> main() {
|
||||
<span class="hljs-keyword">var</span> x, y []<span class="hljs-typename">int</span>
|
||||
<span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i < <span class="hljs-number">10</span>; i++ {
|
||||
y = appendInt(x, i)
|
||||
fmt.Printf(<span class="hljs-string">"%d cap=%d\t%v\n"</span>, i, <span class="hljs-built_in">cap</span>(y), y)
|
||||
x = y
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>每一次容量的變化都會導致重新分配內存和copy操作:</p>
|
||||
<pre><code>0 cap=1 [0]
|
||||
1 cap=2 [0 1]
|
||||
2 cap=4 [0 1 2]
|
||||
3 cap=4 [0 1 2 3]
|
||||
4 cap=8 [0 1 2 3 4]
|
||||
5 cap=8 [0 1 2 3 4 5]
|
||||
6 cap=8 [0 1 2 3 4 5 6]
|
||||
7 cap=8 [0 1 2 3 4 5 6 7]
|
||||
8 cap=16 [0 1 2 3 4 5 6 7 8]
|
||||
9 cap=16 [0 1 2 3 4 5 6 7 8 9]
|
||||
</code></pre><p>讓我們仔細査看i=3次的迭代。當時x包含了[0 1 2]三個元素,但是容量是4,因此可以簡單將新的元素添加到末尾,不需要新的內存分配。然後新的y的長度和容量都是4,併且和x引用着相同的底層數組,如圖4.2所示。</p>
|
||||
<p><img src="../images/ch4-02.png" alt=""></p>
|
||||
<p>在下一次迭代時i=4,現在沒有新的空餘的空間了,因此appendInt函數分配一個容量爲8的底層數組,將x的4個元素[0 1 2 3]複製到新空間的開頭,然後添加新的元素i,新元素的值是4。新的y的長度是5,容量是8;後面有3個空閒的位置,三次迭代都不需要分配新的空間。當前迭代中,y和x是對應不用底層數組的view。這次操作如圖4.3所示。</p>
|
||||
<p><img src="../images/ch4-03.png" alt=""></p>
|
||||
<p>內置的append函數可能使用比appendInt更複雜的內存擴展策略。因此,通常我們併不知道append調用是否導致了內存的分配,因此我們也不能確認新的slice和原始的slice是否引用的是相同的底層數組空間。同樣,我們不能確認在原先的slice上的操作是否會影響到新的slice。因此,通常是將append返迴的結果直接賦值給輸入的slice變量:</p>
|
||||
<pre><code class="lang-Go">runes = <span class="hljs-built_in">append</span>(runes, r)
|
||||
</code></pre>
|
||||
<p>更新slice變量不僅對調用append函數是必要的,實際上對應任何可能導致長度、容量或底層數組變化的操作都是必要的。要正確地使用slice,需要記住盡管底層數組的元素是間接訪問,但是slice本身的指針、長度和容量是直接訪問的。要更新這些信息需要像上面例子那樣一個顯式的賦值操作。從這個角度看,slice併不是一個純粹的引用類型,它實際上是一個類似下面結構體的聚合類型:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">type</span> IntSlice <span class="hljs-keyword">struct</span> {
|
||||
ptr *<span class="hljs-typename">int</span>
|
||||
<span class="hljs-built_in">len</span>, <span class="hljs-built_in">cap</span> <span class="hljs-typename">int</span>
|
||||
}
|
||||
</code></pre>
|
||||
<p>我們的appendInt函數每次隻能向slice追加一個元素,但是內置的append函數則可以追加多個元素,甚至追加一個slice。</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">var</span> x []<span class="hljs-typename">int</span>
|
||||
x = <span class="hljs-built_in">append</span>(x, <span class="hljs-number">1</span>)
|
||||
x = <span class="hljs-built_in">append</span>(x, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>)
|
||||
x = <span class="hljs-built_in">append</span>(x, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>)
|
||||
x = <span class="hljs-built_in">append</span>(x, x...) <span class="hljs-comment">// append the slice x</span>
|
||||
fmt.Println(x) <span class="hljs-comment">// "[1 2 3 4 5 6 1 2 3 4 5 6]"</span>
|
||||
</code></pre>
|
||||
<p>通過下面的小脩改,我們可以可以達到append函數類似的功能。其中在appendInt函數參數中的最後的“...”省略號表示接收變長的參數爲slice。我們將在5.7節詳細解釋這個特性。</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">func</span> appendInt(x []<span class="hljs-typename">int</span>, y ...<span class="hljs-typename">int</span>) []<span class="hljs-typename">int</span> {
|
||||
<span class="hljs-keyword">var</span> z []<span class="hljs-typename">int</span>
|
||||
zlen := <span class="hljs-built_in">len</span>(x) + <span class="hljs-built_in">len</span>(y)
|
||||
<span class="hljs-comment">// ...expand z to at least zlen...</span>
|
||||
<span class="hljs-built_in">copy</span>(z[<span class="hljs-built_in">len</span>(x):], y)
|
||||
<span class="hljs-keyword">return</span> z
|
||||
}
|
||||
</code></pre>
|
||||
<p>爲了避免重複,和前面相同的代碼併沒有顯示。</p>
|
||||
<h3 id="422-slice內存技巧">4.2.2. Slice內存技巧</h3>
|
||||
<p>TODO</p>
|
||||
<p>讓我們看看更多的例子,比如镟轉slice、反轉slice或在slice原有內存空間脩改元素。給定一個字符串列表,下面的nonempty函數將在原有slice內存空間之上返迴不包含空字符串的列表:</p>
|
||||
<pre><code class="lang-Go">gopl.io/ch4/nonempty
|
||||
|
||||
<span class="hljs-comment">// Nonempty is an example of an in-place slice algorithm.</span>
|
||||
<span class="hljs-keyword">package</span> main
|
||||
|
||||
<span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>
|
||||
|
||||
<span class="hljs-comment">// nonempty returns a slice holding only the non-empty strings.</span>
|
||||
<span class="hljs-comment">// The underlying array is modified during the call.</span>
|
||||
<span class="hljs-keyword">func</span> nonempty(strings []<span class="hljs-typename">string</span>) []<span class="hljs-typename">string</span> {
|
||||
i := <span class="hljs-number">0</span>
|
||||
<span class="hljs-keyword">for</span> _, s := <span class="hljs-keyword">range</span> strings {
|
||||
<span class="hljs-keyword">if</span> s != <span class="hljs-string">""</span> {
|
||||
strings[i] = s
|
||||
i++
|
||||
}
|
||||
}
|
||||
<span class="hljs-keyword">return</span> strings[:i]
|
||||
}
|
||||
</code></pre>
|
||||
<p>比較微妙的地方是,輸入的slice和輸出的slice共享一個底層數組。這可以避免分配另一個數組,不過原來的數據將可能會被覆蓋,正如下面兩個打印語句看到的那樣:</p>
|
||||
<pre><code class="lang-Go">data := []<span class="hljs-typename">string</span>{<span class="hljs-string">"one"</span>, <span class="hljs-string">""</span>, <span class="hljs-string">"three"</span>}
|
||||
fmt.Printf(<span class="hljs-string">"%q\n"</span>, nonempty(data)) <span class="hljs-comment">// `["one" "three"]`</span>
|
||||
fmt.Printf(<span class="hljs-string">"%q\n"</span>, data) <span class="hljs-comment">// `["one" "three" "three"]`</span>
|
||||
</code></pre>
|
||||
<p>因此我們通常會這樣使用nonempty函數:data = nonempty(data)。</p>
|
||||
<p>nonempty函數也可以使用append函數實現:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">func</span> nonempty2(strings []<span class="hljs-typename">string</span>) []<span class="hljs-typename">string</span> {
|
||||
out := strings[:<span class="hljs-number">0</span>] <span class="hljs-comment">// zero-length slice of original</span>
|
||||
<span class="hljs-keyword">for</span> _, s := <span class="hljs-keyword">range</span> strings {
|
||||
<span class="hljs-keyword">if</span> s != <span class="hljs-string">""</span> {
|
||||
out = <span class="hljs-built_in">append</span>(out, s)
|
||||
}
|
||||
}
|
||||
<span class="hljs-keyword">return</span> out
|
||||
}
|
||||
</code></pre>
|
||||
<p>無論如何實現,以這種方式重用一個slice一般要求最多爲每個輸入值産生一個輸出值,事實上很多算法都是用來過濾或合併序列中相鄰的元素。這種slice用法是比較複雜的技巧,雖然使用到了slice的一些黑魔法,但是對於某些場合是比較清晰和有效的。</p>
|
||||
<p>一個slice可以原來實現一個stack。最初給定的空slice對應一個空的stack,然後可以使用append函數將新的值壓入stack:</p>
|
||||
<pre><code class="lang-Go">stack = <span class="hljs-built_in">append</span>(stack, v) <span class="hljs-comment">// push v</span>
|
||||
</code></pre>
|
||||
<p>stack的頂部位置對應slice的最後一個元素:</p>
|
||||
<pre><code class="lang-Go">top := stack[<span class="hljs-built_in">len</span>(stack)-<span class="hljs-number">1</span>] <span class="hljs-comment">// top of stack</span>
|
||||
</code></pre>
|
||||
<p>通過收縮stack可以彈出棧頂的元素</p>
|
||||
<pre><code class="lang-Go">stack = stack[:<span class="hljs-built_in">len</span>(stack)-<span class="hljs-number">1</span>] <span class="hljs-comment">// pop</span>
|
||||
</code></pre>
|
||||
<p>要刪除slice中間的某個元素併保存原有的元素順序,可以通過內置的copy函數將後面的子slice向前一位複雜完成:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">func</span> remove(slice []<span class="hljs-typename">int</span>, i <span class="hljs-typename">int</span>) []<span class="hljs-typename">int</span> {
|
||||
<span class="hljs-built_in">copy</span>(slice[i:], slice[i+<span class="hljs-number">1</span>:])
|
||||
<span class="hljs-keyword">return</span> slice[:<span class="hljs-built_in">len</span>(slice)-<span class="hljs-number">1</span>]
|
||||
}
|
||||
|
||||
<span class="hljs-keyword">func</span> main() {
|
||||
s := []<span class="hljs-typename">int</span>{<span class="hljs-number">5</span>, <span class="hljs-number">6</span>, <span class="hljs-number">7</span>, <span class="hljs-number">8</span>, <span class="hljs-number">9</span>}
|
||||
fmt.Println(remove(s, <span class="hljs-number">2</span>)) <span class="hljs-comment">// "[5 6 8 9]"</span>
|
||||
}
|
||||
</code></pre>
|
||||
<p>如果刪除元素後不用保存原來順序的話,我們可以簡單的用最後一個元素覆蓋被刪除的元素:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">func</span> remove(slice []<span class="hljs-typename">int</span>, i <span class="hljs-typename">int</span>) []<span class="hljs-typename">int</span> {
|
||||
slice[i] = slice[<span class="hljs-built_in">len</span>(slice)-<span class="hljs-number">1</span>]
|
||||
<span class="hljs-keyword">return</span> slice[:<span class="hljs-built_in">len</span>(slice)-<span class="hljs-number">1</span>]
|
||||
}
|
||||
|
||||
<span class="hljs-keyword">func</span> main() {
|
||||
s := []<span class="hljs-typename">int</span>{<span class="hljs-number">5</span>, <span class="hljs-number">6</span>, <span class="hljs-number">7</span>, <span class="hljs-number">8</span>, <span class="hljs-number">9</span>}
|
||||
fmt.Println(remove(s, <span class="hljs-number">2</span>)) <span class="hljs-comment">// "[5 6 9 8]</span>
|
||||
}
|
||||
</code></pre>
|
||||
<p><strong>練習 4.3:</strong> 重寫reverse函數,使用數組指針代替slice。</p>
|
||||
<p><strong>練習 4.4:</strong> 編寫一個rotate函數,通過一次循環完成镟轉。</p>
|
||||
<p><strong>練習 4.5:</strong> 寫一個函數在原地完成消除[]string中相鄰重複的字符串的操作。</p>
|
||||
<p><strong>練習 4.6:</strong> 編寫一個函數,原地將一個UTF-8編碼的[]byte類型的slice中相鄰的空格(參考unicode.IsSpace)替換成一個空格返迴</p>
|
||||
<p><strong>練習 4.7:</strong> 脩改reverse函數用於原地反轉UTF-8編碼的[]byte。是否可以不用分配額外的內存?</p>
|
||||
|
||||
|
||||
</section>
|
||||
@@ -2042,7 +2283,7 @@
|
||||
<a href="../ch4/ch4-01.html" class="navigation navigation-prev " aria-label="Previous page: 數組"><i class="fa fa-angle-left"></i></a>
|
||||
|
||||
|
||||
<a href="../ch4/ch4-03.html" class="navigation navigation-next " aria-label="Next page: 字典"><i class="fa fa-angle-right"></i></a>
|
||||
<a href="../ch4/ch4-03.html" class="navigation navigation-next " aria-label="Next page: Map"><i class="fa fa-angle-right"></i></a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user