課程地址 go-class-slides/xmas-2020 at trunk · matt4biz/go-class-slides (github.com) 主講老師 Matt Holiday 04-Strings Strings 字元串在 go 中都是 unicode ,unicode 是一種特 ...
課程地址 go-class-slides/xmas-2020 at trunk · matt4biz/go-class-slides (github.com)
主講老師 Matt Holiday
04-Strings
Strings
字元串在 go 中都是 unicode ,unicode 是一種特殊的技術用於表示國際通用字元。
rune 相當於 wide character,是 int32 的同義詞,四個位元組足夠大,任何 unicode、字元,邏輯字元 可以指向它。
但是為了讓程式更高效,我們不想一直用 4 個位元組表示每個字元,因為很多程式使用 ascii 字元。
因此有一種稱為 utf-8 編碼的 unicode 技術,以位元組 byte 表示 unicode 的簡便方法。
從物理角度上看,strings 就是 unicode 字元的 utf-8 編碼。
ascii characters 適合 0-127 的範圍
func main() {
s := "élite"
fmt.Printf("%8T %[1]v %d\n", s, len(s))
fmt.Printf("%8T %[1]v\n", []rune(s))
b := []byte(s)
fmt.Printf("%8T %[1]v %d\n", b, len(b))
}
string élite 6
[]int32 [233 108 105 116 101]
[]uint8 [195 169 108 105 116 101] 6
é 為 233 超出了 ascii 的表示範圍,由 2 個位元組表示,而不是為每個字元使用 4 個位元組,這是 utf8 編碼的效果。中文字經常為 20000 的數字,五個中文字會用 15 個位元組表示。
len(s) 顯示 6 的原因,在程式中字元串的長度是在 utf-8 中編碼字元串所必需的位元組字元串的長度
The length of a string in the program is the length of the byte string that's necessary to encode the string in utf-8,not the number of unicode characters
就是說給定一個字元串,把它進行 utf-8 編碼需要的位元組數量就是它的長度,而不是 unicode 字元的數量。
String structure
可以把圖片左邊的 s 理解為一個描述符(描述符不是指針、不是 go 的專業術語),它有指針和額外的信息(位元組數)。
go 字元串末尾沒有空位元組,很多編程語言通過迴圈字元串判斷空位元組獲取長度,效率並不高。在 go 中字元串長度直接保存在描述符中。
通過索引字元串創建 hello 的時候,hello 的 data 指向的是跟 s 描述符 data 的相同記憶體地址(共用存儲)。
因為字元串沒有空位元組,而且它們是不可變的,所以共用存儲是完全可行的。world 也是同理。它們重用 s 中的記憶體。
t := s
的結果是 t 將有與 s 一樣的內容,但是 t 跟 s 是不一樣的描述符。
b、c 與 s 共用存儲。
d 開闢了新的記憶體空間,存入了新的字元串。
s[5] = 'a' 出錯,字元串是不可變的,不能單獨修改字元串。
s +="es" 相當於 s = s + "es" ,開闢了新的記憶體空間,複製原有內容,再添加新內容,並使 data 指向新的記憶體地址。
原來的字元串並沒有改變、消失,因為 b、c 依舊指向原來的記憶體地址,s 指向了新開闢的記憶體地址。
String functions
s = strings.ToUpper(s)
字元串不允許被更改,所以會創建新字元串進行舊字元串的拷貝並大寫。由於開闢了新的記憶體空間,將返回值給 s 也就很好理解了。
如果沒有變數引用字元串,它會自動被垃圾回收。
Practice
做一個替換句子中指定單詞的程式
main.go
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
if len(os.Args) < 3 {
fmt.Fprintln(os.Stderr, "not enough args")
os.Exit(-1)
}
old, new := os.Args[1], os.Args[2]
scan := bufio.NewScanner(os.Stdin)
for scan.Scan() {
s := strings.Split(scan.Text(), old)
t := strings.Join(s, new)
fmt.Println(t)
}
}
os.Args
運行 go 程式時附加的參數,具體可以看前幾節的內容。
buffio.NewScanner(os.Stdin)
掃描儀是一個緩衝io工具,預設以行分割輸入的內容。舉個例子,如果輸入特別大,就可以把它以一系列行的形式讀取。
scan.Scan()
將迴圈讀取行,如果有可用的行讀取將會返回true。
scan.Text()
獲取讀取的行。
for 迴圈中使用 strings
標準庫的 Split
方法根據舊單詞 變數 old
(大小寫敏感)分割字元串獲得字元串切片。
再將切片傳入 strings
標準庫的 Join
方法,通過新單詞 變數 new
合併字元串。
test.txt
matt went to greece
where did matt go
alan went to rome
matt didn't go there
第一行留空行,因為會讀取 BOM 頭,具體請看這篇文章
重定向管道流讀取TXT文本第一次讀取為""空字元串 - 小能日記 - 博客園 (cnblogs.com)
result
cat test.txt | go run . matt ed
ed went to greece
where did ed go
alan went to rome
ed didn't go there
這裡我們使用了重定向管道,讀取 test.txt 的內容當做 main.go 的程式輸入,指令在 linux 是 go run . matt ed < test.txt。
old, new := os.Args[1], os.Args[2]
old, new = new, old
值得註意的一點是初始化變數的方式,使用一行初始化兩個變數。巧妙的是可以用這種方式進行兩個變數值的交換。