假設(shè) s 是一個字符串(本質(zhì)上是一個字節(jié)數(shù)組),那么就可以直接通過 c := []byte(s)
來獲取一個字節(jié)的切片 c。另外,您還可以通過 copy 函數(shù)來達(dá)到相同的目的: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 個字節(jié),有些甚至需要 3 個或者 4 個字節(jié)來進(jìn)行表示。如果發(fā)現(xiàn)錯誤的 UTF8 字符,則該字符會被設(shè)置為 U+FFFD 并且索引向前移動一個字節(jié)。和字符串轉(zhuǎn)換一樣,您同樣可以使用 c := []int32(s)
語法,這樣切片中的每個 int 都會包含對應(yīng)的 Unicode 代碼,因?yàn)樽址械拿看巫址紩?yīng)一個整數(shù)。類似的,您也可以將字符串轉(zhuǎn)換為元素類型為 rune 的切片:r := []rune(s)
。
可以通過代碼 len([]int32(s))
來獲得字符串中字符的數(shù)量,但使用 utf8.RuneCountInString(s)
效率會更高一點(diǎn)。(參考count_characters.go)
您還可以將一個字符串追加到某一個字符數(shù)組的尾部:
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
的子字符串。
在內(nèi)存中,一個字符串實(shí)際上是一個雙字結(jié)構(gòu),即一個指向?qū)嶋H數(shù)據(jù)的指針和記錄字符串長度的整數(shù)(見圖 7.4)。因?yàn)橹羔槍τ脩魜碚f是完全不可見,因此我們可以依舊把字符串看做是一個值類型,也就是一個字符數(shù)組。
字符串 string s = "hello"
和子字符串 t = s[2:3]
在內(nèi)存中的結(jié)構(gòu)可以用下圖表示:
http://wiki.jikexueyuan.com/project/the-way-to-go/images/7.6_fig7.4.png" alt="" />
Go 語言中的字符串是不可變的,也就是說 str[index]
這樣的表達(dá)式是不可以被放在等號左側(cè)的。如果嘗試運(yùn)行 str[i] = 'D'
會得到錯誤:cannot assign to str[i]
。
因此,您必須先將字符串轉(zhuǎn)換成字節(jié)數(shù)組,然后再通過修改數(shù)組中的元素值來達(dá)到修改字符串的目的,最后將字節(jié)數(shù)組轉(zhuǎn)換回字符串格式。
例如,將字符串 "hello" 轉(zhuǎn)換為 "cello":
s := "hello"
c := []byte(s)
c[0] = 'c'
s2 := string(c) // s2 == "cello"
所以,您可以通過操作切片來完成對字符串的操作。
下面的 Compare
函數(shù)會返回兩個字節(jié)數(shù)組字典順序的整數(shù)對比結(jié)果,即 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
}
}
// 數(shù)組的長度可能不同
switch {
case len(a) < len(b):
return -1
case len(a) > len(b):
return 1
}
return 0 // 數(shù)組相等
}
標(biāo)準(zhǔn)庫提供了 sort
包來實(shí)現(xiàn)常見的搜索和排序操作。您可以使用 sort
包中的函數(shù) func Ints(a []int)
來實(shí)現(xiàn)對 int 類型的切片排序。例如 sort.Ints(arri)
,其中變量 arri 就是需要被升序排序的數(shù)組或切片。為了檢查某個數(shù)組是否已經(jīng)被排序,可以通過函數(shù) IntsAreSorted(a []int) bool
來檢查,如果返回 true 則表示已經(jīng)被排序。
類似的,可以使用函數(shù) func Float64s(a []float64)
來排序 float64 的元素,或使用函數(shù) func Strings(a []string)
排序字符串元素。
想要在數(shù)組或切片中搜索一個元素,該數(shù)組或切片必須先被排序(因?yàn)闃?biāo)準(zhǔn)庫的搜索算法使用的是二分法)。然后,您就可以使用函數(shù) func SearchInts(a []int, n int) int
進(jìn)行搜索,并返回對應(yīng)結(jié)果的索引值。
當(dāng)然,還可以搜索 float64 和字符串:
func SearchFloat64s(a []float64, x float64) int
func SearchStrings(a []string, x string) int
您可以通過查看 官方文檔 來獲取更詳細(xì)的信息。
這就是如何使用 sort
包的方法,我們會在第 11.6 節(jié)對它的細(xì)節(jié)進(jìn)行深入,并實(shí)現(xiàn)一個屬于我們自己的版本。
我們在第 7.5 節(jié)提到的 append 非常有用,它能夠用于各種方面的操作:
a = append(a, b...)
復(fù)制切片 a 的元素到新的切片 b 上:
b = make([]T, len(a))
copy(b, a)
a = append(a[:i], a[i+1:]...)
a = append(a[:i], a[j:]...)
a = append(a, make([]T, j)...)
a = append(a[:i], append([]T{x}, a[i:]...)...)
a = append(a[:i], append(make([]T, j), a[i:]...)...)
a = append(a[:i], append(b, a[i:]...)...)
x, a = a[len(a)-1], a[:len(a)-1]
a = append(a, x)
因此,您可以使用切片和 append 操作來表示任意可變長度的序列。
從數(shù)學(xué)的角度來看,切片相當(dāng)于向量,如果需要的話可以定義一個向量作為切片的別名來進(jìn)行操作。
如果您需要更加完整的方案,可以學(xué)習(xí)一下 Eleanor McHugh 編寫的幾個包:slices、chain 和 lists。
切片的底層指向一個數(shù)組,該數(shù)組的實(shí)際容量可能要大于切片所定義的容量。只有在沒有任何切片指向的時候,底層的數(shù)組內(nèi)存才會被釋放,這種特性有時會導(dǎo)致程序占用多余的內(nèi)存。
示例 函數(shù) FindDigits
將一個文件加載到內(nèi)存,然后搜索其中所有的數(shù)字并返回一個切片。
var digitRegexp = regexp.MustCompile("[0-9]+")
func FindDigits(filename string) []byte {
b, _ := ioutil.ReadFile(filename)
return digitRegexp.Find(b)
}
這段代碼可以順利運(yùn)行,但返回的 []byte
指向的底層是整個文件的數(shù)據(jù)。只要該返回的切片不被釋放,垃圾回收器就不能釋放整個文件所占用的內(nèi)存。換句話說,一點(diǎn)點(diǎn)有用的數(shù)據(jù)卻占用了整個文件的內(nèi)存。
想要避免這個問題,可以通過拷貝我們需要的部分到一個新的切片中:
func FindDigits(filename string) []byte {
b, _ := ioutil.ReadFile(filename)
b = digitRegexp.Find(b)
c := make([]byte, len(b))
copy(c, b)
return c
}
事實(shí)上,上面這段代碼只能找到第一個匹配正則表達(dá)式的數(shù)字串。要想找到所有的數(shù)字,可以嘗試下面這段代碼:
func FindFileDigits(filename string) []byte {
? fileBytes, _ := ioutil.ReadFile(filename)
b := digitRegexp.FindAll(fileBytes, len(fileBytes))
c := make([]byte, 0)
for _, bytes := range b {
c = append(c, bytes...)
}
return c
}
練習(xí) 7.12
編寫一個函數(shù),要求其接受兩個參數(shù),原始字符串 str 和分割索引 i,然后返回兩個分割后的字符串。
練習(xí) 7.13
假設(shè)有字符串 str,那么 str[len(str)/2:] + str[:len(str)/2]
的結(jié)果是什么?
練習(xí) 7.14
編寫一個程序,要求能夠反轉(zhuǎn)字符串,即將 “Google” 轉(zhuǎn)換成 “elgooG”(提示:使用 []byte
類型的切片)。
如果您使用兩個切片來實(shí)現(xiàn)反轉(zhuǎn),請再嘗試使用一個切片(提示:使用交換法)。
如果您想要反轉(zhuǎn) Unicode 編碼的字符串,請使用 []int32
類型的切片。
練習(xí) 7.15
編寫一個程序,要求能夠遍歷一個數(shù)組的字符,并將當(dāng)前字符和前一個字符不相同的字符拷貝至另一個數(shù)組。
練習(xí) 7.16
編寫一個程序,使用冒泡排序的方法排序一個包含整數(shù)的切片(算法的定義可參考 維基百科)。
練習(xí) 7.17
在函數(shù)式編程語言中,一個 map-function 是指能夠接受一個函數(shù)原型和一個列表,并使用列表中的值依次執(zhí)行函數(shù)原型,公式為:map ( F(), (e1,e2, . . . ,en) ) = ( F(e1), F(e2), ... F(en) )
。
編寫一個函數(shù) mapFunc
要求接受以下 2 個參數(shù):
最后返回保存運(yùn)行結(jié)果的整數(shù)列表。