Golang中slice操作的一些问题

package go_tests

import (
    "bytes"
    "fmt"
    "testing"
)

// 值类型与引用类型
func TestT55(t *testing.T) {

    a := 123
    b := a
    // 指向不同的内存地址
    fmt.Printf("a: %d, %p \n", a, &a) // a: 123, 0xc00000a338
    fmt.Printf("b: %d, %p \n", b, &b) // b: 123, 0xc00000a340

    // 修改b不会影响a
    b += 100
    fmt.Printf("a: %d, %p \n", a, &a) // a: 123, 0xc00000a338
    fmt.Printf("b: %d, %p \n", b, &b) // b: 223, 0xc00000a340

    // 但是引用类型就不是:
    s1 := []int{1, 2, 3}
    s2 := s1
    // 内存地址一样
    fmt.Printf("s1: %v, %p \n", s1, s1) // s1: [1 2 3], 0xc0000a2090
    fmt.Printf("s2: %v, %p \n", s2, s2) // s2: [1 2 3], 0xc0000a2090

    // 修改s2也会修改s1
    s2[0] = 100
    fmt.Printf("s1: %v, %p \n", s1, s1) // s1: [100 2 3], 0xc0000a2090
    fmt.Printf("s2: %v, %p \n", s2, s2) // s2: [100 2 3], 0xc0000a2090

    // TODO 但是,append以后如果修改了s2的容量,系统会重新分配buffer,此时s2跟s1是不一样的
    s2 = append(s2, 123, 222, 33, 344)
    fmt.Printf("s1: %v, %p \n", s1, s1) // s1: [100 2 3], 0xc0000a2090
    fmt.Printf("s2: %v, %p \n", s2, s2) // s2: [100 2 3 123 222 33 344], 0xc00000e200

}

// 0、引用类型2:函数中也能修改
func TestT0(t *testing.T) {

    s1 := []int{1, 2, 3}
    f1 := func(s []int) {
        s[0] += 10
    }
    f1(s1)

    fmt.Println("s1: ", s1) // s1:  [11 2 3]

}

// 4、旧slice
// 当你从一个已存在的 slice 创建新 slice 时,二者的数据指向相同的底层数组。如果你的程序使用这个特性,那需要注意 "旧"(stale) slice 问题。
// 某些情况下,向一个 slice 中追加元素而它指向的底层数组容量不足时,将会重新分配一个新数组来存储数据。而其他 slice 还指向原来的旧底层数组。
func TestT41(t *testing.T) {
    s1 := []int{1, 2, 3}
    fmt.Println("s1: ", len(s1), cap(s1), s1) // s1:  3 3 [1 2 3]

    s2 := s1[1:]
    fmt.Println("s2: ", len(s2), cap(s2), s2) // s2:  2 2 [2 3]

    for i, _ := range s2 {
        s2[i] += 10
    }
    // // 此时的 s1 与 s2 是指向同一个底层数组的
    fmt.Println("s1: ", s1) // s1:  [1 12 13]
    fmt.Println("s2: ", s2) // s2:  [12 13]

    // 向容量为 2 的 s2 中再追加元素,此时将分配新数组来存
    s2 = append(s2, 4, 5, 6)
    for i, _ := range s2 {
        s2[i] *= 100
    }
    fmt.Println("s1: ", s1) // s1:  [1 12 13]  // 此时的 s1 不再更新,为旧数据
    fmt.Println("s2: ", s2) // s2:  [1200 1300 400 500 600]

}

// 1、range迭代更新slice的元素
func TestT11(t *testing.T) {
    s1 := []int{1, 2, 3}
    for _, val := range s1 {
        val += 10
    }
    fmt.Println("one s1: ", s1) // one s1:  [1 2 3]

    for i, _ := range s1 {
        s1[i] += 10
    }
    fmt.Println("two s1: ", s1) // two s1:  [11 12 13]

    // slice中是结构体
    type stru1 struct{ num int }
    ss1 := []stru1{{1}, {2}, {3}}
    for _, val := range ss1 {
        val.num += 1
    }
    fmt.Println("one ss1: ", ss1) //one ss1:  [{1} {2} {3}]

    for i, _ := range ss1 {
        ss1[i].num += 1
    }
    fmt.Println("two ss1: ", ss1) // two ss1:  [{2} {3} {4}]

    // 结构体指针
    ss2 := []*stru1{{22}, {33}, {44}}
    for _, val := range ss2 {
        val.num += 10
    }
    fmt.Println("one ss2: ")
    for _, val := range ss2 {
        fmt.Println("> ", val.num)
    }
    /*
        >  32
        >  43
        >  54
    */
}

// 2、slice中隐藏的数据
// 从 slice 中重新切出新 slice 时,新 slice 会引用原 slice 的底层数组。
// 如果跳了这个坑,程序可能会分配大量的临时 slice 来指向原底层数组的部分数据,将导致难以预料的内存使用。
func TestT22(t *testing.T) {
    s1 := make([]string, 5, 10)
    s1 = []string{"whw", "naruto", "sasuke", "whw2", "whw3", "whw4"}
    fmt.Println("s1: ", len(s1), cap(s1), &s1[0]) // s1:  6 6 0xc000064120

    s2 := s1[:3]
    fmt.Println("s2: ", len(s2), cap(s2), &s2[0]) // s2:  3 6 0xc000064120

    // 过拷贝临时 slice 的数据,而不是重新切片来解决
    s3 := make([]string, 3)
    copy(s3, s1[:3])
    fmt.Println("s3: ", len(s3), cap(s3), &s3[0]) // s3:  3 3 0xc000076780
}

// 3、slice中数据的误用
// 举个简单例子,重写文件路径(存储在 slice 中)
// 分割路径来指向每个不同级的目录,修改第一个目录名再重组子目录名,创建新路径:
// 3-1、错误的方法
func TestT31(t *testing.T) {
    path := []byte("AA/BBB")               // 目标:AAsu/BBB
    println("cap_path: ", cap(path))       // 6 TODO
    sepIndex := bytes.IndexByte(path, '/') // 2
    println(sepIndex)

    // 注意:dir1、 dir2 两个 slice 引用的数据都是 path 的底层数组
    dir1 := path[:sepIndex]
    dir2 := path[sepIndex+1:]
    println("cap_dir1: ", cap(dir1)) // 6 TODO
    println("dir11: ", string(dir1)) // AA
    println("dir21: ", string(dir2)) // BBB

    dir1 = append(dir1, "su"...)
    println("current path: ", string(path)) // AAsuBB —— 这步就有问题:修改 dir1 同时也修改了 path,也导致了 dir2 的修改

    println("dir12: ", string(dir1)) // AAsu
    println("dir22: ", string(dir2)) // uBB

    path = bytes.Join([][]byte{dir1, dir2}, []byte{'/'})
    //println("dir13: ", string(dir1)) // AAsu
    //println("dir23: ", string(dir2)) // uBB

    println("new path: ", string(path)) // AAsu/uBB    // 错误结果
}

// 3-2、正确的方法
func TestT32(t *testing.T) {
    path := []byte("AA/BBB")
    println("cap_path: ", cap(path))       // 6
    sepIndex := bytes.IndexByte(path, '/') // 2
    println(sepIndex)

    // TODO 第三个参数是用来控制 dir1 的新容量,再往 dir1 中 append 超额元素时,将分配新的 buffer 来保存。而不是覆盖原来的 path 底层数组
    dir1 := path[:sepIndex:sepIndex] // 此时 cap(dir1) 为2,不是原先的6
    dir2 := path[sepIndex+1:]
    println("dir11: ", string(dir1)) // AA
    println("dir21: ", string(dir2)) // BBB

    dir1 = append(dir1, "su"...)

    println("dir21: ", string(dir1)) // AAsu
    println("dir22: ", string(dir2)) // BBB

    path = bytes.Join([][]byte{dir1, dir2}, []byte{'/'})

    println("new_path: ", string(path)) // AAsu/BBB

}

~~~

posted on 2022-08-21 11:04  江湖乄夜雨  阅读(49)  评论(0编辑  收藏  举报