Golang 入門 : 字符串
在 Golang 中,字符串是一種基本類型,這一點和 C 語言不同。C 語言沒有原生的字符串類型,而是使用字符數組來表示字符串,並以字符指針來傳遞字符串。Golang 中的字符串是一個不可改變的 UTF-8 字符序列,一個 ASCII 碼占用 1個字節,其它字符根據需要占用 2-4 個字節,這一點與其它主流的開發語言( C++、Java、Python)是不同的。這樣設計的好處有兩個:
- 減少內存的使用,節約硬盤空間
- 統一編碼格式(UTF-8)有助於減少讀取文件時的編碼和解碼工作
字符串的聲明與初始化
聲明和初始化字符串非常容易:
s := "hello world"
上面的代碼聲明了字符串變量 s,其內容為 "hello world"。在 Golang 中字符串的值是不可變的,當創建一個字符串後,無法再次修改這個字符串的內容。所以如果你通過下面的代碼修改 s 中的內容就會發生編譯錯誤:
s := "hello nick"
字符串字面量
Golang 支持兩種類型的字符串字面量:
- 解釋型字符串
- 非解釋型字符串
所謂的解釋型字符串就是用雙引號括起來的字符串(""),其中的轉義字符會被替換掉,這些轉義字符包括:
\a // 響鈴 \b // 退格 \f // 換頁 \n // 換行 \r // 回車 \t // 制表符 \u // Unicode 字符 \v // 垂直制表符 \" // 雙引號 \\ // 反斜杠
非解釋型字符串是指用反引號( ` 一般在 Esc 鍵下面,數字鍵 1 的左邊)括起來的字符串。在非解釋型字符串中的轉義字符不會被解釋,並且還支持換行。
看下面的 demo:
package main import "fmt" func main() { s1 := "Hello\nWorld!" s2 := `Hello\n nick!` fmt.Println(s1) fmt.Println(s2) }
運行上面的代碼,輸出如下:
反單引號可以跨行,並且引號內的所有內容都會直接輸出,包括轉義字符和空格縮進等。而雙引號則不能換行,並且會解析轉義字符。
字符串的長度
內置函數 len() 可以返回一個字符串中的字節數(註意,不是 rune 字符數目),索引操作 s[i] 可以返回字符串 s 中第 i 個字節的值。
s := "abc你" fmt.Printf("字符串的字節長度是:%d\n", len(s)) for i := 0; i < len(s); i++ { fmt.Println(s[i]) }
字符串的字節長度是:6
97 // a 98 // b 99 // c 228 // 你 189 // 你 160 // 你
最後的三個字節組成了漢字 "你"。
如果要獲取字符串中字符的個數,可以先把字符串轉換成 []rune 類型,然後用 len() 函數獲取字符個數:
s := "abc你" r := []rune(s) fmt.Print(len(r))
這次輸出的結果為:4。
從字符串中截取內容
可以通過下面的語法截取字符串中的內容:
s := "abcdef" s1 := s[1:4]
此時 s1 的內容為 "bcd",該語法通過下標索引的方式截取字符串中的內容,特點是 "左含右不含"。也就是說新的子串包含源串索引為 1 的字符,而不包含源串索引為 4 的字符。
如果要從源串的開始處截取可以省略第一個索引:
s2 := s[:4]
s2 的內容為 "abcd"。
如果要從源串的某個位置開始一直截取到末尾,可以省略第二個索引:
s3 := s[2:]
s3 的內容為 "cdef"。
訪問越界問題
在通過索引訪問字符串或者是截取子串時需要考慮索引越界的問題,如果試圖訪問超出字符串索引範圍的字節將會在運行時導致 panic 異常:
s4 := s[2:10]
連接字符串
使用 + 號可輕松的把字符串連接起來:
s := "hello" s1 := " " s2 := "world" s3 := s + s1 + s2
此時 s3 的內容為 "hello world"。
遍歷字符串
由於可以通過下標索引字符串中的字節,所以可以用這種方式遍歷字符串:
s := "abc你好" for i := 0; i < len(s); i++ { fmt.Printf("%c", s[i]) }
輸出的結果如下:
abcä½ å¥½
可見在字符串中含有非單字節的字符時這種方法是不正確的。range 函數能解決這個問題:
for _, v := range s { fmt.Printf("%c", v) }
這次輸出的結果為:
abc你好
修改字符串
在 Golang 中,不能修改字符串的內容,也就是說不能通過 s[i] 這種方式修改字符串中的字符。要修改字符串的內容,可以先將字符串的內容復制到一個可寫的變量中,一般是 []byte 或 []rune 類型的變量,然後再進行修改。
如果要對字符串中的字節進行修改,就轉換為 []byte 類型,如果要對字符串中的字符修改,就轉換為 []rune 類型,在轉換類型的過程中會自動復制數據。
修改字符串中的字節(用 []byte)
對於那些單字節字符來說,可以通過這種方式進行修改:
s := "Hello 世界" b := []byte(s) // 轉換為 []byte,數據被自動復制 b[5] = ‘,‘ // 把空格改為半角逗號 fmt.Printf("%s\n", s) fmt.Printf("%s\n", b)
輸出結果為:
Hello 世界
Hello,世界
修改字符串中的字符(用 []rune)
s := "Hello 世界" b := []rune(s) // 轉換為 []rune,數據被自動復制 b[6] = ‘中‘ b[7] = ‘國‘ fmt.Println(s) fmt.Println(string(b))
輸出結果為:
Hello 世界
Hello 中國
註意:和 C/C++ 不一樣,Golang 語言中的字符串是根據長度限定的,而非特殊的字符 \0。string 類型的 0 值是長度為 0 的字符串,即空字符串 ""。
strings 包
strings 是非常重要的一種基本類型,所需要執行的操作繁多且比較復雜,因此一般的編程語言都會額外封裝一些方法用於處理字符串。Golang 語言的標準庫中也存在這樣一個名稱為 strings 的庫。下面介紹一些 strings 庫的常見用法。
檢查是否包含子串
判斷一個字符串中是否包含某個子串是經常遇到的一種字符串操作,在 strings 包中,可以使用 Contains() 函數進行判斷:
s := "A good tree bears good fruit" fmt.Printf("%t\n", strings.Contains(s, "tree"))
輸出的結果為:true。
如果要檢查字符串是不是以某個子串開始的,可以使用 HasPrefix() 函數:
s := "A good tree bears good fruit" fmt.Printf("%t\n", strings.HasPrefix(s, "A good"))
輸出的結果為:true。
如果要檢查字符串是不是以某個子串結束的,可以使用 HasSuffix() 函數:
s := "A good tree bears good fruit" fmt.Printf("%t\n", strings.HasSuffix(s, "good fruit"))
輸出的結果為:true。
與 Contains() 函數相比,ContainsAny() 函數能夠匹配更廣泛的內容,並且可以匹配 Unicode 字符:
fmt.Println(strings.Contains("failure", "a & o")) // false fmt.Println(strings.Contains("foo", "")) // true fmt.Println(strings.Contains("", "")) // true fmt.Println(strings.ContainsAny("failure", "a & o")) // true fmt.Println(strings.ContainsAny("foo", "")) // false fmt.Println(strings.ContainsAny("", "")) // false fmt.Println(strings.ContainsAny("好樹結好果", "好樹")) // true
獲取子串的索引
在 Golang 中,字符串中的字符都有一個索引值,很多時候我們要操作字符串,就必須先獲取字符在字符串中的索引值。在 strings 包中 Index 函數可以返回指定字符或字符串的第一個字符的索引值,如果不存在則返回 -1:
fmt.Println(strings.Index("Hi I‘m Nick, Hi", "Nick")) // 7 fmt.Println(strings.Index("Hi I‘m Nick, Hi", "Hi")) // 0 fmt.Println(strings.Index("Hi I‘m Nick, Hi", "abc")) // -1 fmt.Println(strings.LastIndex("Hi I‘m Nick, Hi", "Hi")) // 13
LastIndex 函數返回匹配到的最後一個子串的索引值。
如果處理包含多個字節組成的字符的字符串,需要使用 IndexRune 函數來對字符進行定位:
fmt.Println(strings.IndexRune("好樹結好果", ‘樹‘)) // 3
註意這裏返回的是 3,這是 "樹" 的第一個字節在字符串中的位置。
替換字符串
替換字符串最常用的方式其實是通過正則匹配去替換的,其靈活度更高。而 Golang 則為比較基礎的替換操作提供了 Replace 函數:
fmt.Println(strings.Replace("你好世界", "世界", "地球", 1))
輸出的結果為:你好地球
strings.Replate(str, old, new, n) 函數一共有 4 個參數,第一個為源字符串,第二個表示源字符串中需要被替換掉的字符串,第三個是替換的內容,最後一個 n 則表示替換匹配到的前 n 個記錄。
大小寫轉換
操作字符串就免不了大小寫轉換,ToLower() 函數把字符串轉換為小寫,ToUpper() 函數把字符串轉換為大寫:
s := "A good tree bears good fruit" s1 := "HOW ARE YOU?" fmt.Printf("%s\n", strings.ToUpper(s)) fmt.Printf("%s\n", strings.ToLower(s1))
輸出的結果如下:
A GOOD TREE BEARS GOOD FRUIT
how are you?
修剪
在處理用戶的輸入時,去掉字符串前後多余的空白字符非常重要,strings 包中提供了 Trem()、TrimLeft() 和 TrimRight() 來實現這個功能:
fmt.Printf("%q\n", strings.Trim(" Golang ", " ")) fmt.Printf("%q\n", strings.TrimLeft(" Golang ", " ")) fmt.Printf("%q\n", strings.TrimRight(" Golang ", " "))
輸出的結果如下:
"Golang"
"Golang "
" Golang"
分隔與拼接
Split() 函數按照指定的分隔符分隔字符串並返回一個切片:
fmt.Printf("%q\n", strings.Split("a,b,c", ",")) fmt.Printf("%q\n", strings.Split("a boy a girl a cat", "a ")) fmt.Printf("%q\n", strings.Split("xyz", ""))
輸出的結果如下:
["a" "b" "c"]
["" "boy " "girl " "cat"]
["x" "y" "z"]
Join() 函數則會將元素類型為 string 的切片使用分隔符拼接組成一個字符串:
fmt.Printf("%q\n", strings.Join([]string{"boy", "girl", "cat"}, ";"))
輸出的結果如下:
"boy;girl;cat"
strconv 包
這個包主要用於字符串與其他類型的轉換。這裏我們簡單的看下如何通過 Itoa() 函數把字符串轉換為整型:
num, _ := strconv.Atoi("123") num += 5 fmt.Printf("%d\n", num)
上面這段代碼輸出的結果為:128
這說明字符串 "123" 被成功的轉換成了十進制整數 123,隨後還進行了加法運算。
總結
對於任何一門編程語言來說,字符串的定義和相關操作都是非常基礎的內容。從本文我們可以看到,Golang 的字符串類型原生支持 Unicode,操作起來也非常的方便,特別是提供了便利的 strings 包。這讓我們能夠輕松的了解並使用 Golang 的 string 類型。
參考:
《Go語言編程入門與實戰技巧》
Golang 入門 : 字符串