返回

揭秘 Go 函数入参值拷贝前提下的 Slice 和 Map 不同表现差异

后端

在 Go 语言中,函数入参默认采用值拷贝的方式进行传递。这意味着函数内对入参所做的修改不会影响函数外的原变量。然而,当入参为 Slice 或 Map 类型时,情况却有所不同。本文将对这种差异进行深入分析,揭示其背后的原理和影响。

函数值拷贝概述

在 Go 语言中,函数入参默认采用值拷贝的方式进行传递。这意味着函数内对入参所做的修改不会影响函数外的原变量。值拷贝是指将变量的值复制一份传递给函数,函数内对该拷贝进行修改不会影响原变量。这种方式可以保证函数的独立性,防止函数内的修改对调用者造成意外影响。

Slice 和 Map 的特殊性

Slice 和 Map 是 Go 语言中两种特殊的复合数据类型。Slice 是对数组的封装,提供了动态调整长度的功能;Map 是键值对的集合,可以根据键快速查找对应的值。这两者在函数入参时的表现与其他类型略有不同。

对于 Slice 来说,函数内对入参所做的修改会影响函数外的原变量。这是因为 Slice 本身只存储了对底层数组的引用,而并非数组本身。当函数内对入参 Slice 进行修改时,实际上是对底层数组进行修改,自然会影响函数外的原变量。

对于 Map 来说,函数内对入参所做的修改不会影响函数外的原变量。这是因为 Map 本身存储了键值对的数据,而不是对底层数据的引用。当函数内对入参 Map 进行修改时,实际上是在创建新的键值对,而不会影响函数外的原变量。

示例代码

以下代码演示了 Slice 和 Map 在函数入参时的不同表现:

package main

import "fmt"

func main() {
    s := []int{1, 2, 3}
    m := map[string]int{"a": 1, "b": 2}

    reverse(s) // reverse 函数修改了 s
    fmt.Println(s) // 输出:[3 2 1]

    updateMap(m) // updateMap 函数修改了 m
    fmt.Println(m) // 输出:map[a:1 b:2]
}

func reverse(s []int) {
    for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
        s[i], s[j] = s[j], s[i]
    }
}

func updateMap(m map[string]int) {
    m["c"] = 3
}

在该代码中,reverse 函数对入参 Slice s 进行修改,而在 main 函数中可以看到,修改后的 s 也随之改变。这是因为 Slice 在函数内是通过引用传递的,函数内对入参的修改会影响函数外的原变量。

updateMap 函数对入参 Map m 进行修改,但在 main 函数中可以看到,修改后的 m 没有变化。这是因为 Map 在函数内是通过值拷贝传递的,函数内对入参的修改不会影响函数外的原变量。

总结

综上所述,Go 语言中函数入参默认采用值拷贝的方式进行传递。然而,当入参为 Slice 或 Map 类型时,情况却有所不同。Slice 在函数内是通过引用传递的,函数内对入参的修改会影响函数外的原变量;而 Map 在函数内是通过值拷贝传递的,函数内对入参的修改不会影响函数外的原变量。这种差异源于 Slice 和 Map 在内存中的存储方式和操作行为的不同。