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