天天看點

Go語言--字元串的使用 一 字元串類型二 字元串的應用

 一 字元串類型

字元串在Go語言中是以原生資料類型出現的,使用字元串就像使用其他基本類型(int、bool、float32、float64等)一樣。Go語言中,使用關鍵字string來聲明字元串變量。

 字元串的值為雙引号中的内容,可以在Go語言的源碼中直接添加非ASCII碼字元。示例代碼如下:

str := "Hello, Golang"
ch  := "中文"
           

1.1 字元串轉義符

Go語言的字元串轉義符和其他程式設計語言的是一樣,如:回車、換行、單雙引号、制表符等。

轉義符    含義
\r    回車符(傳回行首)
\n    換行符(直接跳到下一行的同列位置)
\t    制表符
\'    單引号
\"    雙引号
\\    反斜杠
           

在Go語言源碼中使用轉義字元的示例如下:

package main

import (
    "fmt"
)

func main() {
    fmt.Println("str := \"c:\\Go\\bin\\go.exe\"")
}
           

運作結果為:str := "c:\Go\bin\go.exe"

這段代碼中将雙引号("")和反斜杠(\)進行轉義。

1.2 字元串實作基于UTF-8編碼

Go語言字元串的内部實作使用的是UTF-8編碼。通過rune類型,可以很友善地對每個UTF-8字元進行通路。當然,Go語言也支援按傳統的ASCII碼方式進行逐個字元通路。

1.3 定義多行字元串

在源碼中,将字元串的值以雙引号("")書寫的方式是字元串的常見表達方式,被稱為字元串字面量(string literal)。這種雙引号字面量不能跨行,如果需要在源碼中嵌入一個多行字元串時,就必須使用反引号字元(`)。示例代碼如下:

package main

import "fmt"

func main(){
	const str = `第一行
	第二行
	第三行
	\r\n
	`
	fmt.Println(str)
}
           

輸出結果如下:go run strDemo.go

第一行

        第二行

        第三行

        \r\n

<說明> 在兩個反引号中的字元串将被原樣指派到str變量中。反引号間的換行被視為字元串中的換行,但是所有的轉義字元均無效,文本将會原樣輸出。多行字元串一般用于内嵌源碼和内嵌資料等。在反引号間的所有代碼均不會被編譯器識别,而隻是作為字元串的一部分。

二 字元串的應用

2.1 計算字元串的長度

Go語言的内建函數 len(),可以用來計算切片、字元串、通道(channel)等的長度。示例代碼如下:

tip1 := "genji is a nijia"
fmt.Println(len(tip1))  //11
	
tip2 := "忍者"
fmt.Println(len(tip2)) //6
           

len()函數傳回值的類型為int,表示字元串的ASCII碼字元串個數或是位元組長度,因為一個ASCII碼字元使用一個位元組的空間存儲。

而輸出語句的第2行的中文字元串"忍者",傳回的位元組數為6,這是因為Go語言的字元串都以UTF-8格式儲存,每個中文字元占用3個位元組的記憶體空間,是以使用len()函數獲得兩個中文文字對應的位元組數為6。

如果希望按習慣上的字元個數來計算,就需要使用Go語言中UTF-8包提供的RuneCountInString()方法,統計Unicode字元數量。

package main

import(
	"fmt"
	"unicode/utf8"  //導入utf-8包
)

func main(){
	tip1 := "genji is a nijia"
	fmt.Println(len(tip1))  //11
	
	tip2 := "忍者"
	fmt.Println(len(tip2)) //6
	
	fmt.Println(utf8.RuneCountInString("忍者")) //2
	fmt.Println(utf8.RuneCountInString("龍刃出鞘, fight!")) //12
}
           

<總結>

  • 計算ASCII字元串長度使用len()函數。
  • 計算Unicode字元串長度使用utf8.RuneCountInString()函數。

2.2 周遊字元串 —— 擷取每一個字元串元素

1、周遊ASCII字元串中的字元

周遊ASCII字元串使用for循環語句,直接取每個字元的下标索引即可。示例如下:

package main

import(
    "fmt"
)

func main(){
    str := "Hello, Golang"
    for i:=0; i<len(str); i++ {
        fmt.Printf("ASCII: %c %d\n", str[i], str[i])
    }   
}
           

運作程式:go run traversalString.go

ASCII: H 72

ASCII: e 101

ASCII: l 108

ASCII: l 108

ASCII: o 111

ASCII: , 44

ASCII:   32

ASCII: G 71

ASCII: o 111

ASCII: l 108

ASCII: a 97

ASCII: n 110

ASCII: g 103

2、周遊Unicode字元串

package main

import(
    "fmt"
)

func main(){
    theme := "阻擊 start"
    for _, s := range theme{
        fmt.Printf("Unicode: %c %d\n", s, s)
    }
}
           

程式運作:go run rangeString.go

Unicode: 阻 38459

Unicode: 擊 20987

Unicode:   32

Unicode: s 115

Unicode: t 116

Unicode: a 97

Unicode: r 114

Unicode: t 116

<總結>

  • ASCII字元串周遊直接使用下标。
  • Unicode字元串周遊使用for...range。

3、Go語言字元

字元串中的每一個元素叫做“字元”。在周遊或者擷取單個字元串元素時可以獲得字元。

GO語言字元有下面兩種:

  • 一種是uint8類型,或者叫byte類型,代表了ASCII碼的一個字元。
  • 另一種是rune類型,代表了一個UTF-8字元。當需要進行中文、日文或者其他複合字元時,需要用到rune類型。rune類型實際上是一個int32類型。

使用fmt.Printf中的"%T"格式輸出符,可以輸出變量的實際類型,使用這個方法可以檢視byte和rune的本來類型,代碼如下:

var a byte = 'a'
fmt.Printf("%d %T\n", a, a)

var b rune = '你'
fmt.Printf("%d %T\n", b, b)
           

輸出結果如下:

97 uint8

20320 int32

可以看到,byte類型的變量a,實際類型是uint8,其值為'a',對應的ASCII編碼為97。rune類型的變量b的實際類型是int32,對應的Unicode編碼是20320。Go使用特殊的rune類型來處理Unicode字元串,讓基于Unicoded的文本處理更為友善,也可以使用byte型進行預設字元串處理,性能和擴充性都有展現。

擴充 - UTF-8和Unicode的差別?

ASCII 和 Unicode 二者都是字元集。字元集為每一個字元配置設定一個唯一的ID。例如,上面的例子中,字元'a'在ASCII和Unicode的編碼ID都是97。中文字元 '你' 在Unicode字元集的編碼為20320。

UTF-8是編碼規則,将Unicode中字元的ID以某種方式進行編碼。UTF-8是一種變長編碼規則,從1到4位元組不等。編碼規則如下:

  • 0xxx xxxx 表示文字元号0~127,以相容ASCII字元集。
  • 從128~0x10 ffff 表示其他字元。

根據這個規則,拉丁文語系的字元編碼一般情況下,每個字元依然占用一個位元組,而中文每個字元占用3個位元組。廣義的Unicode指一個标準,定義字元集及編碼規則,即Unicode字元集和UTF-8、UTF-16編碼等。

2.3 擷取字元串的子串(substring)

使用strings.Index()函數在字元串中搜尋另一個子串。代碼如下:

tracer := "死神來了,死神 bye bye"
comma := strings.Index(tracer[comma:], ",")
pos := strings.Index(tracer[comma:], "死神")
fmt.Println(comma, pos, tracer[comma+pos:])
           

運作結果:12 3 死神 bye bye

代碼說明如下:

  • 第2行嘗試在tracer字元串中搜尋中文逗号,傳回的位置存在comma變量中,類型是int,表示從tracer字元串開始的ASCII碼位置。

strings.Index()函數并沒有像其他程式設計語言一樣,提供一個從某偏移量開始搜搜的功能,但是我們可以對字元串進行切片操作來實作這個邏輯。

  • 第3行中,tracer[comma:] 表示從tracer的comma位置開始到tracer字元串結尾構造一個子字元串,傳回給strings.Index()進行再索引。得到的pos是相對于tracer[comma:]的結果,即此時tracer[comma:] = ",死神 bye bye"。
  • comma中文逗号的位置是12(一個中文字元3個位元組,offset=3 * 4 = 12)。而pos是相對位置,值為3(中文逗号占3個位元組)。我們為了獲得第2個死神的位置,也就是中文逗号後面的字元串,就必須讓comma加上pos的相對偏移量,計算出15的偏移量,然後再通過切片操作tracer[comma+pos:]計算出最終的子串,獲得最終的結果:"死神 bye bye"。

<總結>

  • strings.Index():用于正向搜尋子串。
  • strings.LastIndex():反向搜尋子串。
  • 搜尋的起始位置可以通過切片操作實作偏移量。

2.4 修改字元串

Go語言的字元串和其他進階程式設計語言(Java、C#)一樣,預設是不可變的(immutable),無法直接修改字元串中的每一個字元元素,隻能通過重新構造新的字元串并通過指派原來的字元串來實作。示例代碼如下:

angel := "Heros never die"
angelBytes := []byte(angel)
for i:=5; i<=10; i++ {
    angelBytes[i] = ' '
}
fmt.Println(angel)
fmt.Println(string(angelBytes))
           

運作結果:

Heros never die

Heros           die

代碼說明:上面的代碼中,我們并沒有修改angel字元串中的元素,實際修改的是[]byte,[]byte在Go語言中是可變的,本身就是一個切片。在完成了對[]byte操作後,使用string()函數将[]byte轉換為字元串,重新構造了一個新的字元串。

字元串不可變的好處:天生線程安全,大家使用的都是隻讀對象,無須加鎖通路;友善記憶體共享,而不必使用寫時複制(Copy On Write)技術;字元串hash值也隻需要制作一份。

<總結>

  • Go語言字元串是不可變的。
  • 修改字元串時,可以将字元串轉換為 []byte 進行修改。
  • []byte 和 string 可以通過強制類型互相轉換。

2.5 連接配接字元串

Go語言和其他程式設計語言(Java、C#等)一樣,使用加号“+”對字元串進行連接配接操作。

Go語言除了使用加号連接配接字元串,Go語言也有類似于Java語言的StringBuilder機制來進行高效的字元串連接配接。示例代碼如下:

hammer := "吃我一錘"
sickle := "死吧"

//聲明位元組緩存
var stringBuilder bytes.BUffer

//把字元串寫入字元緩存
stringBuilder.WriteString(hammer)
stringBuilder.WriteString(sickle)

//将字元緩存以字元串形式輸出
fmt.Println(stringBuilder.String())
           

代碼說明:bytes.Buffer 是可以緩存并可以往裡面寫入各種位元組數組的。字元串也是一種位元組數組,使用WriteString()方法進行寫入。将需要連接配接的字元串,通過調用 WriteString()方法,寫入stringBuilder中,然後再通過stringBuilder.String()方法将緩沖轉換為字元串。

2.6 格式化

Go語言字元串格式化寫法:

fmt.Sprintf(格式化樣式, 參數清單)
           
  • 格式化樣式:格式化動詞以%開頭。
  • 參數清單:多個參數用逗号隔開,個數必須與格式化樣式中的個數一一對應,否則運作時會報錯。

                                                                表1 字元串格式化時常用動詞及功能

Go語言--字元串的使用 一 字元串類型二 字元串的應用

 在Go語言中,格式化的命名延續了C語言風格。示例代碼如下:

var progress = 2
var target = 8
str := fmt.Sprintf("已采集%d個草藥, 還需要%d個完成任務", progress, target);
fmt.Println(str)

pi := 3.14159
//按數值本身的格式輸出
variant := fmt.Sprintf("%v %v %v", "月球基地", true, pi)
fmt.Println(variant)

//匿名結構體聲明,并初始化
profile := &struct{
    Name string
    HP   int
}{
    Name: "rat",
    HP: 150,
}
fmt.Printf("使用'%%+v' %+v\n", profile)
fmt.Printf("使用'%%#v' %#v\n", profile)
fmt.Printf("使用'%%T' %T\n", profile)
           

 運作結果如下:

已采集2個草藥, 還需要8個完成任務

月球基地 true 3.14159

使用'%+v' &{Name:rat HP:150}

使用'%#v' &struct { Name string; HP int }{Name:"rat", HP:150}

使用'%T' *struct { Name string; HP int }

2.7 數字字元串轉換為數值

strconv包内的Atoi()函數或 ParseInt()函數用于解釋表示整數的字元串,而 ParsUint()用于無符号整數。

x, err := strconv.Atoi("123")  //x是整數
y, err := strconv.ParseInt("123", 10, 64) //10進制,最長為64位
           

ParseInt()函數的第三個參數指定結果必須比對何種大小的整型;例如,16表示int16,而0作為特殊值表示int。任何情況下,結果y的類型總是int64。