• Go语言基础(三)


    一.切片类型

    1.什么是切片

    切片是由数组建立的一种方便、灵活且功能强大的包装(Wrapper)。切片本身不拥有任何数据。它们只是对现有数组的引用

    2.切片的第一种方式:用数组创建一个切片

    // 切片定义的第一种方式:由数组切出来
    var a [5]int= [5]int{1,2,3}
    // 切片是对数组的引用
    var b []int=a[1:4]  // 数组的切片没有步长
    fmt.Println(b)
    //a[2] = 30  // 底层数组会影响切片
    //fmt.Println(b)
    b[0] = 20  // 改变切片会影响底层数组
    fmt.Println(a)

    修改切片的值会影响到底层数组,并且修改底层数组也会影响到切片

    切片的空值是nil类型

    // 切片的空值是nil类型
    var a[]int
    if a == nil{
        fmt.Println("nil类型")
    }
    a[0] = 10  // 空值也不能赋值
    fmt.Println(a)

    3.切片的第二种方式:用make创建一个切片

    func make([]T,len,cap)[]T 通过传递类型,长度和容量来创建切片。容量是可选参数, 默认值为切片长度。make 函数创建一个数组,并返回引用该数组的切片。

    // 第二种方式:直接定义,第一个数字是切片的大小,第二个数字是底层数组的长度,也就是切片的容量
    var a[]int = make([]int,4,5)
    fmt.Println(a)

    4.切片的长度(len)和容量(cap)

    切片追加值与查看长度和容量

    // 切片的长度(len)和容量(cap)
    var a [5]int= [5]int{1,2,3,4,5}
    var b []int=a[1:4]
    // 切片追加值
    b = append(b,999)  // 长度加1,容量不变
    fmt.Println(b)
    fmt.Println(a)  // 底层数组也会跟着改变
    fmt.Println(len(b))  // 长度4
    fmt.Println(cap(b))  // 容量4

    当切片追加超过容量的值时,会创建一个新的数组。现有数组的元素被复制到这个新数组中,并返回这个新数组的新切片引用。并且这个新切片的容量是原来切片的两倍

    // 切片追加超过容量的值
    b = append(b,888)
    fmt.Println(len(b))  // 长度5,超过了容量
    fmt.Println(cap(b))  // 容量8,拷贝了原容量数组,并生成了一个原容量两倍的新数组
    b[0] = 777
    fmt.Println(b)
    fmt.Println(a)  // 此时修改切片不会影响底层数组,他们间的关系断了,反之亦然,修改数组也不会英雄切片

    5.切片的函数传递

    我们可以认为,切片在内部可由一个结构体类型表示。

    切片的数据结构展示:

    type slice struct {  
        Length        int
        Capacity      int
        ZerothElement *byte
    }

    切片包含长度、容量和指向数组第零个元素的指针。当切片传递给函数时,即使它通过值传递,指针变量也将引用相同的底层数组。因此,当切片作为参数传递给函数时,函数内所做的更改也会在函数外可见。让我们写一个程序来检查这点。

    // 切片的函数传递
    a := make([]int,5)
    test(a)
    fmt.Println(a)

    test函数

    func test(a []int) {
        a[0] = 999
        fmt.Println(a)
    }

    当我们在函数中修改切片的值时,外部的切片也会随之更改

    6.多维切片

    类似于数组,切片可以有多个维度。

    // 多维切片
    var a[][]string = make([][]string,3)
    fmt.Println(a)  // 长度为3的切片,有三个空值的切片元素
    a[0] = make([]string,2)
    a[0][0] = "sxc"
    a[0][1] = "age"
    fmt.Println(a)  // 长度为3的切片,并且第一个元素是一个长度为2的切片

    循环多维数组

    // 循环多维切片
    var a = [][]string{{"sxc","cool"},{"zzj","dsb"}}
    //fmt.Println(a)
    for _,v := range a{
        for _,v1 := range v{  // 循环两次
            fmt.Println(v1)
        }
    }

    7.切片的第三种方式:切片初始化时直接赋值

    // 第三种方式:切片初始化,直接赋值
    var a[]int = []int{1,2,3}
    fmt.Println(a)
    fmt.Println(len(a))  // 长度为3
    fmt.Println(cap(a))  // 容量也为3

    多维数组初始化时直接赋值

    // 多维切片初始化
    var a[][]string = [][]string{{"sxc","cool"},{"zzj","dsb"}}
    fmt.Println(a)
    a[0][0] = "zzp"
    fmt.Println(a)

    8.切片修改值只跟切片的长度有关,跟容量无关

    只能修改长度以内的切片

    // 修改值只跟长度有关
    var a[]int = []int{1,2}
    a[0] = 10
    fmt.Println(a)
    //a[2] = 30  // 超过长度,不能修改
    a = append(a, 30)
    a[2] = 50
    fmt.Println(a)  // 可以修改

    9.内存优化

    切片持有对底层数组的引用。只要切片在内存中,数组就不能被垃圾回收。在内存管理方面,这是需要注意的。让我们假设我们有一个非常大的数组,我们只想处理它的一小部分。然后,我们由这个数组创建一个切片,并开始处理切片。这里需要重点注意的是,在切片引用时数组仍然存在内存中。

    一种解决方法是使用 [copy] 函数 func copy(dst,src[]T)int 来生成一个切片的副本。这样我们可以使用新的切片,原始数组可以被垃圾回收。

    // 内存优化,copy函数
    var a[]int = make([]int,3,10000)
    a[0] = 5
    a[1] = 6
    fmt.Println(a)
    //b:= make([]int,2,6)  // 当长度少时,只取前几个
    b:= make([]int,4,6)  // 当长度多时,后面用0补充
    copy(b, a)
    fmt.Println(b)

    如上述切片,他的长度只有三,但是他引用的底层数组长度却有10000,此时使用该切片就十分耗费内存,我们可以使用copy函数来进行内存优化

    二.可变参数函数

    可变参数函数是一种参数个数可变的函数。

    语法:

    如果函数最后一个参数被记作 ...T ,这时函数可以接受任意个 T 类型参数作为最后一个参数。

    请注意只有函数的最后一个参数才允许是可变的。

    func main() {
        var a = []int{1,2,3,4}
        //test1(1,2,3,4)
        test1(a...)  // 相当于打散
    }
    
    func test1(a ...int) {
        fmt.Println(a)
    }

    三.Map

    1.什么是map

    map 是在 Go 中将值(value)与键(key)关联的内置类型。通过相应的键可以获取到值。类似于python中的字典

    2.如何创建map

    //语法
    // map类型的key必须可hash
    var a map[key类型]value值类型
    var a map[int]string
    fmt.Println(a)  // map也是一个引用类型,空值也是nil

    3.第一种创建方式:使用make初始化

    // map使用make初始化
    var a = make(map[int]string)
    a[0] = "sxc"
    fmt.Println(a[0])
    // 取不存在的值会返回value类型的空值
    fmt.Println(a[1])

    map的空值,就是value对应的类型的空值,比如int就是0

    // 判断空值
    if a[1]==""{  // 字符串的空值就是空字符串
        fmt.Println("空值")
    }
    //可以使用该方法来判断是否为空值,因为每个类型的的空值的类型都是不一样的
    if v,ok := a[1];ok{
        fmt.Println(v)
    }else{
        fmt.Println("空值")
    }

    3.第二种创建方式:直接赋值

    // 初始化第二种方式,直接赋值
    var a = map[int]string{0:"sxc",1:"zzj"}
    fmt.Println(a)

    4.map删除值:delete方法

    删除 map 中 key 的语法是 [delete(map, key)]。这个函数没有返回值。

    // map删除值,内置函数delete
    delete(a, 1)
    fmt.Println(a)

    5.获取map的长度

    获取 map 的长度使用 [len]函数。

    // map长度,len
    fmt.Println(len(a))

    6.map是引用类型

    和 [slices]类似,map 也是引用类型。当 map 被赋值为一个新变量的时候,它们指向同一个内部数据结构。因此,改变其中一个变量,就会影响到另一变量。

    // map是引用类型,函数中修改也会影响
    test5(a)
    fmt.Println(a)

    test函数中修改值,原值也会跟着改变

    func test5(a map[int]string)  {
        a[0] = "zzp"
    }

    7.map的相等性

    map 之间不能使用 == 操作符判断,== 只能用来检查 map 是否为 nil

    判断两个 map 是否相等的方法是遍历比较两个 map 中的每个元素。

    8.扩展

    map是无序的,可以通过逻辑设计成有序的

    m := make(map[int]string)
    var l []int
    m,l = add(1,"sxc", m, l)
    m,l = add(2,"zzj", m, l)
    m,l = add(3,"zzp", m, l)
    m,l = add(4,"lzx", m, l)
    m,l = add(5,"yzy", m, l)
    fmt.Println(m)
    fmt.Println(l)
    for _,v := range l{
        fmt.Println(m[v])
    }
    func add(b int, c string,m map[int]string,l []int) (map[int]string,[]int){
        m[b] = c
        l = append(l,b)
        return m,l
    }

    四.字符串

    1.什么是字符串

    Go 语言中的字符串是一个字节切片。把内容放在双引号""之间,我们可以创建一个字符串。让我们来看一个创建并打印字符串的简单示例。

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        name := "Hello World"
        fmt.Println(name)
    }

    Go 中的字符串是兼容 Unicode 编码的,并且使用 UTF-8 进行编码

    2.字符串的字节数

    name := "hello 你好"
    fmt.Println(len(name)) // 统计字节数,12

    上述代码中的name由5个英文字符(5*1共5个字节),一个空格(1个字节),两个中文字符组成(2*3共6个字符),故字节数是12

    循环获取每个字节代码的十进制数字,都是unit8类型,也就是byte类型

    // 字符串的循环,字符串是个只读切片
    name := "hello你"
    for i:=0;i<len(name);i++{
        fmt.Println(name[i])
        fmt.Printf("%T",name[i])  // uint8也就是byte类型
        fmt.Println()
    }

    3.字符串的字符数

    name := "hello 你好"
    fmt.Println(utf8.RuneCountInString(name))  // 统计字符数

    上述代码中的name一共由8个字符组成,故字符数是8

    循环获取每个字符代码的十进制数字,都是int32类型,也就是rune类型

    for _,v := range name{
        fmt.Println(v)
        fmt.Printf("%T",v)  // rune也就是int32类型
        fmt.Println()
        fmt.Println(string(v))
    }

    4.字符串的长度

    [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
    ", s, utf8.RuneCountInString(s))
    }
    func main() { 
        word1 := "Señor" 
        length(word1)
        word2 := "Pets"
        length(word2)
    }

    上面程序的输出结果是:

    length of Señor is 5  
    length of Pets is 4

    使用切片的方法拿值

    fmt.Println(string(name[6]))  // 只能拿字符的字节数对应的值,所以中文字符取不到

    5.字符串是不可变的

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

    在上面程序中的第 8 行,我们试图把这个字符串中的第一个字符修改为 'a'。由于字符串是不可变的,因此这个操作是非法的。所以程序抛出了一个错误 main.go:8: cannot assign to s[0]。

    为了修改字符串,可以把字符串转化为一个 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)))
    }

    在上面程序的第 7 行,mutate 函数接收一个 rune 切片参数,它将切片的第一个元素修改为 'a',然后将 rune 切片转化为字符串,并返回该字符串。程序的第 13 行调用了该函数。我们把 h 转化为一个 rune 切片,并传递给了 mutate。这个程序输出 aello

    五.指针

    1.什么是指针

    指针是一种存储变量内存地址(Memory Address)的变量。

     如上图所示,变量 b 的值为 156,而 b 的内存地址为 0x1040a124。变量 a 存储了 b 的地址。我们就称 a 指向了 b

    2.指针的声明(*+变量的类型)

    指针变量的类型为 *T,该指针指向一个 T 类型的变量。

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

    & 操作符用于获取变量的地址。上面程序的第 9 行我们把 b 的地址赋值给 *int 类型的 a。我们称 a 指向了 b。当我们打印 a 的值时,会打印出 b 的地址。程序将输出:

    Type of a is *int  
    address of b is 0x1040a124

    3.指针的零值

    指针的零值是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)
        }
    }

    上面的程序中,b 初始化为 nil,接着将 a 的地址赋值给 b。程序会输出:

    b is <nil>  
    b after initialisation is 0x1040a124

    4.指针的解引用,反解(*+变量)

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

    在上面程序的第 10 行,我们将 a 解引用,并打印了它的值。不出所料,我们会打印出 b 的值。程序会输出:

    address of b is 0x1040a124  
    value of b is 255

    我们再编写一个程序,用指针来修改 b 的值。

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

    在上面程序的第 12 行中,我们把 a 指向的值加 1,由于 a 指向了 b,因此 b 的值也发生了同样的改变。于是 b 的值变为 256。程序会输出:

    address of b is 0x1040a124  
    value of b is 255  
    new value of b is 256

    5.向函数传递指针参数

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

    在上面程序中的第 14 行,我们向函数 change 传递了指针变量 b,而 b 存储了 a 的地址。程序的第 8 行在 change 函数内使用解引用,修改了 a 的值。该程序会输出:

    value of a before function call is 58  
    value of a after function call is 55

    6.不要向函数传递数组的指针,而应该使用切片

    假如我们想要在函数内修改一个数组,并希望调用函数的地方也能得到修改后的数组,一种解决方案是把一个指向数组的指针传递给这个函数。

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

    在上面程序的第 13 行中,我们将数组的地址传递给了 modify 函数。在第 8 行,我们在 modify 函数里把 arr 解引用,并将 90 赋值给这个数组的第一个元素。程序会输出 [90 90 91]

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

    该程序也会输出 [90 90 91]

    这种方式向函数传递一个数组指针参数,并在函数内修改数组。尽管它是有效的,但却不是 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)
    }

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

    7.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)。

    104

  • 相关阅读:
    异常、中断、陷阱
    BigDecimal
    事务
    jsp的九大内置对象
    timer和ScheduledThreadPoolExecutor
    关于Python的导入覆盖解决办法
    RTTI
    Golang不会自动把slice转换成interface{}类型的slice
    Python中下划线的5种含义
    Python如何合并两个字典
  • 原文地址:https://www.cnblogs.com/sxchen/p/12026638.html
Copyright © 2020-2023  润新知