# 引言
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