sonumb

Go - 다차원 배열을 파라미터로 받을 때 본문

개발자 이야기/Go

Go - 다차원 배열을 파라미터로 받을 때

sonumb 2021. 12. 10. 16:45

개요

결론을 말하자면, '다차원 배열'을 '참조에의한호출'로 다른 함수에서 참조할 수 있다.

그러나, 배열 대신 슬라이스를 이용하는 것을 가장 추천한다. (코드가 간단해지므로)


 

다차원 배열을 선언할 때, var arr [5][2]int처럼 배열 크기를 명시하거나,
var arr [...][2]int와 같이 ...을 기입해주어 해야 배열로 선언되고,

var sliece [][2]int와 같이 크기가 없거나,
slice := make([][2]int, 0)와 같이 make()를 호출해야 슬라이스로 선언된다.

그렇다면, 다차원 배열을 함수에 인자값으로 넘길 때, call-by-reference를 하고 싶다면?

참고로, C 언어 같은 경우

void foo( int arr[][2] );
// 혹은
void bar( int (*arr)[2] );

처럼 선언해야, 콜바이 레퍼런스가 된다.

결론적으로, Go에서는 C언어의 첫번째 방법과 동일하게 파라미터를 정의하면 된다.

func foo( arr [][2]int ) {
  // blah
}

아래와 같이 '...' 키워드(?)를 이용해도 된다.

func bar( arr ...[2]int ) {
  // blah
}

다만, bar() 함수 호출할 때 유의점이 있다.

일단, 아래와 같이 두 가지 방법이 존재한다.

  1. ...을 이용한 호출
  2. 개별적으로 값을 넘긴 호출

결론만 말하자면, 둘의 차이는 call-by-ref, call-by-value 와 같다.

func main() {
  a := [5][2]int {
    {1, 2},
    {3, 4},
   }

   foo(a[:])
   bar(a[:]...)
   bar(a[0], a[1])
}

아래 소스코드와 이의 결과를 확인해 볼 것.

그리고 위 함수를 호출할때, 슬라이스와 배열 모두 처리가능 하다. (어떤 원리인지 궁금하다. 아시는 분?)

소스코드

package main

import (
    "fmt"
)


func call_by_pointer(elem *[5][2]int) {
    for i:=0; i< len(elem) ;i++ {
        elem[i][0] += 10
    }
}

func call_by_slice(elem [][2]int) {
    for i:=0; i< len(elem) ;i++ {
        elem[i][0] += 10
    }
}

func call_by_slice2(elem [][]int) {
    for i:=0; i< len(elem) ;i++ {
        elem[i][0] += 10
    }
}

func call_by_dots(elem ...[2]int) {
    for i:=0; i< len(elem) ;i++ {
        elem[i][0] += 10
    }
}

func call_by_dots2(elem ...[]int) {
    for i:=0; i< len(elem) ;i++ {
        elem[i][0] += 10
    }
}

func main() {
    fmt.Println("-------- array ----------------------------------------------------")
    a1 := [...][2]int{ // [5][2]int
        {11, 1},
        {12, 1},
        {13, 1},
        {14, 1},
        {15, 1},
    }

    fmt.Printf("%-*s:%+v\n", 30, "original array 1", a1)

    // foo(a ... T)의 a에 foo(a1[0], a[1]) 으로 넘겨주면, 포인터가 아니라 [2]int{1,2}와 같은 값이 넘어간다.
    call_by_dots(a1[0], a1[2], a1[3], a1[4])                           // call by value 다.  reference 가 아님!
    fmt.Printf("%-*s:%+v\n", 30, "call_by_dots(a1[0], a1[1], ..)", a1) // 따라서, 값 증가하지 않음,

    // foo(a ... T)의 a에 foo(val...) 으로 값을 넘겨주면, 포인터로 동작한다.
    // 즉, call by reference 임
    call_by_dots(a1[:]...)
    fmt.Printf("%-*s:%+v\n", 30, "call_by_dots(a1[:]...)", a1) // 따라서 값 증가.

    // 따라서, 아래처럼 복합적으로 호출하는 것은 불가.
    // call_by_dots(a1[0], a1[2], a1[3:]...) // compile error

    // pointer 로 호출가능 하지만, 같은 크기 배열의 포인터만 인자값으로 허용
    call_by_pointer(&a1)
    fmt.Printf("%-*s:%+v\n", 30, "call_by_dots(&a1)", a1)

    call_by_slice(a1[:])
    fmt.Printf("%-*s:%+v\n", 30, "call_by_slice(a1[:])", a1)
    // call_by_slice(a1[0], a1[2], a1[3], a1[4]) // compile error
    // call_by_slice2(a2[:]) // compile error

    a2 := [3][2]int{
        {11, 2},
        {12, 2},
        {13, 2},
    }
    fmt.Println("")
    fmt.Printf("%-*s:%+v\n", 30, "original array 2", a2)

    call_by_dots(a2[:]...)
    fmt.Printf("%-*s:%+v\n", 30, "call_by_dots(a2[:]...)", a2)

    call_by_slice(a2[:])
    fmt.Printf("%-*s:%+v\n", 30, "call_by_slice(a2[:])", a2)

    // 포인터가 가르키는 배열의 크기가 다르므로 컴파일 에러
    // call_by_pointer(&a2)
    //fmt.Println(a2)

    fmt.Println("")
    fmt.Println("-------- slice ----------------------------------------------------")

    s1 := make([][2]int, 5)
    for i := 0; i < 5; i++ {
        s1[i] = [2]int{11 + i, 3}
    }
    //s1 := [][2]int { // make([][2]int, 5)
    //    {11,3},
    //    {12,3},
    //    {13,3},
    //    {14,3},
    //    {15,3},
    //}

    fmt.Printf("%-*s:%+v\n", 30, "original slice 1", s1)

    call_by_dots(s1[0], s1[1], s1[2], s1[3], s1[4])
    fmt.Printf("%-*s:%+v\n", 30, "call_by_dots(s1[0], s1[2],..)", s1)

    call_by_dots(s1[:]...)
    fmt.Printf("%-*s:%+v\n", 30, "call_by_dots(s1[:]...)", s1)

    // call_by_pointer(&s1)  // compile error
    // fmt.Println(a1)
    s2 := [][2]int{
        {1, 4},
        {2, 4},
        {3, 4},
    }
    fmt.Println("")
    fmt.Printf("%-*s:%+v\n", 30, "original slice 2", s2)

    call_by_dots(s2[:]...)
    fmt.Printf("%-*s:%+v\n", 30, "call_by_dots(s2[:]...)", s2)

    s3 := [][]int{
        {11,2,3},
        {12,2,3},
    }
    fmt.Println("")
    fmt.Printf("%-*s:%+v\n", 30, "original slice 3", s3)

    call_by_slice2( s3 )
    fmt.Printf("%-*s:%+v\n", 30, "call_by_slice2(s3)", s3)

    call_by_dots2( s3... )
    fmt.Printf("%-*s:%+v\n", 30, "call_by_dots2(s3...)", s3)



    call_by_dots2( s3[0], s3[1] )  // '값에의한호출'인데, []int 슬라이스이므로 참조'값'으로 호출된다.
                                   // 결과적으로 '참조에의한호출'이 되는 셈.
    fmt.Printf("%-*s:%+v\n", 30, "call_by_dots2(s3[0], s3[1])", s3)
    return
}

실행 결과

$ go run call_func_with_arr.go
-------- array ----------------------------------------------------
original array 1              :[[11 1] [12 1] [13 1] [14 1] [15 1]]
call_by_dots(a1[0], a1[1], ..):[[11 1] [12 1] [13 1] [14 1] [15 1]]
call_by_dots(a1[:]...)        :[[21 1] [22 1] [23 1] [24 1] [25 1]]
call_by_dots(&a1)             :[[31 1] [32 1] [33 1] [34 1] [35 1]]
call_by_slice(a1[:])          :[[41 1] [42 1] [43 1] [44 1] [45 1]]

original array 2              :[[11 2] [12 2] [13 2]]
call_by_dots(a2[:]...)        :[[21 2] [22 2] [23 2]]
call_by_slice(a2[:])          :[[31 2] [32 2] [33 2]]

-------- slice ----------------------------------------------------
original slice 1              :[[11 3] [12 3] [13 3] [14 3] [15 3]]
call_by_dots(s1[0], s1[2],..) :[[11 3] [12 3] [13 3] [14 3] [15 3]]
call_by_dots(s1[:]...)        :[[21 3] [22 3] [23 3] [24 3] [25 3]]

original slice 2              :[[1 4] [2 4] [3 4]]
call_by_dots(s2[:]...)        :[[11 4] [12 4] [13 4]]

original slice 3              :[[11 2 3] [12 2 3]]
call_by_slice2(s3)            :[[21 2 3] [22 2 3]]
call_by_dots2(s3...)          :[[31 2 3] [32 2 3]]
call_by_dots2(s3[0], s3[1])   :[[41 2 3] [42 2 3]]

$
반응형