Go language slicing for function parameter passing + append() function expansion

Go language slice function parameter passing + append() function expansion

Given the root node of your binary tree and an integer target and targetSum, find all the paths from the root node to the leaf nodes whose sum is equal to the given target sum.

Binary tree recursive go code:

var ans [][]int
func pathSum(root *TreeNode, targetSum int) ( [][]int) {
    
    
	ans := make([][]int, 0) 
    path := []int{
    
    }
    dfs(root, targetSum,path)
    return ans
}
func dfs(node *TreeNode, left int,path []int) {
    
    
        if node == nil {
    
    
            return
        }
        left -= node.Val
        path = append(path, node.Val)
        if node.Left == nil && node.Right == nil && left == 0 {
    
    
            ans = append(ans, append([]int(nil), path...))//在二维切片中添加一维切片
            return
        }
        dfs(node.Left, left,path)
        dfs(node.Right, left,path)
}

My doubts arise from the code of this question. You can see that in the dfs recursive function, the parameter path slice is used as a variable, and anyone who has learned Go knows that slice is a reference type, so is it passed by reference when the function is passed? .
It can be known that the path records the data that has been traversed so far, and my doubt is that data has been added to the slice path, so why does the modification in the lower-level recursive function not affect the upper-level path after it is passed as a parameter into the path, the following is I look up explanations of many sources.

1. Sliced ​​structure in Go

insert image description here
As shown in the figure, the slice structure contains three parts. The first part is a pointer to the underlying array, followed by the size len of the slice and the capacity cap of the slice.

2. Slicing expansion mechanism

The slice's capacity (cap) represents the maximum length of the underlying array that the slice can use. When the length (len) of the slice exceeds the capacity, the slice will be automatically expanded, that is, a larger underlying array will be allocated and the original data will be copied over. This process is done by the append function, we don't need to do it manually.
According to the comments in the Go language source code, the rules for slice expansion are as follows:

If the original capacity is less than 1024, the new capacity is twice the original capacity;
if the original capacity is greater than or equal to 1024, the new capacity is 1.25 times the
original capacity; It is 1.5 times the original capacity;
if the allocation fails, memory overflow will be triggered.

**When the slice is expanded or shrunk inside the function, it will cause the slice to point to a new underlying array. At this time, the same underlying array will no longer be shared inside and outside the function. ** Therefore, when the append exceeds the original capacity, after changing the content of the slice, it will have no effect on the original array

3. The append function in Go

insert image description here
insert image description here
As can be seen from the figure above, if append is performed,

  1. If the slice is not expanded, then the value pointing to the last bit in the underlying array will be directly added or modified, so the underlying array will be changed;
  2. However, if the expansion is performed, the slice will point to a new underlying array, which will have no effect on the original array

4. There is only one way to pass parameters in Go function, and * must be added to pass the address—in fact, it is also the value of the address variable.

I have seen some blogs saying that slices in Go are passed by address, but this is actually wrong. The
official Go document states: In Go, function parameters can only be passed by value , and * must be added to pass the address, which is actually the value of the passed address variable

Back to the issue of slices as function parameters, because there is only one way of passing parameters in Go, when a slice is used as a parameter, it is actually a copy of the slice, but in the copied slice, the pointer member variable it contains The values ​​of are the same, that is to say they are 指向的底层数组数据源是一样, so modifying the formal parameters within the calling function can affect the actual parameters.

Usually, we refer to the data type that can directly modify the actual parameter by modifying the formal parameter in the process of copying by value as a reference type.
All parameter passing in the Go language is value passing (passing by value), which is a copy, a copy.

因为拷贝的内容有时候是非引用类型(int、string、struct等这些),这样就在函数中就无法修改原内容数据;有的是引用类型(指针、map、slice、chan等这些),这样就可以修改原内容数据。

It should be noted here that reference types and pass-by-references are two concepts.
insert image description here
insert image description here

Summarize:

When slices or arrays are passed as function parameters, the essence is to pass values ​​instead of addresses. Because the slice relies on its underlying array, modifying the slice is essentially modifying the array, and the array has a size limit. When the capacity of the slice is exceeded, that is, when the array is out of bounds, a new array block needs to be created through dynamic programming. Copy the original data to a new array, and this new array is the new underlying dependency of the slice.

Passing by value copies a new slice that also points to the underlying array of the original variable. Whether the function modifies the slice directly or appends to create a new slice, it is based on the underlying array of the shared slice. Whether the outermost original slice is changed depends on the operation in the function and the capacity of the slice itself, whether the underlying array is modified array.

  • If you want to modify the value of the slice, you must modify the underlying array to affect the slice outside the function
  • If it is an append operation, it depends on whether the slice is expanded
    • If the slice is not expanded, then the value pointing to the last bit in the underlying array will be directly added or modified, so the underlying array will be changed, and the slice outside the function will be changed;
    • However, if the expansion is performed, the slice will point to a new underlying array, and all modifications will have no effect on the original slice outside the function
      .

Of course, if you want to modify the original variable, you can specify the type of the parameter as a pointer type. What is passed is the memory address of the slice. The operation in the function is to find the variable itself according to the memory address.

Recursive Code Analysis

func dfs(node *TreeNode, left int,path []int) {
    
    
        if node == nil {
    
    
            return
        }
        left -= node.Val
        path = append(path, node.Val)
        if node.Left == nil && node.Right == nil && left == 0 {
    
    
            ans = append(ans, append([]int(nil), path...))//在二维切片中添加一维切片
            return
        }
        dfs(node.Left, left,path)
        dfs(node.Right, left,path)
}

Therefore, in the recursive function dfs, we must first know that each recursion is from left to right, and a path will be returned after recursion, so the first recursion will cause the path to reach the leaf node. And the definition of the path is represented by path, which is easy to know path的长度代表着当前遍历结点的深度.
The parameter path passed each time is the slice value passed, not the address of the path, so they are all different slice values, but the underlying array pointed to is the same, and each time append is performed inside the function, it may expand or may no expansion

  1. If the path is expanded after append, the slice will point to a new underlying array. At this time, the internal path of the function and the external path will no longer share the same underlying array, so the path of the upper recursive function will not be affected.
  2. If append does not lead to path expansion, then the slice will be directly added or modified to point to the last value in the underlying array;
    • If it is the first time to traverse to the current depth, then it will add a value to the underlying array, then add it directly, and judge whether the current node has a subtree, if there is no subtree, add it to ans
    • If the current depth has been traversed, the value of the last bit of the slice of the underlying array should be modified, which will indeed affect the underlying array of the path, but has no effect on the answer ans sought, because the path before the modification has already been recursive before Added in ans; it has no effect on the path in the subsequent recursion, because the subsequent path will also modify the path value of the depth when traversing to the current depth for append

Guess you like

Origin blog.csdn.net/qq_45808700/article/details/129960501