# 引言 String Handling在程式中非常常見,也是學習過程的必修課之一。本文記錄了使用Golang處理String的常見方法。 # String是什麼 > string is the set of all strings of 8-bit bytes, conventionally but not necessarily representing UTF-8-encoded text. A string may be empty, but not nil. Values of string type are immutable. 本質上,[string](https://pkg.go.dev/builtin#string)是一串byte (byte slice, 1 byte=8 bits),而Golang的[bytes類](https://pkg.go.dev/builtin#byte)等於uint8。bytes按照[UTF-8](https://en.wikipedia.org/wiki/ASCII) encode後產生對應的character (字元)。 由於每個1 byte只有8 bits,因此並不能代表所有UTF-8 encoded字元,例如中文字、emoji等,所以golang另外有`rune` type處理這些字元。 從 [runtime/string.go](https://github.com/golang/go/blob/master/src/runtime/string.go#L232-L241) 可見Golang runtime對string的定義是由byte pointer跟一個int組成。 ```go! type stringStruct struct { str unsafe.Pointer // underlying bytes len int // number of bytes } // Variant with *byte pointer type for DWARF debugging. type stringStructDWARF struct { str *byte len int } ``` # String Handling ## `nil` Golang有自己一套對null (空值) 的處理。對於String type而言,是沒有nil的,只有預設值`""`(empty string)。 ```go! str := "hello" fmt.Println(str == nil) ``` 這段code會報錯: ```shell invalid operation: str == nil (mismatched types string and untyped nil) ``` 雖然可以通過 `str==""` 檢查null string,但是對於某些情況而言,empty string跟null string有其各自的意義,並不對等。因此會使用`*string` (pointer of string) 處理需要null string的情況。 ```go! var strp *string fmt.Println(strp == nil) // true // fmt.Println(*strp) // runtime error: invalid memory address or nil pointer dereference var str = "" strp = &str fmt.Println(strp == nil) // false fmt.Println(*strp) // ``` ## `len()` 因為string是一串bytes,即byte slice。可以通過`len(string)`找出string的長度。 ```go! var str = "Hello\n" fmt.Println(len(str)) // 6 ``` ## String Literals 跨行的string有兩種表達方式,分別是使用`""`跟` `` `: ```go! var str = "Hello1\n2World3\n4!" ``` 等於 ```go! var str = `Hello1 2World3 4!` ``` ## String concatenation ### operator `+` ```go! s := "Hello" + "World!" fmt.Println(s) // HelloWorld! ``` ### `fmt.Sprint`, `fmt.Sprintln`, `fmt.Sprintf` [fmt.Sprintf](https://pkg.go.dev/fmt#Sprintf) ```go! s := fmt.Sprint("HelloWorld!") fmt.Println(s) // HelloWorld! s = fmt.Sprintln("Bye", "World", "~") fmt.Println(s) // ByeWorld~ s = fmt.Sprintf("%s", "NiceWorld!") fmt.Println(s) // NiceWorld! ``` `Sprint`亦可以將不同類型的variable轉換成String: ```go! sli := []int{1,2,3} str := fmt.Sprint(sli) fmt.Println(str) // [1 2 3] ``` ### `strings.Join()` [strings.Join](https://pkg.go.dev/strings#Join) ```go! ss := []string{"Hello", "World", "~"} s := strings.Join(ss, "") fmt.Println(s) // HelloWorld~ ``` ### `bytes.Buffer` [bytes.Buffer](https://pkg.go.dev/bytes#Buffer) ```go! var b bytes.Buffer b.WriteString("Hello") b.WriteString("World!") fmt.Println(b.String()) // HelloWorld! ``` ## String convertion 使用[strconv](https://pkg.go.dev/strconv) package ```go! str := "1234" v, _ := strconv.Atoi(str) fmt.Printf("%T\n", v) // int // Or, use "reflect", fmt.Println(reflect.TypeOf(v)) s := strconv.Itoa(v) fmt.Printf("%T\n", s) // string u, _ := strconv.ParseUint(str, 10, 32) fmt.Printf("%T\n", u) // uint64 ``` ### Convert int slice into string ```go! a := []int{1,2,3,4} str := strings.Trim(strings.Replace(fmt.Sprint(a), " ", ",", -1), "[]") fmt.Print(str) // 1,2,3,4 ``` ## 特殊情況 ### Remove the last character from a string ```go! ss := []string{"Hello", "World", "Peter", "Tom"} var s string for _, v := range ss{ s = s + v + ", " } // s == "Hello, World, Peter, Tom, " s = s[:i] + strings.Replace(s[i:], ", ", "", 1) fmt.Println(s) // Hello, World, Peter, Tom ``` 如果你知道要刪除的substring是什麼,可以使用`bytes.Buffer`: ```go! b.WriteString("Hello") b.WriteString("World!") b.Truncate(b.Len() - len("rld!")) fmt.Println(b.String()) // HelloWo ``` ### Create a random string ```go! letterRunes := []rune("3456789ABCEFGHJKLMNPQRSTXY") func RandStringRunes(n int) string { b := make([]rune, n) for i := range b { b[i] = letterRunes[rand.Intn(len(letterRunes))] } return string(b) } ``` ### Handle full space character 使用`rune`可以處理更多的UTF-8字元,包含中文字、emoji等全形字。 使用byte slice去表示中文時可以發現,golang會使用多於一個byte去表示中文字元,例如: ```go! fmt.Println([]byte("Hello, 世界")) // [72 101 108 108 111 44 32 228 184 150 231 149 140] // 228 184 150 世 // 231 149 140 界 ``` 一個例子:`validateComment` 會用 * 取代comment中的特定字眼,其中comment可以包含全形字。`strings.Builder.WriteRune`就可以直接寫入全形字元。 ```go! func validateComment(comment string) string { lowerComment := sensitive.Filter.Replace(strings.ToLower(comment), '*') var sb strings.Builder var runeCount int for _, runeValue := range lowerComment { if runeValue != '*' { _, err := sb.WriteRune([]rune(comment)[runeCount]) if err != nil { log.Error(err) } } else { _, err := sb.WriteRune(runeValue) if err != nil { log.Error(err) } } runeCount++ } defer sb.Reset() return sb.String() } // sensitive: https://github.com/importcjj/sensitive ``` 這個例子同時展示了,如何還原`ToLower`後的String。 # Reference * https://pkg.go.dev/builtin * https://go101.org/article/string.html * https://github.com/golang/go/blob/master/src/runtime/string.go