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