聊聊 Go 語言中的字符表示與字符串遍歷

和其餘語言不一樣,在 Go 語言中沒有字符類型,字符只是整數的特殊用例html

爲何說字符只是整數的特殊用例呢?由於在 Go 中,用於表示字符的 byterune 類型都是整型的別名。在 Go 的源碼中咱們能夠看到:golang

// byte is an alias for uint8 and is equivalent to uint8 in all ways. It is
// used, by convention, to distinguish byte values from 8-bit unsigned
// integer values.
type byte = uint8

// rune is an alias for int32 and is equivalent to int32 in all ways. It is
// used, by convention, to distinguish character values from integer values.
type rune = int32
複製代碼
  • byteuint8 的別名,長度爲 1 個字節,用於表示 ASCII 字符
  • runeint32 的別名,長度爲 4 個字節,用於表示以 UTF-8 編碼的 Unicode 碼點

Tips:Unicode 從 0 開始,爲每一個符號指定一個編號,這叫作「碼點」(code point)。編程

字符的表示

那麼,如何在 Go 語言中表示字符呢?數組

在 Go 語言中使用單引號包圍來表示字符,例如 'j'函數

byte

若是要表示 byte 類型的字符,可使用 byte 關鍵字來指明字符變量的類型:post

var byteC byte = 'j'
複製代碼

又由於 byte 實質上是整型 uint8,因此能夠直接轉成整型值。在格式化說明符中咱們使用 %c 表示字符,%d 表示整型:ui

// 聲明 byte 類型字符
var byteC byte = 'j'
fmt.Printf("字符 %c 對應的整型爲 %d\n", byteC, byteC)
// Output: 字符 j 對應的整型爲 106
複製代碼

rune

byte 相同,想要聲明 rune 類型的字符可使用 rune 關鍵字指明:編碼

var runeC rune = 'J'
複製代碼

但若是在聲明一個字符變量時沒有指明類型,Go 會默認它是 rune 類型spa

runeC := 'J'
fmt.Printf("字符 %c 的類型爲 %T\n", runeC, runeC)
// Output: 字符 J 的類型爲 int32
複製代碼

爲何須要兩種類型?

看到這裏你可能會問了,既然都用於表示字符,爲何還須要兩種類型呢?code

咱們知道,byte 佔用一個字節,所以它能夠用於表示 ASCII 字符。而 UTF-8 是一種變長的編碼方法,字符長度從 1 個字節到 4 個字節不等byte 顯然不擅長這樣的表示,就算你想要使用多個 byte 進行表示,你也無從知曉你要處理的 UTF-8 字符究竟佔了幾個字節。

所以,若是你在中文字符串上狂妄地進行截取,必定會輸出亂碼:

testString := "你好,世界"
fmt.Println(testString[:2]) // 輸出亂碼,由於截取了前兩個字節
fmt.Println(testString[:3]) // 輸出「你」,一箇中文字符由三個字節表示
複製代碼

此時就須要 rune 的幫助了。利用 []rune() 將字符串轉爲 Unicode 碼點再進行截取,這樣就無需考慮字符串中含有 UTF-8 字符的狀況了:

testString := "你好,世界"
fmt.Println(string([]rune(testString)[:2])) // 輸出:「你好」
複製代碼

Tips:Unicode 和 ASCII 同樣,是一種字符集,UTF-8 則是一種編碼方式。

遍歷字符串

字符串遍歷有兩種方式,一種是下標遍歷,一種是使用 range

下標遍歷

因爲在 Go 語言中,字符串以 UTF-8 編碼方式存儲,使用 len() 函數獲取字符串長度時,獲取到的是該 UTF-8 編碼字符串的字節長度,經過下標索引字符串將會產生一個字節。所以,若是字符串中含有 UTF-8 編碼字符,就會出現亂碼:

testString := "Hello,世界"

for i := 0; i < len(testString); i++ {
	c := testString[i]
	fmt.Printf("%c 的類型是 %s\n", c, reflect.TypeOf(c))
}

/* Output: H 的類型是 uint8(ASCII 字符返回正常) e 的類型是 uint8 l 的類型是 uint8 l 的類型是 uint8 o 的類型是 uint8 ï 的類型是 uint8(從這裏開始出現了奇怪的亂碼) ¼ 的類型是 uint8 Œ 的類型是 uint8 ä 的類型是 uint8 ¸ 的類型是 uint8 – 的類型是 uint8 ç 的類型是 uint8 • 的類型是 uint8 Œ 的類型是 uint8 */
複製代碼

range

range 遍歷則會獲得 rune 類型的字符:

testString := "Hello,世界"

for _, c := range testString {
	fmt.Printf("%c 的類型是 %s\n", c, reflect.TypeOf(c))
}

/* Output: H 的類型是 int32 e 的類型是 int32 l 的類型是 int32 l 的類型是 int32 o 的類型是 int32 , 的類型是 int32 世 的類型是 int32 界 的類型是 int32 */
複製代碼

總結

  • Go 語言中沒有字符的概念,一個字符就是一堆字節,它多是單個字節(ASCII 字符集),也有多是多個字節(Unicode 字符集)
  • byteuint8 的別名,長度爲 1 個字節,用於表示 ASCII 字符
  • rune 則是 int32 的別名,長度爲 4 個字節,用於表示以 UTF-8 編碼的 Unicode 碼點
  • 字符串的截取是以字節爲單位的
  • 使用下標索引字符串會產生字節
  • 想要遍歷 rune 類型的字符則使用 range 方法進行遍歷

參考資料

相關閱讀


若是你以爲文章寫得不錯,請幫我兩個小忙:

  1. 點贊並關注我,讓這篇文章被更多人看到
  2. 關注公衆號「編程拯救世界」,你將第一時間得到新文章的推送

你的鼓勵是我創做最大的動力,感謝你們!

相關文章
相關標籤/搜索