鍍金池/ 教程/ GO/ 7.6 字符串、數(shù)組和切片的應(yīng)用
4.7 strings 和 strconv 包
13.6 啟動外部命令和程序
?# 11.4 類型判斷:type-switch
12.1 讀取用戶的輸入
10.6 方法
12.2 文件讀寫
13 錯誤處理與測試
9.3 鎖和 sync 包
12.3 文件拷貝
?# 11.7 第一個例子:使用 Sorter 接口排序
?# 11.5 測試一個值是否實(shí)現(xiàn)了某個接口
6.4 defer 和追蹤
12.10 XML 數(shù)據(jù)格式
13.10 性能調(diào)試:分析并優(yōu)化 Go 程序
?# 11.1 接口是什么
2.2 Go 環(huán)境變量
2.6 安裝目錄清單
2.5 在 Windows 上安裝 Go
11.11 Printf 和反射
1.2 語言的主要特性與發(fā)展的環(huán)境和影響因素
9.0 包(package)
7.4 切片重組(reslice)
13.2 運(yùn)行時異常和 panic
10.2 使用工廠方法創(chuàng)建結(jié)構(gòu)體實(shí)例
12.8 使用接口的實(shí)際例子:fmt.Fprintf
2.4 在 Mac OS X 上安裝 Go
3.8 Go 性能說明
7.2 切片
8.0 Map
3.1 Go 開發(fā)環(huán)境的基本要求
5.6 標(biāo)簽與 goto
6.10 使用閉包調(diào)試
9.5 自定義包和可見性
4.3 常量
?# 11.2 接口嵌套接口
6.5 內(nèi)置函數(shù)
前言
10.8 垃圾回收和 SetFinalizer
2.8 Go 解釋器
13.7 Go 中的單元測試和基準(zhǔn)測試
6.8 閉包
4.9 指針
13.1 錯誤處理
10.1 結(jié)構(gòu)體定義
5.1 if-else 結(jié)構(gòu)
6.6 遞歸函數(shù)
9.9 通過 Git 打包和安裝
2.7 Go 運(yùn)行時(runtime)
10.7 類型的 String() 方法和格式化描述符
3.7 其它工具
9.6 為自定義包使用 godoc
11.12 接口與動態(tài)類型
13.3 從 panic 中恢復(fù)(Recover)
10.3 使用自定義包中的結(jié)構(gòu)體
11.14 結(jié)構(gòu)體、集合和高階函數(shù)
3.6 生成代碼文檔
9.2 regexp 包
4.1 文件名、關(guān)鍵字與標(biāo)識符
?# 11.6 使用方法集與接口
7.0 數(shù)組與切片
7.1 聲明和初始化
12.11 用 Gob 傳輸數(shù)據(jù)
5.5 Break 與 continue
1.1 起源與發(fā)展
?# 11 接口(Interfaces)與反射(reflection)
6.9 應(yīng)用閉包:將函數(shù)作為返回值
4.2 Go 程序的基本結(jié)構(gòu)和要素
8.6 將 map 的鍵值對調(diào)
6.11 計算函數(shù)執(zhí)行時間
5.0 控制結(jié)構(gòu)
10.5 匿名字段和內(nèi)嵌結(jié)構(gòu)體
4.6 字符串
3.0 編輯器、集成開發(fā)環(huán)境與其它工具
13.8 測試的具體例子
7.6 字符串、數(shù)組和切片的應(yīng)用
8.4 map 類型的切片
3.9 與其它語言進(jìn)行交互
7.3 For-range 結(jié)構(gòu)
9.7 使用 go install 安裝自定義包
6.0 函數(shù)
9.8 自定義包的目錄結(jié)構(gòu)、go install 和 go test
6.3 傳遞變長參數(shù)
13.9 用(測試數(shù)據(jù))表驅(qū)動測試
11.9 空接口
8.1 聲明、初始化和 make
6.2 函數(shù)參數(shù)與返回值
9.11 在 Go 程序中使用外部庫
3.3 調(diào)試器
4.5 基本類型和運(yùn)算符
?# 11.8 第二個例子:讀和寫
12.5 用 buffer 讀取文件
總結(jié):Go 中的面向?qū)ο?/span>
11.10 反射包
12.7 用 defer 關(guān)閉文件
9.4 精密計算和 big 包
4.4 變量
6.1 介紹
13.4 自定義包中的錯誤處理和 panicking
12.4 從命令行讀取參數(shù)
9.10 Go 的外部包和項(xiàng)目
8.3 for-range 的配套用法
3.5 格式化代碼
10.4 帶標(biāo)簽的結(jié)構(gòu)體
7.5 切片的復(fù)制與追加
?# 11.3 類型斷言:如何檢測和轉(zhuǎn)換接口變量的類型
5.4 for 結(jié)構(gòu)
4.8 時間和日期
2.3 在 Linux 上安裝 Go
12 讀寫數(shù)據(jù)
6.12 通過內(nèi)存緩存來提升性能
9.1 標(biāo)準(zhǔn)庫概述
12.6 用切片讀寫文件
10 結(jié)構(gòu)(struct)與方法(method)
8.5 map 的排序
12.9 JSON 數(shù)據(jù)格式
13.5 一種用閉包處理錯誤的模式
3.2 編輯器和集成開發(fā)環(huán)境
12.12 Go 中的密碼學(xué)
5.2 測試多返回值函數(shù)的錯誤
6.7 將函數(shù)作為參數(shù)
8.2 測試鍵值對是否存在及刪除元素
3.4 構(gòu)建并運(yùn)行 Go 程序
2.1 平臺與架構(gòu)
5.3 switch 結(jié)構(gòu)

7.6 字符串、數(shù)組和切片的應(yīng)用

7.6.1 從字符串生成字節(jié)切片

假設(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...)

7.6.2 獲取字符串的某一部分

使用 substr := str[start:end] 可以從字符串 str 獲取到從索引 start 開始到 end-1 位置的子字符串。同樣的,str[start:] 則表示獲取從 start 開始到 len(str)-1 位置的子字符串。而 str[:end] 表示獲取從 0 開始到 end-1 的子字符串。

7.6.3 字符串和切片的內(nèi)存結(jié)構(gòu)

在內(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="" />

7.6.4 修改字符串中的某個字符

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"

所以,您可以通過操作切片來完成對字符串的操作。

7.6.5 字節(jié)數(shù)組對比函數(shù)

下面的 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ù)組相等
}

7.6.6 搜索及排序切片和數(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.6.7 append 函數(shù)常見操作

我們在第 7.5 節(jié)提到的 append 非常有用,它能夠用于各種方面的操作:

  1. 將切片 b 的元素追加到切片 a 之后:a = append(a, b...)
  2. 復(fù)制切片 a 的元素到新的切片 b 上:

      b = make([]T, len(a))
      copy(b, a)
  3. 刪除位于索引 i 的元素:a = append(a[:i], a[i+1:]...)
  4. 切除切片 a 中從索引 i 至 j 位置的元素:a = append(a[:i], a[j:]...)
  5. 為切片 a 擴(kuò)展 j 個元素長度:a = append(a, make([]T, j)...)
  6. 在索引 i 的位置插入元素 x:a = append(a[:i], append([]T{x}, a[i:]...)...)
  7. 在索引 i 的位置插入長度為 j 的新切片:a = append(a[:i], append(make([]T, j), a[i:]...)...)
  8. 在索引 i 的位置插入切片 b 的所有元素:a = append(a[:i], append(b, a[i:]...)...)
  9. 取出位于切片 a 最末尾的元素 x:x, a = a[len(a)-1], a[:len(a)-1]
  10. 將元素 x 追加到切片 a:a = append(a, x)

因此,您可以使用切片和 append 操作來表示任意可變長度的序列。

從數(shù)學(xué)的角度來看,切片相當(dāng)于向量,如果需要的話可以定義一個向量作為切片的別名來進(jìn)行操作。

如果您需要更加完整的方案,可以學(xué)習(xí)一下 Eleanor McHugh 編寫的幾個包:slices、chainlists。

7.6.8 切片和垃圾回收

切片的底層指向一個數(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ù):

  • 一個將整數(shù)乘以 10 的函數(shù)
  • 一個整數(shù)列表

最后返回保存運(yùn)行結(jié)果的整數(shù)列表。

鏈接