索引遍历 slice

在线运行open in new window启动 AI 助手open in new window

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

在线运行open in new window启动 AI 助手open in new window

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 临时循环变量

在线运行open in new window启动 AI 助手open in new window

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 对象

在线运行open in new window启动 AI 助手open in new window

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 遍历顺序

在线运行open in new window启动 AI 助手open in new window

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 时修改遍历对象

在线运行open in new window启动 AI 助手open in new window

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 时修改遍历对象

在线运行open in new window启动 AI 助手open in new window

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。

Last Updated:
Contributors: Bob Wang