Go 언어 클로저에 대한 콩 지식

8334 단어 5Closure

배경



for분에 함수를 쓸 때, 특히 for분의 범위와 함수의 조합에 의해, 클로저가 생성되어 함수의 실행 타이밍이 for분과 일치하지 않는 경우는, 버그가 발생하기 쉽습니다.
예를 들어, 자바스크립트에는 다음과 같은 클로저 전형적인 예가 있습니다.
자바 스크립트 콩 지식 (let, var, 클로저에 대한 인터뷰 문제)

Javascript에서는 let이라는 변수 선언 방법을 활용하여 잘 for분 함께 있는 클로저 문제점을 해소할 수 있습니다만, Go 언어라면, 어떻게 해소할 수 있습니까?

문제



코드
func main()  {
    n := 5

    funcs := []func(){}

    for i:=0;i<n;i++ {
        // fmt.Println(&i)  // コメントアウトを外してみたら、iのメモリアドレスは同じものです。
        funcs = append(funcs, func() {
            fmt.Print(i)
        })
    }
    for i:=0;i<n;i++ {
        funcs[i]()
    }
}

출력
5
5
5
5
5

원인

두 가지 원인이 있습니다.
  • 이것은 for분에서 루프하는 타이밍(funcs 슬라이스에 함수를 만들어 넣는 타이밍)과 funcs 슬라이스에 있는 함수가 실행되는 타이밍은 다릅니다.
  • func 슬라이스의 모든 함수에 있는 변수 i는 같은 곳에서 참조됩니다. (for 분은 메모리 주소가 같은 i를 사용하고 있기 때문입니다)
  • 그 때문에, 함수가 실행하는 타이밍에서는, 모든 함수로부터 참조하고 있는 i는 벌써 5가 되었으므로, 모든 함수는 같은 i(10)를 출력했습니다.


  • 자바 스크립트라면 for 분에 i를 let로 정의하여 문제를 해결할 수 있지만 Go 언어는 어떻게됩니까?
    여러가지 조사했습니다.

    해결 방법 1



    새로운 변수 i를 만들어, 새로운 i를 메모리장에서 개척해, funcs 슬라이스에 넣는 함수는 이쪽의 i를 기억합니다.


    코드
    func main()  {
        n := 5
    
        funcs := []func(){}
    
        for i:=0;i<n;i++ {
            i := i
            // fmt.Println(&i) //コメントアウトを外して見たら、iのメモリアドレスはそれぞれ異なります。
            funcs = append(funcs, func() {
                fmt.Println(i)
            })
        }
    
        for i:=0;i<n;i++ {
            funcs[i]()
        }
    }
    

    출력
    0
    1
    2
    3
    4
    

    해결 방법 2



    실행된 함수 A가 함수 B를 반환하고, 함수 B를 funcs 슬라이스에 넣습니다. 함수 A에 전달한 인수는 값 전달이므로 함수 A가 실행될 때 i가 새로 만들어집니다.
    그러면 함수 B는 함수 A와 클로저를 생성하고, 함수 A 안에 새로운 i가 메모리 필드에서 개척되고, 함수 B가 그 i를 참조합니다. 각 함수 B는 각각의 i를 참조합니다.


    코드
    func main()  {
        n := 5
        funcs := []func(){}
        for i:=0;i<n;i++ {
            funcs = append(funcs, func(i int) func() {
                // fmt.Println(&i) // コメントアウトを外してみたら、iのメモリアドレスはそれぞれ異なります。
                return func() {
                    fmt.Println(i)
                }
            }(i))
        } // ここ → 関数Aにforループのiを渡し、関数Bを返されます。
    
        for i:=0;i<n;i++ {
            funcs[i]()
        }
    }
    

    출력
    0
    1
    2
    3
    4
    

    덧붙여서, 함수 A에 인수를 포인터 전달로 실행하면 어떻게됩니까?
        for i:=0;i<n;i++ {
            funcs = append(funcs, func(i *int) func() {
                fmt.Println(i)
                return func() {
                    fmt.Println(*i)
                }
            }(&i))
        }
    

    당연히 모든 i는 동일한 메모리 주소를 참조하고 모든 func의 출력은 동일한 5입니다.
    0xc00001c080
    0xc00001c080
    0xc00001c080
    0xc00001c080
    0xc00001c080
    5
    5
    5
    5
    5
    

    참고 자료








    좋은 웹페이지 즐겨찾기