[TOC]
## 7.6.1 從字符串生成字節切片
假設 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:y? 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](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/exercises/chapter_4/count_characters.go))
您還可以將一個字符串追加到某一個字符數組的尾部:
~~~
var b []byte
var s string
b = append(b, s...)
~~~
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/07.6.md#762-獲取字符串的某一部分)7.6.2 獲取字符串的某一部分
使用?`substr := str[start:end]`?可以從字符串 str 獲取到從索引 start 開始到?`end-1`?位置的子字符串。同樣的,`str[start:]`?則表示獲取從 start 開始到?`len(str)-1`?位置的子字符串。而?`str[:end]`?表示獲取從 0 開始到?`end-1`的子字符串。
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/07.6.md#763-字符串和切片的內存結構)7.6.3 字符串和切片的內存結構
在內存中,一個字符串實際上是一個雙字結構,即一個指向實際數據的指針和記錄字符串長度的整數(見圖 7.4)。因為指針對用戶來說是完全不可見,因此我們可以依舊把字符串看做是一個值類型,也就是一個字符數組。
字符串?`string s = "hello"`?和子字符串?`t = s[2:3]`?在內存中的結構可以用下圖表示:
[](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/images/7.6_fig7.4.png)
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/07.6.md#764-修改字符串中的某個字符)7.6.4 修改字符串中的某個字符
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"
~~~
所以,您可以通過操作切片來完成對字符串的操作。
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/07.6.md#765-字節數組對比函數)7.6.5 字節數組對比函數
下面的?`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 // 數組相等
}
~~~
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/07.6.md#766-搜索及排序切片和數組)7.6.6 搜索及排序切片和數組
標準庫提供了?`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
~~~
您可以通過查看?[官方文檔](http://golang.org/pkg/sort/)?來獲取更詳細的信息。
這就是如何使用?`sort`?包的方法,我們會在第 11.6 節對它的細節進行深入,并實現一個屬于我們自己的版本。
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/07.6.md#767-append-函數常見操作)7.6.7 append 函數常見操作
我們在第 7.5 節提到的 append 非常有用,它能夠用于各種方面的操作:
1. 將切片 b 的元素追加到切片 a 之后:`a = append(a, b...)`
2. 復制切片 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 擴展 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 操作來表示任意可變長度的序列。
從數學的角度來看,切片相當于向量,如果需要的話可以定義一個向量作為切片的別名來進行操作。
如果您需要更加完整的方案,可以學習一下 Eleanor McHugh 編寫的幾個包:[slices](http://github.com/feyeleanor/slices)、[chain](http://github.com/feyeleanor/chain)?和?[lists](http://github.com/feyeleanor/lists)。
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/07.6.md#768-切片和垃圾回收)7.6.8 切片和垃圾回收
切片的底層指向一個數組,該數組的實際體積可能要大于切片所定義的體積。只有在沒有任何切片指向的時候,底層的數組內層才會被釋放,這種特性有時會導致程序占用多余的內存。
**示例**?函數?`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
}
~~~
**練習 7.12**
編寫一個函數,要求其接受兩個參數,原始字符串 str 和分割索引 i,然后返回兩個分割后的字符串。
**練習 7.13**
假設有字符串 str,那么?`str[len(str)/2:] + str[:len(str)/2]`?的結果是什么?
**練習 7.14**
編寫一個程序,要求能夠反轉字符串,即將 “Google” 轉換成 “elgooG”(提示:使用?`[]byte`?類型的切片)。
如果您使用兩個切片來實現反轉,請再嘗試使用一個切片(提示:使用交換法)。
如果您想要反轉 Unicode 編碼的字符串,請使用?`[]int`?類型的切片。
**練習 7.15**
編寫一個程序,要求能夠遍歷一個數組的字符,并將當前字符和前一個字符不相同的字符拷貝至另一個數組。
**練習 7.16**
編寫一個程序,使用冒泡排序的方法排序一個包含整數的切片(算法的定義可參考?[維基百科](http://en.wikipedia.org/wiki/Bubble_sort))。
**練習 7.17**
在函數式編程語言中,一個 map-function 是指能夠接受一個函數原型和一個列表,并使用列表中的值依次執行函數原型,公式為:`map ( F(), (e1,e2, . . . ,en) ) = ( F(e1), F(e2), ... F(en) )`。
編寫一個函數?`mapFunc`?要求接受以下 2 個參數:
* 一個將整數乘以 10 的函數
* 一個整數列表
最后返回保存運行結果的整數列表。
- 前言
- 第一部分:學習 Go 語言
- 第1章:Go 語言的起源,發展與普及
- 1.1 起源與發展
- 1.2 語言的主要特性與發展的環境和影響因素
- 第2章:安裝與運行環境
- 2.1 平臺與架構
- 2.2 Go 環境變量
- 2.3 在 Linux 上安裝 Go
- 2.4 在 Mac OS X 上安裝 Go
- 2.5 在 Windows 上安裝 Go
- 2.6 安裝目錄清單
- 2.7 Go 運行時(runtime)
- 2.8 Go 解釋器
- 第3章:編輯器、集成開發環境與其它工具
- 3.1 Go 開發環境的基本要求
- 3.2 編輯器和集成開發環境
- 3.3 調試器
- 3.4 構建并運行 Go 程序
- 3.5 格式化代碼
- 3.6 生成代碼文檔
- 3.7 其它工具
- 3.8 Go 性能說明
- 3.9 與其它語言進行交互
- 第二部分:語言的核心結構與技術
- 第4章:基本結構和基本數據類型
- 4.1 文件名、關鍵字與標識符
- 4.2 Go 程序的基本結構和要素
- 4.3 常量
- 4.4 變量
- 4.5 基本類型和運算符
- 4.6 字符串
- 4.7 strings 和 strconv 包
- 4.8 時間和日期
- 4.9 指針
- 第5章:控制結構
- 5.1 if-else 結構
- 5.2 測試多返回值函數的錯誤
- 5.3 switch 結構
- 5.4 for 結構
- 5.5 Break 與 continue
- 5.6 標簽與 goto
- 第6章:函數(function)
- 6.1 介紹
- 6.2 函數參數與返回值
- 6.3 傳遞變長參數
- 6.4 defer 和追蹤
- 6.5 內置函數
- 6.6 遞歸函數
- 6.7 將函數作為參數
- 6.8 閉包
- 6.9 應用閉包:將函數作為返回值
- 6.10 使用閉包調試
- 6.11 計算函數執行時間
- 6.12 通過內存緩存來提升性能
- 第7章:數組與切片
- 7.1 聲明和初始化
- 7.2 切片
- 7.3 For-range 結構
- 7.4 切片重組(reslice)
- 7.5 切片的復制與追加
- 7.6 字符串、數組和切片的應用
- 第8章:Map
- 8.1 聲明、初始化和 make
- 8.2 測試鍵值對是否存在及刪除元素
- 8.3 for-range 的配套用法
- 8.4 map 類型的切片
- 8.5 map 的排序
- 8.6 將 map 的鍵值對調
- 第9章:包(package)
- 9.1 標準庫概述
- 9.2 regexp 包
- 9.3 鎖和 sync 包
- 9.4 精密計算和 big 包
- 9.5 自定義包和可見性
- 9.6 為自定義包使用 godoc
- 9.7 使用 go install 安裝自定義包
- 9.8 自定義包的目錄結構、go install 和 go test
- 9.9 通過 Git 打包和安裝
- 9.10 Go 的外部包和項目
- 9.11 在 Go 程序中使用外部庫
- 第10章:結構(struct)與方法(method)
- 10.1 結構體定義
- 10.2 使用工廠方法創建結構體實例
- 10.3 使用自定義包中的結構體
- 10.4 帶標簽的結構體
- 10.5 匿名字段和內嵌結構體
- 10.6 方法
- 10.8 垃圾回收和 SetFinalizer
- 第11章:接口(interface)與反射(reflection)
- 11.1 接口是什么
- 11.2 接口嵌套接口
- 11.3 類型斷言:如何檢測和轉換接口變量的類型
- 11.4 類型判斷:type-switch
- 11.5 測試一個值是否實現了某個接口
- 11.6 使用方法集與接口
- 11.7 第一個例子:使用 Sorter 接口排序
- 11.8 第二個例子:讀和寫
- 11.9 空接口
- 11.10 反射包
- 第三部分:Go 高級編程
- 第12章 讀寫數據
- 12.1 讀取用戶的輸入
- 12.2 文件讀寫
- 12.3 文件拷貝
- 12.4 從命令行讀取參數
- 12.5 用buffer讀取文件
- 12.6 用切片讀寫文件
- 12.7 用 defer 關閉文件
- 12.8 使用接口的實際例子:fmt.Fprintf
- 12.9 Json 數據格式
- 12.10 XML 數據格式
- 12.11 用 Gob 傳輸數據
- 12.12 Go 中的密碼學
- 第13章 錯誤處理與測試
- 13.1 錯誤處理
- 13.2 運行時異常和 panic
- 13.3 從 panic 中恢復(Recover)
- 13.4 自定義包中的錯誤處理和 panicking
- 13.5 一種用閉包處理錯誤的模式
- 13.6 啟動外部命令和程序
- 13.7 Go 中的單元測試和基準測試
- 13.8 測試的具體例子
- 13.9 用(測試數據)表驅動測試
- 13.10 性能調試:分析并優化 Go 程序
- 第14章:協程(goroutine)與通道(channel)
- 14.1 并發、并行和協程
- 14.2 使用通道進行協程間通信
- 14.3 協程同步:關閉通道-對阻塞的通道進行測試
- 14.4 使用 select 切換協程
- 14.5 通道,超時和計時器(Ticker)
- 14.6 協程和恢復(recover)
- 第15章:網絡、模版與網頁應用
- 15.1 tcp服務器
- 15.2 一個簡單的web服務器
- 15.3 訪問并讀取頁面數據
- 15.4 寫一個簡單的網頁應用
- 第四部分:實際應用
- 第16章:常見的陷阱與錯誤
- 16.1 誤用短聲明導致變量覆蓋
- 16.2 誤用字符串
- 16.3 發生錯誤時使用defer關閉一個文件
- 16.5 不需要將一個指向切片的指針傳遞給函數
- 16.6 使用指針指向接口類型
- 16.7 使用值類型時誤用指針
- 16.8 誤用協程和通道
- 16.9 閉包和協程的使用
- 16.10 糟糕的錯誤處理
- 第17章:模式
- 17.1 關于逗號ok模式
- 第18章:出于性能考慮的實用代碼片段
- 18.1 字符串
- 18.2 數組和切片
- 18.3 映射
- 18.4 結構體
- 18.5 接口
- 18.6 函數
- 18.7 文件
- 18.8 協程(goroutine)與通道(channel)
- 18.9 網絡和網頁應用
- 18.10 其他
- 18.11 出于性能考慮的最佳實踐和建議
- 附錄