Golang에서 슬라이스에 추가하는 동안 문제가 발생했습니다.

3135 단어 gogotchas
슬라이스를 함수에 전달하고 슬라이스의 요소를 변경하려고 하면 제대로 작동합니다. 그러나 함수 내에서 슬라이스하기 위해 새 요소를 추가하려고 하면 추가된 요소가 호출자 함수에 표시되지 않습니다.

package main

import (
    "fmt"
)

func main() {
    var slice = make([]int, 3, 4)
    fmt.Println(slice) //[0,0,0]

    slice[0] = 1
    fmt.Println(slice) //[1 0 0]

    slice = append(slice, 4) // works, new element gets added to the slice
    fmt.Println(slice)       // [1 0 0 4]

    modifySlice(slice)
    fmt.Println(slice) //[11 0 0 4]
}

func modifySlice(s []int) {
    s[0] = 11

    s = append(s, 2) // new element gets added to the slice inside function, but not visible to caller function.
    s[2] = 3         // changes after append will not be visible to caller

    fmt.Println(s) //[11 0 3 4 2]
}



산출[0 0 0]
[1 0 0]
[1 0 0 4]
[11 0 3 4 2]
[11 0 3 4]

운동장으로 이동Link .

위의 문제를 이해하기 위해 슬라이스의 내부 표현을 자세히 살펴보겠습니다.

내부적으로 슬라이스는 세 가지로 표현됩니다.
  • - 기본 배열에 대한 포인터
  • - 기본 배열의 길이
  • - 기본 어레이가 확장할 수 있는 최대 용량인 총 용량입니다.

  • type SliceHeader struct {
            Pointer uintptr
            Len  int
            Cap  int
    }
    

    슬라이스가 기본 배열에 대한 포인터를 포함하더라도 그 자체가 값이라는 것을 이해하는 것이 중요합니다. 따라서 슬라이스를 modifySlice 에 전달할 때 기본 배열, 길이 및 배열 용량에 대한 포인터를 보유하는 구조체 값을 전달합니다. 여기서 주목해야 할 점은 슬라이스가 구조체에 대한 포인터가 아니라는 것입니다.

    슬라이스 헤더가 값으로 전달되더라도 헤더에는 배열 요소에 대한 포인터가 포함되어 있으므로 s[0] = 11 함수에서 슬라이스의 0번째 인덱스modifySlice를 수정하면 기본 배열 요소가 변경되었습니다. 따라서 함수가 반환될 때 수정된 인덱스가 호출자 함수에 표시되었습니다.

    내부에 새로 추가된 요소modifySlice와 이후 변경 사항이 호출자 함수에 표시되지 않는 이유는 무엇입니까?
  • 슬라이스에서 추가 작업이 수행되고 용량을 사용할 수 없을 때마다 용량이 두 배인 새 어레이가 생성되고 기본 어레이에 대한 포인터를 덮어씁니다. 이제 원래 배열에 있는 요소가 새 배열로 복사되고 새 배열을 가리키는 새 슬라이스가 반환됩니다.
  • 슬라이스가 추가를 통해 커지면 Go 런타임이 새 메모리를 할당하고 이전 메모리에서 새 메모리로 기존 데이터를 복사하는 데 시간이 걸립니다. 이전 메모리도 가비지 수집해야 합니다. 이러한 이유로 Go 런타임은 일반적으로 용량이 부족할 때마다 슬라이스를 1개 이상 늘립니다. Go 1.14의 규칙은 용량이 1,024 미만일 때 슬라이스 크기를 두 배로 늘린 다음 이후에 최소 25% 증가하는 것입니다.
  • 따라서 슬라이스가 재할당되면 메모리의 새 위치가 사용됩니다. 슬라이스의 값이 같더라도 슬라이스는 새 메모리 위치를 가리키므로 슬라이스 재할당 후 변경 사항은 호출자 함수에 표시되지 않습니다.

  • 해결책:
    슬라이스에 추가한 후 값을 반환한 다음 원래 슬라이스에 할당합니다.

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        var slice = make([]int, 3, 4)
        fmt.Println(slice) //[0,0,0]
    
        slice[0] = 1
        fmt.Println(slice) //[1 0 0]
    
        slice = append(slice, 4)
        fmt.Println(slice) // [1 0 0 4]
    
        slice = modifySlice(slice)
        fmt.Println(slice) //[11 0 3 4 2]
    }
    
    func modifySlice(s []int) []int {
        s[0] = 11
    
        s = append(s, 2)
        s[2] = 3
    
        fmt.Println(s) //[11 0 3 4 2]
    
        return s
    }
    
    


    운동장으로 이동link .

    한 가지 고려해야 할 사항은 업데이트된 슬라이스를 반환하고 동일한 값에 할당하더라도 원래 길이와 용량이 변경되어 길이가 다른 새로운 기본 배열이 생성된다는 것입니다. 슬라이스 교체 전과 후의 길이와 용량을 확인하여 차이를 확인하세요.
    fmt.Println(len(slice), cap(slice))

    좋은 웹페이지 즐겨찾기