Go基础语法(五)

作者: kakarotto | 来源:发表于2018-11-17 10:33 被阅读1次

    字符串

    • Go 语言中的字符串是一个字节切片。把内容放在双引号""之间,我们可以创建一个字符串。
    • Go 中的字符串是兼容 Unicode 编码的,并且使用 UTF-8 进行编码。
    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,进行切片遍历,可以发现输出的字节是不对的:S e à ± o r。
    这是因为 ñ 的 Unicode 代码点(Code Point)是 U+00F1。它的 UTF-8 编码占用了 c3 和 b1 两个字节。它的 UTF-8 编码占用了两个字节 c3 和 b1。而我们打印字符时,却假定每个字符的编码只会占用一个字节,这是错误的。在 UTF-8 编码中,一个代码点可能会占用超过一个字节的空间。那么我们该怎么办呢?rune 能帮我们解决这个难题。

    rune 是 Go 语言的内建类型,它也是 int32 的别称。在 Go 语言中,rune 表示一个代码点。代码点无论占用多少个字节,都可以用一个 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 行,字符串被转化为一个 rune 切片。然后我们循环打印字符。

    字符串的 for range 循环
    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 循环遍历了字符串。循环返回的是是当前 rune 的字节位置。

    字符串的长度

    utf8 package 包中的 func RuneCountInString(s string) (n int) 方法用来获取字符串的长度。这个方法传入一个字符串参数然后返回字符串中的 rune 的数量。

    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 中的字符串是不可变的。一旦一个字符串被创建,那么它将无法被修改。

    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))
    }
    

    为了修改字符串,可以把字符串转化为一个 rune 切片。然后这个切片可以进行任何想要的改变,然后再转化为一个字符串。

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

    指针

    学过C语言的知道,指针是一种存变量内存地址的变量。


    image.png

    如上图所示,变量 b 的值为 156,而 b 的内存地址为 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)
    }
    
    运行结果:
    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)
        }
    }
    
    指针的解引用

    指针的解引用可以获取指针所指向的变量的值。将 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的值。

    向函数传递指针参数
    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)
    }
    
    不要向函数传递数组的指针,而应该使用切片
    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)
    }
    

    这种方式向函数传递一个数组指针参数,并在函数内修改数组。尽管它是有效的,但却不是 Go 语言惯用的实现方式。我们最好使用切片来处理。

    接下来我们用切片来重写之前的代码:

    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 函数。在 modify 函数中,我们把切片的第一个元素修改为 90。程序也会输出 [90 90 91]。所以别再传递数组指针了,而是使用切片吧。上面的代码更加简洁,也更符合 Go 语言的习惯。

    Go 不支持指针运算

    Go 并不支持其他语言(例如 C)中的指针运算。

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

    上面的程序会抛出编译错误:main.go:6: invalid operation: p++ (non-numeric type *[3]int)

    结构体

    什么是结构体?

    结构体是用户定义的类型,表示若干个字段(Field)的集合。有时应该把数据整合在一起,而不是让这些数据没有联系。这种情况下可以使用结构体。

    声明结构体

    例如,一个职员有 firstName、lastName 和 age 三个属性,而把这些属性组合在一个结构体 employee 中就很合理。

    type Employee struct {
        firstName string
        lastName  string
        age       int
    }
    

    还可以这样声明:

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

    上面的结构体 Employee 称为 命名的结构体(Named Structure)。我们创建了名为 Employee 的新类型,而它可以用于创建 Employee 类型的结构体变量

    声明结构体时也可以不用声明一个新类型,这样的结构体类型称为 匿名结构体(Anonymous Structure)

    //匿名结构体
    var employee struct {
        firstName, lastName string
        age int
    }
    
    创建命名的结构体
    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)
    }
    

    上述代码中两种方法创建结构体,第一种无需顺序对应声明时结构体中的字段,第二种必须顺序对应,不能乱序。

    创建匿名结构体
    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)
    }
    

    之所以称这种结构体是匿名的,是因为它只是创建一个新的结构体变量 em3,而没有定义任何结构体类型。

    结构体的零值(Zero Value)

    当定义好的结构体并没有被显式地初始化时,该结构体的字段将默认赋为零值。

    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

    当然还可以为某些字段指定初始值,而忽略其他字段。这样,忽略的字段名会赋值为零值。

    访问结构体的字段

    点号操作符 . 用于访问结构体的字段。

    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)
    }
    

    还可以创建零值的 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)
    }
    
    结构体的指针

    还可以创建指向结构体的指针。

    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

    匿名字段

    当我们创建结构体时,字段可以只有类型,而没有字段名。这样的字段称为匿名字段(Anonymous Field)。

    package main
    
    import (  
        "fmt"
    )
    
    type Person struct {  
        string
        int
    }
    
    func main() {  
        p := Person{"Naveen", 50}
        fmt.Println(p)
    }
    

    虽然匿名字段没有名称,但其实匿名字段的名称就是为它的类型。比如在上面的 Person 结构体里,虽说字段是匿名的,但 Go 默认这些字段名是它们各自的类型。所以 Person 结构体有两个名为 string 和 int 的字段。

    可以这样操作:

    func main() {  
        var p1 Person
        p1.string = "naveen"
        p1.int = 50
        fmt.Println(p1)
    }
    
    嵌套结构体(Nested Structs)

    结构体的字段有可能也是一个结构体。这样的结构体称为嵌套结构体。

    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)

    匿名字段为结构体的字段称之为提升字段。

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

    上述代码中Person结构体中的匿名字段Address也是一个结构体,而Address结构体中有两个字段,访问这两个字段就像在 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
    }
    
    导出结构体和字段

    如果结构体名称以大写字母开头,则它是其他包可以访问的导出类型(Exported Type)。同样,如果结构体里的字段首字母大写,它也能被其他包访问到。

    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)
    }
    

    如果访问未导出的字段 model,编译器会提示错误。

    结构体相等性(Structs Equality)

    结构体是值类型。如果它的每一个字段都是可比较的,则该结构体也是可比较的。如果两个结构体变量的对应字段相等,则这两个变量也是相等的。

    如果结构体包含不可比较的字段,则结构体变量也不可比较。

    先看可比较的示例:

    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")
        }
    }
    

    结构体类型 image 包含一个 map 类型的字段。由于 map 类型是不可比较的,因此 image1 和 image2 也不可比较。如果运行该程序,编译器会报错:main.go:18: invalid operation: image1 == image2 (struct containing map[int]int cannot be compared)。

    如果以上文章对你有帮助,记得点赞加关注作者,后续持续更新哦~~~~

    相关文章

      网友评论

        本文标题:Go基础语法(五)

        本文链接:https://www.haomeiwen.com/subject/pzkmfqtx.html