Go基礎(chǔ)語法(五)

字符串

  • Go 語言中的字符串是一個字節(jié)切片。把內(nèi)容放在雙引號""之間,我們可以創(chuàng)建一個字符串。
  • Go 中的字符串是兼容 Unicode 編碼的,并且使用 UTF-8 進(jìn)行編碼。
package main

import (
    "fmt"
)

func printBytes(s string) {
    for i:= 0; i < len(s); i++ {
        fmt.Printf("%x ", s[i])
    }
}


func printChars(s string) {
    for i:= 0; i < len(s); i++ {
        fmt.Printf("%c ",s[i])
    }
}

func main() {
    name := "Hello World"
    printBytes(name)
    fmt.Printf("\n")
    printChars(name)
}

如果換成這個字符串:Se?or,進(jìn)行切片遍歷,可以發(fā)現(xiàn)輸出的字節(jié)是不對的:S e ? ± o r。
這是因為 ? 的 Unicode 代碼點(Code Point)是 U+00F1。它的 UTF-8 編碼占用了 c3 和 b1 兩個字節(jié)。它的 UTF-8 編碼占用了兩個字節(jié) c3 和 b1。而我們打印字符時,卻假定每個字符的編碼只會占用一個字節(jié),這是錯誤的。在 UTF-8 編碼中,一個代碼點可能會占用超過一個字節(jié)的空間。那么我們該怎么辦呢?rune 能幫我們解決這個難題。

rune 是 Go 語言的內(nèi)建類型,它也是 int32 的別稱。在 Go 語言中,rune 表示一個代碼點。代碼點無論占用多少個字節(jié),都可以用一個 rune 來表示。讓我們修改一下上面的程序,用 rune 來打印字符。

package main

import (
    "fmt"
)

func printBytes(s string) {
    for i:= 0; i < len(s); i++ {
        fmt.Printf("%x ", s[i])
    }
}

func printChars(s string) {
    runes := []rune(s)
    for i:= 0; i < len(runes); i++ {
        fmt.Printf("%c ",runes[i])
    }
}

func main() {
    name := "Hello World"
    printBytes(name)
    fmt.Printf("\n")
    printChars(name)
    fmt.Printf("\n\n")
    name = "Se?or"
    printBytes(name)
    fmt.Printf("\n")
    printChars(name)
}

在上面代碼的第 14 行,字符串被轉(zhuǎn)化為一個 rune 切片。然后我們循環(huán)打印字符。

字符串的 for range 循環(huán)
func printCharsAndBytes(s string) {
    for index, rune := range s {
        fmt.Printf("%c starts at byte %d\n", rune, index)
    }
}

func main() {
    name := "Se?or"
    printCharsAndBytes(name)
}

使用 for range 循環(huán)遍歷了字符串。循環(huán)返回的是是當(dāng)前 rune 的字節(jié)位置。

字符串的長度

utf8 package 包中的 func RuneCountInString(s string) (n int) 方法用來獲取字符串的長度。這個方法傳入一個字符串參數(shù)然后返回字符串中的 rune 的數(shù)量。

package main

import (  
    "fmt"
    "unicode/utf8"
)

func length(s string) {  
    fmt.Printf("length of %s is %d\n", s, utf8.RuneCountInString(s))
}
func main() { 
    word1 := "Se?or" 
    length(word1)
    word2 := "Pets"
    length(word2)
}
字符串是不可變的

Go 中的字符串是不可變的。一旦一個字符串被創(chuàng)建,那么它將無法被修改。

package main

import (  
    "fmt"
)

func mutate(s string)string {  
    s[0] = 'a'//any valid unicode character within single quote is a rune 
    return s
}
func main() {  
    h := "hello"
    fmt.Println(mutate(h))
}

為了修改字符串,可以把字符串轉(zhuǎn)化為一個 rune 切片。然后這個切片可以進(jìn)行任何想要的改變,然后再轉(zhuǎn)化為一個字符串。

package main

import (  
    "fmt"
)

func mutate(s []rune) string {  
    s[0] = 'a' 
    return string(s)
}
func main() {  
    h := "hello"
    fmt.Println(mutate([]rune(h)))
}

指針

學(xué)過C語言的知道,指針是一種存變量內(nèi)存地址的變量。


image.png

如上圖所示,變量 b 的值為 156,而 b 的內(nèi)存地址為 0x1040a124。變量 a 存儲了 b 的地址。我們就稱 a 指向了 b。

指針的聲明

例如:指針變量的類型為 *T,該指針指向一個 T 類型的變量。

package main

import (
    "fmt"
)

func main() {
    b := 255
    var a *int = &b
    fmt.Printf("Type of a is %T\n", a)
    fmt.Println("address of b is", a)
}

運(yùn)行結(jié)果:
Type of a is *int  
address of b is 0x1040a124
指針的零值(Zero Value)

指針的零值是 nil。

package main

import (  
    "fmt"
)

func main() {  
    a := 25
    var b *int
    if b == nil {
        fmt.Println("b is", b)
        b = &a
        fmt.Println("b after initialization is", b)
    }
}
指針的解引用

指針的解引用可以獲取指針?biāo)赶虻淖兞康闹?。?a 解引用的語法是 *a。

package main  
import (  
    "fmt"
)

func main() {  
    b := 255
    a := &b
    fmt.Println("address of b is", a)
    fmt.Println("value of b is", *a)

    *a++
    fmt.Println("new value of b is", b)
}

上述代碼使用指針的解引用修改了b的值。

向函數(shù)傳遞指針參數(shù)
package main

import (  
    "fmt"
)

func change(val *int) {  
    *val = 55
}
func main() {  
    a := 58
    fmt.Println("value of a before function call is",a)
    b := &a
    change(b)
    fmt.Println("value of a after function call is", a)
}
不要向函數(shù)傳遞數(shù)組的指針,而應(yīng)該使用切片
package main

import (  
    "fmt"
)

func modify(arr *[3]int) {  
    (*arr)[0] = 90
}

func main() {  
    a := [3]int{89, 90, 91}
    modify(&a)
    fmt.Println(a)
}

a[x] 是 (a)[x] 的簡寫形式,因此上面代碼中的 (arr)[0] 可以替換為 arr[0]。下面我們用簡寫形式重寫以上代碼。

package main

import (  
    "fmt"
)

func modify(arr *[3]int) {  
    arr[0] = 90
}

func main() {  
    a := [3]int{89, 90, 91}
    modify(&a)
    fmt.Println(a)
}

這種方式向函數(shù)傳遞一個數(shù)組指針參數(shù),并在函數(shù)內(nèi)修改數(shù)組。盡管它是有效的,但卻不是 Go 語言慣用的實現(xiàn)方式。我們最好使用切片來處理。

接下來我們用切片來重寫之前的代碼:

package main

import (  
    "fmt"
)

func modify(sls []int) {  
    sls[0] = 90
}

func main() {  
    a := [3]int{89, 90, 91}
    modify(a[:])
    fmt.Println(a)
}

我們將一個切片傳遞給了 modify 函數(shù)。在 modify 函數(shù)中,我們把切片的第一個元素修改為 90。程序也會輸出 [90 90 91]。所以別再傳遞數(shù)組指針了,而是使用切片吧。上面的代碼更加簡潔,也更符合 Go 語言的習(xí)慣。

Go 不支持指針運(yùn)算

Go 并不支持其他語言(例如 C)中的指針運(yùn)算。

package main

func main() {  
    b := [...]int{109, 110, 111}
    p := &b
    p++
}

上面的程序會拋出編譯錯誤:main.go:6: invalid operation: p++ (non-numeric type *[3]int)

結(jié)構(gòu)體

什么是結(jié)構(gòu)體?

結(jié)構(gòu)體是用戶定義的類型,表示若干個字段(Field)的集合。有時應(yīng)該把數(shù)據(jù)整合在一起,而不是讓這些數(shù)據(jù)沒有聯(lián)系。這種情況下可以使用結(jié)構(gòu)體。

聲明結(jié)構(gòu)體

例如,一個職員有 firstName、lastName 和 age 三個屬性,而把這些屬性組合在一個結(jié)構(gòu)體 employee 中就很合理。

type Employee struct {
    firstName string
    lastName  string
    age       int
}

還可以這樣聲明:

type Employee struct {
    firstName, lastName string
    age, salary         int
}

上面的結(jié)構(gòu)體 Employee 稱為 命名的結(jié)構(gòu)體(Named Structure)。我們創(chuàng)建了名為 Employee 的新類型,而它可以用于創(chuàng)建 Employee 類型的結(jié)構(gòu)體變量。

聲明結(jié)構(gòu)體時也可以不用聲明一個新類型,這樣的結(jié)構(gòu)體類型稱為 匿名結(jié)構(gòu)體(Anonymous Structure)。

//匿名結(jié)構(gòu)體
var employee struct {
    firstName, lastName string
    age int
}
創(chuàng)建命名的結(jié)構(gòu)體
package main

import (  
    "fmt"
)

type Employee struct {  
    firstName, lastName string
    age, salary         int
}

func main() {

    //creating structure using field names
    emp1 := Employee{
        firstName: "Sam",
        age:       25,
        salary:    500,
        lastName:  "Anderson",
    }

    //creating structure without using field names
    emp2 := Employee{"Thomas", "Paul", 29, 800}

    fmt.Println("Employee 1", emp1)
    fmt.Println("Employee 2", emp2)
}

上述代碼中兩種方法創(chuàng)建結(jié)構(gòu)體,第一種無需順序?qū)?yīng)聲明時結(jié)構(gòu)體中的字段,第二種必須順序?qū)?yīng),不能亂序。

創(chuàng)建匿名結(jié)構(gòu)體
package main

import (
    "fmt"
)

func main() {
    emp3 := struct {
        firstName, lastName string
        age, salary         int
    }{
        firstName: "Andreah",
        lastName:  "Nikola",
        age:       31,
        salary:    5000,
    }

    fmt.Println("Employee 3", emp3)
}

之所以稱這種結(jié)構(gòu)體是匿名的,是因為它只是創(chuàng)建一個新的結(jié)構(gòu)體變量 em3,而沒有定義任何結(jié)構(gòu)體類型。

結(jié)構(gòu)體的零值(Zero Value)

當(dāng)定義好的結(jié)構(gòu)體并沒有被顯式地初始化時,該結(jié)構(gòu)體的字段將默認(rèn)賦為零值。

package main

import (  
    "fmt"
)

type Employee struct {  
    firstName, lastName string
    age, salary         int
}

func main() {  
    var emp4 Employee //zero valued structure
    fmt.Println("Employee 4", emp4)
}

string 的零值:(""),int的零值:0

當(dāng)然還可以為某些字段指定初始值,而忽略其他字段。這樣,忽略的字段名會賦值為零值。

訪問結(jié)構(gòu)體的字段

點號操作符 . 用于訪問結(jié)構(gòu)體的字段。

package main

import (  
    "fmt"
)

type Employee struct {  
    firstName, lastName string
    age, salary         int
}

func main() {  
    emp6 := Employee{"Sam", "Anderson", 55, 6000}
    fmt.Println("First Name:", emp6.firstName)
    fmt.Println("Last Name:", emp6.lastName)
    fmt.Println("Age:", emp6.age)
    fmt.Printf("Salary: $%d", emp6.salary)
}

還可以創(chuàng)建零值的 struct,以后再給各個字段賦值。

package main

import (
    "fmt"
)

type Employee struct {  
    firstName, lastName string
    age, salary         int
}

func main() {  
    var emp7 Employee
    emp7.firstName = "Jack"
    emp7.lastName = "Adams"
    fmt.Println("Employee 7:", emp7)
}
結(jié)構(gòu)體的指針

還可以創(chuàng)建指向結(jié)構(gòu)體的指針。

package main

import (  
    "fmt"
)

type Employee struct {  
    firstName, lastName string
    age, salary         int
}

func main() {  
    emp8 := &Employee{"Sam", "Anderson", 55, 6000}
    fmt.Println("First Name:", (*emp8).firstName)
    fmt.Println("Age:", (*emp8).age)
}

Go 語言允許我們在訪問 firstName 字段時,可以使用 emp8.firstName 來代替顯式的解引用 (*emp8).firstName

匿名字段

當(dāng)我們創(chuàng)建結(jié)構(gòu)體時,字段可以只有類型,而沒有字段名。這樣的字段稱為匿名字段(Anonymous Field)。

package main

import (  
    "fmt"
)

type Person struct {  
    string
    int
}

func main() {  
    p := Person{"Naveen", 50}
    fmt.Println(p)
}

雖然匿名字段沒有名稱,但其實匿名字段的名稱就是為它的類型。比如在上面的 Person 結(jié)構(gòu)體里,雖說字段是匿名的,但 Go 默認(rèn)這些字段名是它們各自的類型。所以 Person 結(jié)構(gòu)體有兩個名為 string 和 int 的字段。

可以這樣操作:

func main() {  
    var p1 Person
    p1.string = "naveen"
    p1.int = 50
    fmt.Println(p1)
}
嵌套結(jié)構(gòu)體(Nested Structs)

結(jié)構(gòu)體的字段有可能也是一個結(jié)構(gòu)體。這樣的結(jié)構(gòu)體稱為嵌套結(jié)構(gòu)體。

package main

import (  
    "fmt"
)

type Address struct {  
    city, state string
}
type Person struct {  
    name string
    age int
    address Address
}

func main() {  
    var p Person
    p.name = "Naveen"
    p.age = 50
    p.address = Address {
        city: "Chicago",
        state: "Illinois",
    }
    fmt.Println("Name:", p.name)
    fmt.Println("Age:",p.age)
    fmt.Println("City:",p.address.city)
    fmt.Println("State:",p.address.state)
}
提升字段(Promoted Fields)

匿名字段為結(jié)構(gòu)體的字段稱之為提升字段。

type Address struct {  
    city, state string
}
type Person struct {  
    name string
    age  int
    Address
}

上述代碼中Person結(jié)構(gòu)體中的匿名字段Address也是一個結(jié)構(gòu)體,而Address結(jié)構(gòu)體中有兩個字段,訪問這兩個字段就像在 Person 里直接聲明的一樣,因此我們稱之為提升字段。

package main

import (
    "fmt"
)

type Address struct {
    city, state string
}
type Person struct {
    name string
    age  int
    Address
}

func main() {  
    var p Person
    p.name = "Naveen"
    p.age = 50
    p.Address = Address{
        city:  "Chicago",
        state: "Illinois",
    }
    fmt.Println("Name:", p.name)
    fmt.Println("Age:", p.age)
    fmt.Println("City:", p.city) //city is promoted field
    fmt.Println("State:", p.state) //state is promoted field
}
導(dǎo)出結(jié)構(gòu)體和字段

如果結(jié)構(gòu)體名稱以大寫字母開頭,則它是其他包可以訪問的導(dǎo)出類型(Exported Type)。同樣,如果結(jié)構(gòu)體里的字段首字母大寫,它也能被其他包訪問到。

package computer

type Spec struct { //exported struct  
    Maker string //exported field
    model string //unexported field
    Price int //exported field
}
package main

import "structs/computer"  
import "fmt"

func main() {  
    var spec computer.Spec
    spec.Maker = "apple"
    spec.Price = 50000
    fmt.Println("Spec:", spec)
}

如果訪問未導(dǎo)出的字段 model,編譯器會提示錯誤。

結(jié)構(gòu)體相等性(Structs Equality)

結(jié)構(gòu)體是值類型。如果它的每一個字段都是可比較的,則該結(jié)構(gòu)體也是可比較的。如果兩個結(jié)構(gòu)體變量的對應(yīng)字段相等,則這兩個變量也是相等的。

如果結(jié)構(gòu)體包含不可比較的字段,則結(jié)構(gòu)體變量也不可比較。

先看可比較的示例:

type name struct {
    firstName string
    lastName string
}


func main() {
    name1 := name{"Steve", "Jobs"}
    name2 := name{"Steve", "Jobs"}
    if name1 == name2 {
        fmt.Println("name1 and name2 are equal")
    } else {
        fmt.Println("name1 and name2 are not equal")
    }

    name3 := name{firstName:"Steve", lastName:"Jobs"}
    name4 := name{}
    name4.firstName = "Steve"
    if name3 == name4 {
        fmt.Println("name3 and name4 are equal")
    } else {
        fmt.Println("name3 and name4 are not equal")
    }
}

再看不可比較的示例:

package main

import (  
    "fmt"
)

type image struct {  
    data map[int]int
}

func main() {  
    image1 := image{data: map[int]int{
        0: 155,
    }}
    image2 := image{data: map[int]int{
        0: 155,
    }}
    if image1 == image2 {
        fmt.Println("image1 and image2 are equal")
    }
}

結(jié)構(gòu)體類型 image 包含一個 map 類型的字段。由于 map 類型是不可比較的,因此 image1 和 image2 也不可比較。如果運(yùn)行該程序,編譯器會報錯:main.go:18: invalid operation: image1 == image2 (struct containing map[int]int cannot be compared)。

如果以上文章對你有幫助,記得點贊加關(guān)注作者,后續(xù)持續(xù)更新哦~~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.安裝 https://studygolang.com/dl 2.使用vscode編輯器安裝go插件 3.go語...
    go含羞草閱讀 1,693評論 0 6
  • 出處---Go編程語言 歡迎來到 Go 編程語言指南。本指南涵蓋了該語言的大部分重要特性 Go 語言的交互式簡介,...
    Tuberose閱讀 18,737評論 1 46
  • 官方網(wǎng)站:https://golang.org/標(biāo)準(zhǔn)庫文檔:https://golang.org/pkg/在線編碼...
    技術(shù)學(xué)習(xí)閱讀 2,417評論 2 39
  • 昨天在公交車站等車,看見一小孩子拿了一大包巧克力在吃?一小會就已經(jīng)吃了半袋子。我出于好心說了句:“小孩子巧克力不能...
    夜微購閱讀 650評論 0 0
  • 風(fēng)在外面吹著,你說 爐子里的火苗竄的老高老高 屋里的煙氣小了不少 你往我身邊湊了湊 我聽不到你的心跳 窗外的風(fēng)正大...
    酥白閱讀 281評論 0 3

友情鏈接更多精彩內(nèi)容