字元串、數組和切片的應用
從字元串生成位元組切片
假設 s 是一個字元串(本質上是一個位元組數組),那麼就可以直接通過
c := []bytes(s)
來擷取一個位元組的切片 c。另外,您還可以通過 copy 函數來達到相同的目的:
copy(dst []byte, src string)
。
同樣的,還可以使用 for-range 來獲得每個元素(Listing 7.13—for_string.go):
package main
import "fmt"
func main() {
s := "\u00ff\u754c"
for i, c := range s {
fmt.Printf("%d:%c ", i, c)
}
}
輸出:
0:ÿ 2:界
我們知道,Unicode 字元會占用 2 個位元組,有些甚至需要 3 個或者 4 個位元組來進行表示。如果發現錯誤的 UTF8 字元,則該字元會被設定為 U+FFFD 并且索引向前移動一個位元組。和字元串轉換一樣,您同樣可以使用
c := []int(s)
文法,這樣切片中的每個 int 都會包含對應的 Unicode 代碼,因為字元串中的每次字元都會對應一個整數。類似的,您也可以将字元串轉換為元素類型為 rune 的切片:
r := []rune(s)
。
可以通過代碼
len([]int(s))
來獲得字元串中字元的數量,但使用
utf8.RuneCountInString(s)
效率會更高一點。(參考count_characters.go)
您還可以将一個字元串追加到某一個字元數組的尾部:
var b []byte
var s string
b = append(b, s...)
擷取字元串的某一部分
使用
substr := str[start:end]
可以從字元串 str 擷取到從索引 start 開始到
end-1
位置的子字元串。同樣的,
str[start:]
則表示擷取從 start 開始到
len(str)-1
位置的子字元串。而
str[:end]
表示擷取從 0 開始到
end-1
的子字元串。
字元串和切片的記憶體結構
在記憶體中,一個字元串實際上是一個雙字結構,即一個指向實際資料的指針和記錄字元串長度的整數(見圖 7.4)。因為指針對使用者來說是完全不可見,是以我們可以依舊把字元串看做是一個值類型,也就是一個字元數組。
字元串
string s = "hello"
和子字元串
t = s[2:3]
在記憶體中的結構可以用下圖表示:

修改字元串中的某個字元
Go 語言中的字元串是不可變的,也就是說
str[index]
這樣的表達式是不可以被放在等号左側的。如果嘗試運作
str[i] = 'D'
會得到錯誤:
cannot assign to str[i]
。
是以,您必須先将字元串轉換成位元組數組,然後再通過修改數組中的元素值來達到修改字元串的目的,最後将位元組數組轉換回字元串格式。
例如,将字元串 "hello" 轉換為 "cello":
s := "hello"
c := []byte(s)
c[0] = ’c’
s2 := string(c) // s2 == "cello"
是以,您可以通過操作切片來完成對字元串的操作。
位元組數組對比函數
下面的
Compare
函數會傳回兩個位元組數組字典順序的整數對比結果,即
0 if a == b, -1 if a < b, 1 if a > b
。
func Compare(a, b[]byte) int {
for i:=0; i < len(a) && i < len(b); i++ {
switch {
case a[i] > b[i]:
return 1
case a[i] < b[i]:
return -1
}
}
// 數組的長度可能不同
switch {
case len(a) < len(b):
return -1
case len(a) > len(b):
return 1
}
return 0 // 數組相等
}
搜尋及排序切片和數組
标準庫提供了
sort
包來實作常見的搜尋和排序操作。您可以使用
sort
包中的函數
func Ints(a []int)
來實作對 int 類型的切片排序。例如
sort.Ints(arri)
,其中變量 arri 就是需要被升序排序的數組或切片。為了檢查某個數組是否已經被排序,可以通過函數
IntsAreSorted(a []int) bool
來檢查,如果傳回 true 則表示已經被排序。
類似的,可以使用函數
func Float64s(a []float64)
來排序 float64 的元素,或使用函數
func Strings(a []string)
排序字元串元素。
想要在數組或切片中搜尋一個元素,該數組或切片必須先被排序(因為标準庫的搜尋算法使用的是二分法)。然後,您就可以使用函數
func SearchInts(a []int, n int) int
進行搜尋,并傳回對應結果的索引值。
當然,還可以搜尋 float64 和字元串:
func SearchFloat64s(a []float64, x float64) int
func SearchStrings(a []string, x string) int
您可以通過檢視 官方文檔 來擷取更詳細的資訊。
這就是如何使用
sort
包的方法,我們會在第 11.6 節對它的細節進行深入,并實作一個屬于我們自己的版本。
append 函數常見操作
我們在第 7.5 節提到的 append 非常有用,它能夠用于各種方面的操作:
- 将切片 b 的元素追加到切片 a 之後:
a = append(a, b...)
- 複制切片 a 的元素到新的切片 b 上:
b = make([]T, len(a)) copy(b, a)
- 删除位于索引 i 的元素:
a = append(a[:i], a[i+1:]...)
- 切除切片 a 中從索引 i 至 j 位置的元素:
a = append(a[:i], a[j:]...)
- 為切片 a 擴充 j 個元素長度:
a = append(a, make([]T, j)...)
- 在索引 i 的位置插入元素 x:
a = append(a[:i], append([]T{x}, a[i:]...)...)
- 在索引 i 的位置插入長度為 j 的新切片:
a = append(a[:i], append(make([]T, j), a[i:]...)...)
- 在索引 i 的位置插入切片 b 的所有元素:
a = append(a[:i], append(b, a[i:]...)...)
- 取出位于切片 a 最末尾的元素 x:
x, a = a[len(a)-1], a[:len(a)-1]
- 将元素 x 追加到切片 a:
a = append(a, x)
是以,您可以使用切片和 append 操作來表示任意可變長度的序列。
從數學的角度來看,切片相當于向量,如果需要的話可以定義一個向量作為切片的别名來進行操作。
如果您需要更加完整的方案,可以學習一下 Eleanor McHugh 編寫的幾個包:slices、chain 和 lists。
切片和垃圾回收
切片的底層指向一個數組,該數組的實際體積可能要大于切片所定義的體積。隻有在沒有任何切片指向的時候,底層的數組内層才會被釋放,這種特性有時會導緻程式占用多餘的記憶體。
示例 函數
FindDigits
将一個檔案加載到記憶體,然後搜尋其中所有的數字并傳回一個切片。
var digitRegexp = regexp.MustCompile("[0-9]+")
func FindDigits(filename string) []byte {
b, _ := ioutil.ReadFile(filename)
return digitRegexp.Find(b)
}
這段代碼可以順利運作,但傳回的
[]byte
指向的底層是整個檔案的資料。隻要該傳回的切片不被釋放,垃圾回收器就不能釋放整個檔案所占用的記憶體。換句話說,一點點有用的資料卻占用了整個檔案的記憶體。
想要避免這個問題,可以通過拷貝我們需要的部分到一個新的切片中:
func FindDigits(filename string) []byte {
b, _ := ioutil.ReadFile(filename)
b = digitRegexp.Find(b)
c := make([]byte, len(b))
copy(c, b)
return c
}