索引遍历 slice
func main() {
s := []int{1, 2, 3}
// 只申明索引即可,切片底层是数组,是有顺序的数据结构
for idx := range s {
fmt.Println(idx, "=>", s[idx])
}
}
给定的代码定义了一个整型切片 s,并使用 for range 循环遍历切片,打印每个元素的索引和值。for range 循环遍历切片 s,每次迭代将索引和值分别赋值给变量 idx 和 _(空标识符)。在每次迭代中,打印变量 idx 和切片 s 在索引 idx 处的元素值。
由于切片底层是数组,是有顺序的数据结构,所以遍历切片的顺序与元素在切片中的顺序相同。因此,上述代码输出了索引从 0 到 2 的元素以及它们的值。
索引和临时变量遍历 slice
func main() {
s := []int{1, 2, 3}
// n 是一个循环临时变量,每次循环时把切片对应的数据复制到 n 上
for idx, n := range s {
fmt.Println(idx, "=>", n)
}
}
代码定义了一个整型切片 s,并使用 for range 循环遍历切片,打印每个元素的索引和值。for range 循环遍历切片 s,每次迭代将索引和值分别赋值给变量 idx 和 n。在每次迭代中,打印变量 idx 和变量 n 的值。
由于 n 是一个循环临时变量,每次循环时把切片对应的数据复制到 n 上。因此,输出的结果与上一个问题中的输出结果相同。
理解遍历 slice 临时循环变量
func main() {
s1 := []int{1, 2, 3}
// 切片里是普通对象,每次 n 都复制了它的值,修改不影响原始切片
for idx, n := range s1 {
n = 0
fmt.Println(idx, "=>", n)
// 每次打印 n 的地址,都是一样的地址,说明每次循环都使用的是同一个内存
fmt.Printf("%p\n", &n) // 0xc00001c030 0xc00001c030 0xc00001c030
}
fmt.Println(s1) // [1 2 3]
// 可以使用索引来引用原始的切片对象,对其修改
for idx, n := range s1 {
s1[idx] = 0
fmt.Println(idx, "=>", n)
}
fmt.Println(s1) // [0 0 0]
n1, n2, n3 := 1, 2, 23
s2 := []*int{&n1, &n2, &n3}
// 每次循拷贝的是切片里元素的指针地址,所以对其修改会反映到原始切片上
for idx, n := range s2 {
*n = 0
fmt.Println(idx, "=>", *n)
}
fmt.Println(*s2[0], *s2[1], *s2[2]) // 0 0 0
}
代码定义了一个整型切片 s1 和一个整型指针切片 s2。首先,使用 for range 循环遍历 s1 和 s2,分别打印每个元素的索引和值。在遍历 s1 的循环中,将变量 n 的值赋为 0,但这并不会修改切片中的元素值。然后,使用索引来引用 s1 中的元素,并将其值赋为 0,从而修改了切片中的元素值。接着,使用 for range 循环遍历 s2,将指针指向的值赋为 0,这会直接修改切片中的元素值。
在遍历 s1 的循环中,将变量 n 的值赋为 0,但这并不会修改切片中的元素值,因为 n 是一个循环临时变量,每次循环时把切片对应的数据复制到 n 上。因此,输出的结果与上一个问题中的输出结果相同。同时,每次打印变量 n 的地址都是一样的地址,这说明每次循环都使用的是同一个内存。
在使用索引引用 s1 中的元素时,可以直接修改切片中的元素值。因为切片里是普通对象,每个元素都是有实际值的,因此修改每个元素的值不会影响切片中的其他元素。
在遍历 s2 的循环中,将指针指向的值赋为 0,这会直接修改切片中的元素值。因为切片 s2 中的元素都是指针,每个元素保存了一个变量的地址,因此修改指针指向的值会直接反映到原始变量上。在循环结束后,打印 s2[0]、s2[1] 和 s2[2] 的值,发现它们都被修改为了 0。
遍历 map 对象
func main() {
m := map[string]int{
"one": 1,
"two": 2,
"three": 3,
}
for k, v := range m {
fmt.Println(k, "=>", v)
}
for k := range m {
fmt.Println(k, "=>", m[k])
}
}
代码定义了一个字符串类型到整型的映射 m,使用 for range 循环遍历 m,分别打印每个键和对应的值。然后,使用 for range 循环遍历 m 中的键,使用键来访问对应的值,并打印键和值。
在第一个循环中,使用 for range 遍历 m,每次迭代将键和值分别赋值给变量 k 和 v。在每次迭代中,打印变量 k 和 v 的值。因为映射类型是无序的,所以键值对的顺序可能与定义时的顺序不同。
在第二个循环中,使用 for range 遍历 m 中的键,每次迭代将键赋值给变量 k。在每次迭代中,使用键 k 来访问映射 m 中对应的值,并打印键和值。
由于映射 m 是以字符串为键的,因此两个循环中的输出结果是相同的,分别打印了每个键和对应的值。
不稳定的 map 遍历顺序
func main() {
m := map[string]int{
"one": 1,
"three": 3,
"two": 2,
}
/*
和申明顺序不同
three => 3
two => 2
one => 1
*/
for k, v := range m {
fmt.Println(k, "=>", v)
}
/*
也和上次遍历顺序不同
one => 1
three => 3
two => 2
*/
for k := range m {
fmt.Println(k, "=>", m[k])
}
// 对 key 值进行排序后可获得稳定的遍历顺序
var keys []string
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
/*
one => 1
three => 3
two => 2
*/
for _, k := range keys {
fmt.Println(k, "=>", m[k])
}
}
代码定义了一个字符串类型到整型的映射 m,首先使用 for range 循环遍历 m,打印每个键和对应的值。因为映射类型是无序的,所以键值对的顺序可能与定义时的顺序不同。接着,再次使用 for range 循环遍历 m 中的键,打印每个键和对应的值。由于映射 m 是无序的,所以这两次遍历输出的顺序可能不同。最后,定义一个字符串切片 keys,并将 m 中的键按照字母顺序存入切片中。然后,使用 for range 循环遍历切片 keys,打印每个键和对应的值,这会获得稳定的遍历顺序。
在第一个循环中,使用 for range 遍历 m,每次迭代将键和值分别赋值给变量 k 和 v。在每次迭代中,打印变量 k 和 v 的值。因为映射类型是无序的,所以键值对的顺序可能与定义时的顺序不同。
在第二个循环中,使用 for range 遍历 m 中的键,每次迭代将键赋值给变量 k。在每次迭代中,使用键 k 来访问映射 m 中对应的值,并打印键和值。
由于映射 m 是无序的,所以这两次遍历输出的顺序可能不同。
在第三个循环中,首先定义一个字符串切片 keys,并将 m 中的键按照字母顺序存入切片中。然后,使用 for range 遍历切片 keys,每次迭代将当前元素的值赋值给变量 k。在每次迭代中,使用键 k 来访问映射 m 中对应的值,并打印键和值。由于 keys 中的键按照字母顺序存入,所以这个遍历输出的顺序是稳定的。
遍历 slice 时修改遍历对象
func main() {
s := []int{1, 2, 3}
for idx := range s {
s = append(s, 0) // 不会影响到当前遍历
fmt.Println(idx, "=>", s[idx])
}
fmt.Println(s) // [1 2 3 0 0 0]
}
代码定义了一个整型切片 s,使用 for range 循环遍历 s,分别打印每个索引和对应的值,并在每次迭代中使用 append 方法在切片 s 的末尾添加一个值为 0 的新元素。在循环结束后,打印整个切片 s 的值。
在循环中,虽然每次迭代都使用 append 方法在切片 s 的末尾添加一个新元素,但这不会影响到当前的迭代。因为 for range 循环会使用切片 s 当前的长度和容量来确定循环次数和每次迭代的元素值,而在循环中添加新元素不会影响切片的长度和容量。因此,输出结果与切片定义时的元素值相同,即 [1 2 3]。
在循环结束后,打印整个切片 s 的值,可以看到在循环中添加的新元素已经被加入到切片的末尾,即输出结果为 [1 2 3 0 0 0]。
遍历 map 时修改遍历对象
func main() {
m1 := map[string]int{
"one": 1,
"two": 2,
"three": 3,
}
for k, v := range m1 {
// 对 m 的修改不会影响当前遍历
delete(m1, k)
fmt.Println(k, "=>", v)
}
fmt.Println(m1) // map[]
m2 := map[string]int{
"one": 1,
"two": 2,
"three": 3,
}
/*
three => 4
one => 2
two => 3
*/
for k, v := range m2 {
m2[k] = v + 1
fmt.Println(k, "=>", m2[k])
}
fmt.Println(m2) // map[one:2 three:4 two:3]
}
代码定义了两个字符串类型到整型的映射 m1 和 m2。首先,使用 for range 循环遍历 m1,分别打印每个键和对应的值,并在每次迭代中使用 delete 方法删除当前迭代的键值对。在循环结束后,打印整个映射 m1 的值,可以看到 m1 已经被清空。
接着,使用 for range 循环遍历 m2,分别打印每个键和对应的值,并在每次迭代中将当前迭代的值加上 1,并将结果赋值给对应的键。在循环结束后,打印整个映射 m2 的值。
在第一个循环中,使用 for range 遍历 m1,每次迭代将键和值分别赋值给变量 k 和 v。在每次迭代中,使用 delete 方法删除键为 k 的键值对,并打印变量 k 和 v 的值。因为映射类型是无序的,所以键值对的顺序可能与定义时的顺序不同。循环结束后,打印整个映射 m1 的值,可以看到 m1 已经被清空。
在第二个循环中,使用 for range 遍历 m2,每次迭代将键和值分别赋值给变量 k 和 v。在每次迭代中,将当前迭代的值加上 1,并将结果赋值给对应的键 k。然后,打印变量 k 和修改后的 m2[k] 的值。因为映射类型是无序的,所以键值对的顺序可能与定义时的顺序不同。循环结束后,打印整个映射 m2 的值,可以看到映射中的每个值都加了 1。