1024
在這個(gè)對(duì)程序員有意義的節(jié)日里,希望大家快樂柒啤,少加班,開開心心寫代碼畸颅。
正題
在這篇文章中,我們將討論 Golang 中的字符串方援,并查看一些不同的場(chǎng)景没炒,以避免常見錯(cuò)誤。讓我們深入探討犯戏!
1. 字符串是否可以為 nil送火?
我們已經(jīng)對(duì) Golang 中的字符串有了基本的了解,但我們可以從 Golang 字符串不能為 nil
開始先匪,除非您使用指向字符串的指針。
如下代碼所示,當(dāng)我們創(chuàng)建一個(gè)字符串變量時(shí)馒过,默認(rèn)值必須是空的""禽作。如果我們用 nil
值初始化字符串變量,我們將面臨在變量聲明中不能使用 nil
作為字符串值的錯(cuò)誤岸裙。例如:
func main() {
var s string
s = nil // Cannot use 'nil' as the type string
fmt.Println(s)
}
編譯器會(huì)提示我們不能使用 nil
賦予 string
類型猖败。因此,我們可以只是定義變量降允,或者使用""作為默認(rèn)值:
func main() {
var s string
var ss = ""
fmt.Println(s, ss)
}
如果我們堅(jiān)持在字符串類型變量中使用 nil
值恩闻,則應(yīng)使用指針,如下所示:
func main() {
var s *string
fmt.Println(s)
}
這個(gè)時(shí)候輸出則為:
<nil>
但是剧董,我們必須謹(jǐn)慎使用這種方法幢尚。每次要為變量賦值時(shí)破停,我們都必須編寫更多的代碼,而且在賦新值之前還要檢查是否有零值或前一個(gè)值尉剩。
func main() {
var s *string
tmp := "hello"
s = &tmp
fmt.Printf("address: %+v, value: %s", s, *s)
}
這個(gè)時(shí)候打印出來(lái) s 的地址以及所指向的值:
address: 0xc00008a030, value: hello
2. 字符串是不可變的
Golang 中的字符串是不可變的辱挥,這意味著我們不能更改每個(gè)字符的值。例如:
func main() {
tmp := "hello"
tmp[0] = 'J'
fmt.Println(tmp)
}
上述代碼會(huì)導(dǎo)致編譯時(shí)錯(cuò)誤边涕,因?yàn)闊o(wú)法賦值給 tmp[0]
晤碘。
更改字符串中單個(gè)字符的常見錯(cuò)誤如下:
func main() {
tmp := "hello"
tbs := []byte(tmp)
tbs[0] = 'J'
fmt.Println(string(tbs))
chi := "你好"
chiTBS := []byte(chi)
chiTBS[0] = 'J'
fmt.Println(string(chiTBS))
}
輸出為:
Jello
J??好
雖然第一個(gè)輸出顯示的結(jié)果符合我們的預(yù)期,但這并不是更改某個(gè)字符的正確方法功蜓。
這是因?yàn)槲覀兇蛩阈薷牡膯蝹€(gè)部分可能存儲(chǔ)在多個(gè)字節(jié)中园爷,即使你想將變量轉(zhuǎn)換為符文類型并更改你想要的部分,我也不得不說(shuō)式撼,這是不可能做到的童社,因?yàn)樗赡鼙环胖迷诙鄠€(gè)符文中,我們需要謹(jǐn)慎行事著隆!
3. 字符串是字節(jié)數(shù)組
在 Golang 中扰楼,字符串由字節(jié)(字節(jié)的片段)組成,某些字符需要存儲(chǔ)在多個(gè)字節(jié)中美浦,例如:"?"弦赖。
因此,當(dāng)需要確定一個(gè)字符串類型變量的長(zhǎng)度時(shí)浦辨,我們必須謹(jǐn)慎編碼蹬竖。例如
func main() {
tmp := "¥"
fmt.Println("bytes: ", len(tmp))
fmt.Println("runes: ", utf8.RuneCountInString(tmp))
}
len
函數(shù)返回的是字符串的字節(jié)數(shù),而不是字符數(shù)流酬。當(dāng)我們需要找出字符串的符文數(shù)時(shí)币厕,可以使用 uft8.RuneCountIntString()
函數(shù)。
另一個(gè)常見的誤解是使用 uft8.RuneCountIntString()
來(lái)確定字符數(shù)芽腾,但這并不是在任何情況下都正確旦装,因?yàn)橐粋€(gè)字符串變量可能跨越多個(gè)符文。請(qǐng)看這個(gè)例子:
func main() {
tmp := "??"
fmt.Println("bytes: ", len(tmp))
fmt.Println("runes: ", utf8.RuneCountInString(tmp))
}
輸出為:
bytes: 6
runes: 2
4. 字符串索引和forrange
在 Golang 中摊滔,使用索引檢索字符串的單個(gè)部分將為我們提供字符的 uint
值阴绢,并且只能檢索第一個(gè)字節(jié)。但在字符串變量的 for
循環(huán)中惭载,我們可以訪問每個(gè)字符的符值:
func main() {
tmp := "?¥%……&*"
fmt.Printf("char at 0 index, has type %T and value is %+v\n", tmp[0], tmp[0])
for _, t := range tmp {
fmt.Printf("value is %+v type is %T\n", t, t)
}
}
輸出:
char at 0 index, has type uint8 and value is 226
value is 10084 type is int32
value is 65509 type is int32
value is 37 type is int32
value is 8230 type is int32
value is 8230 type is int32
value is 38 type is int32
value is 42 type is int32
在對(duì)字符串進(jìn)行迭代時(shí)旱函,還要注意變量中可能存在的非 UTF8 字符,如果 Golang 無(wú)法將其理解為 UTF8描滔,則會(huì)使用 unicode 替換而非實(shí)際值棒妨。
5. 字符串平等
在 Golang 中,我們總是可以使用 ==
來(lái)檢查簡(jiǎn)單的字符串是否相等,但如果我們的變量存在隱藏點(diǎn)券腔,則應(yīng)在比較兩個(gè)字符串變量之前使用 unicode
規(guī)范包將其規(guī)范化:
func main() {
cafe1 := "Café"
cafe2 := "Cafe\u0301"
normalizeCafe1 := norm.NFC.String(cafe1)
normalizeCafe2 := norm.NFC.String(cafe2)
fmt.Println(cafe1 == cafe2)
fmt.Println(normalizeCafe1 == normalizeCafe2)
}
6. 高效字符串構(gòu)建
使用“+”
連接大量字符串的效率可能非常低伏穆。使用 strings.Builder
是高效構(gòu)建字符串的最佳方法之一:
func main() {
sb := strings.Builder{}
for i := 0; i < 1000; i++ {
sb.WriteString("hello ")
}
result := sb.String()
fmt.Println(result)
}
與傳統(tǒng)的 + 連接方法相比,這種方法速度更快纷纫,內(nèi)存消耗更少枕扫,而且可以避免創(chuàng)建不必要的中間字符串。我們還可以使用 bytes.Buffer
軟件包來(lái)實(shí)現(xiàn)這一目標(biāo)辱魁。
總結(jié)
- 字符串的默認(rèn)值是""
-
len
和RuneCountIntString
函數(shù)具有不同的行為 - 我們應(yīng)該小心 for 循環(huán)和字符串
- 字符串相等是我們需要更精確的地方