Connecting

Go 언어 배열 / 슬라이스 / 맵 본문

Go 언어

Go 언어 배열 / 슬라이스 / 맵

팬도라 2020. 7. 12. 13:40
반응형

배열 사용하기

Go 언어에도 다른 프로그래밍 언어와 동일하게 배열이 존재합니다. 배열은 숫자, 문자, 문자열 등을 나열해 놓은 상태로서 다양한 값이 들어갈 수 있습니다. 또한, 배열은 비슷한 데이터들을 한번에 가지고 있을 때 사용되거나, 반복적으로 사용되어야 하는 값들의 집합을 만들 때 사용합니다.

Go 언어에서 배열은 길이가 고정되어 있고, 다른 언어와 마찬가지로 배열의 인덱스는 0부터 시작합니다. 다음 예제를 통해 배열을 선언하도록 하겠습니다.

    var empty [5]int // 크기가 5인 배열을 생성
    empty[4] = 5 // empty 배열 4번째 부분에 값을 5로 초기화 한다. 
    f.Println(empty)

    var a [3]int = [3]int{1, 2, 3} // 정수형의 크기가 3인 배열을 선언하고 값을 초기화 한다. 
    var b = [3]string{"name", "age", "weight"} // 문자열의 크기가 3인 배열을 선언하고 값을 초기화 한다. 
    var c = [5]float32{ // 실수형의 크기가 5인 배열을 선언하고 값을 초기화 한다. 
        1.14,
        2.14,
        3.14,
        4.14,
        5.14,
    }

배열의 크기를 미리 선언하지 않더라도 초기화 할 요소의 개수 만큼 배열의 크기가 정해집니다.

    d := [...]string{"apple", "lg", "samsung", "kt"}

만약 다차원의 배열을 선언하고 싶다면 다음 예제와 같이 작성합니다. 현재는 2차원 배열이지만 3차원 4차원 배열 등도 동일하게 선언할 수 있습니다.

    var multiArray = [3][3]int{
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9},
    }

len 키워드를 사용하면 배열의 크기를 쉽게 산출할 수 있습니다. 다음 예제 코드를 통해 배열을 연습해 보세요.

package main

import f "fmt"

func main() {

    var empty [5]int
    empty[4] = 5
    f.Println(empty)

    var a [3]int = [3]int{1, 2, 3}
    var b = [3]string{"name", "age", "weight"}
    var c = [5]float32{
        1.14,
        2.14,
        3.14,
        4.14,
        5.14,
    }

    d := [...]string{"apple", "lg", "samsung", "kt"} // 배열을 선언과 동시에 초기화 하는 경우 []의 배열 크기를 생략할 수 있습니다. 

    f.Println("array size", len(a))

    for i := 0; i < len(b); i++ {
        f.Println("b 내용 : ", b[i])
    }

    f.Println(c, d)

    var multiArray = [3][3]int{
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9},
    }

  for _, array := range multiArray {
   for _, value := range array {
      f.Println(value)
   }
 }

    f.Println("----------------------")
    f.Println(multiArray)
    f.Println("array size", len(multiArray))

}

배열의 값을 for문으로 쉽게 순회하고 싶다면 아래와 같이 간단하게 정의할 수도 있습니다.

    for  _, value := range a { 
        f.Println("b 내용 : ", value)
    }

배열을 복사하고 싶다면 다음과 같이 실행합니다.

package main

import f "fmt"

func main() {

    original := [...]string{"ios", "linux", "unix", "android", "windows"}
    clone := original // 배열 전체 복사

    original[3] = "ubuntu"

    f.Println(original)
    f.Println(clone)
}

위의 코드에서 확인할 수 있듯이 배열을 복사하면서로 다른 메모리 영역에 복사하기 때문에 원본에 수정이 일어나도 복사한 배열에는 값이 변하지 않습니다. 이를 수정하고 싶다면 포인터를 통해서 레퍼런스 타입으로 변경하면 됩니다.

슬라이스 사용하기

Go 언어의 슬라이스는 기본적으로 배열과 동일하지만 크기가 고정되어 있지 않고 동적으로 길이가 늘어난다는 특징을 가지고 있습니다. 만약 길이가 0인 빈 배열을 만들기 위해선 내장 함수 make를 사용하면 됩니다. 이때 생성되는 슬라이스를 nil 슬라이스라고 하며 null과 동일합니다.

기본적으로 Go 언어에서의 슬라이스는 레퍼런스 타입입니다. 다음 예제는 Go 언어에서 슬라이스를 선언하는 방법에 대해서 알아보도록 하겠습니다.

    var a []int // 슬라이스는 배열과 달리 [] 안에 길이를 지정하지 않습니다. 이렇게 생성된 슬라이스의 길이는 0입니다. 
    var a []int = make([]int, 5) // make 함수로 int형에 길이가 5인 슬라이스에 공간 할당
    var b = make([]string, 5) // 슬라이스를 선언할 때 자료형과 [] 생략
    c := make([]float64, 5, 10) // 슬라이스를 선언할 때 var 키워드, 자료형과 [] 생략

슬라이스의 값을 초기화 하기 위해서는 {}를 반드시 사용해야 합니다. 한줄로 작성할 수도 있지만 여러줄로 초기화 할 수도 있습니다. 여기서 반드시 기억해야 할 점은 여러줄로 선언시 마지막 원소에서 콤마를 붙어야 한다는 점입니다.

    a := []int{70, 80, 90, 100, 110}

    b := []int{
        10,
        20,
        30,
        40,
        50,  // 여러 줄로 나열할 때는 마지막 요소에 콤마를 붙임
}

슬라이스에서 길이는 익숙하지만 용량은 상대적으로 생소할 수 밖에 없습니다. 배열은 길이의 변경이 필요할 때마다 새로운 길이를 가진 배열을 다시 할당하는 비효율적인 작업을 진행해야 하지만 slice는 길이의 변경에 대비하여 미리 특정 용량을 가진 배열을 할당해두고, 정해진 길이 만큼만 사용할 수 있도록 하여, 길이의 수정 만으로 배열을 재할당할 필요 없이 유동적으로 사용할 수 있습니다.

위의 그림을 이해하기 위해서 다음 코드를 확인하시길 바랍니다. 참고로 미리 슬라이스의 용량을 크게 할당하면 요소가 추가될 때마다 메모리를 새로 할당하지 않아도 되므로 성능상 이점이 있으나 메모리 공간을 많이 차지하게 되고. 반대로 슬라이스 용량을 적게 할당하면 처음부터 메모리 공간은 적게 차지하지만, 요소가 추가될 때마다 메모리를 새로 할당하게 되므로 성능이 떨어질 수 있습니다.

슬라이스의 길이는 len 함수로 구할 수 있으며, 용량은 cap 함수로 구할 수 있습니다

    c := make([]float64, 5, 10)
    os := []string{"ios", "linux", "unix", "android", "windows"}

    f.Println(len(os), cap(os))
    f.Println(len(c), cap(c))

슬라이스에 값을 추가로 지정하고 싶다면 append 함수를 사용합니다.

    var a []int = make([]int, 5)

    for i := 0; i < len(a); i++ {
        a[i] = i
    }    

    add := append(a, 6, 7, 8)
    f.Println("size :", len(add), ", components :", add)

만약에 슬라이스에 다른 슬라이스를 추가해야 한다면 append 함수에 ... 을 추가로 사용합니다. 다음을 통해 예시를 확인할 수 있습니다.

    a := []int{1, 2, 3}
    b := []int{4, 5, 6}

    a = append(a, b...) 

    f.Println(a)

만약 슬라이스에 할당된 용량이 길이보다 크더라도 길이를 벗어난 인덱스에는 접근할 경우 Panic 에러를 발생시킵니다. 슬라이스는 레퍼런스타입이라고 위에서 설명했습니다. 따라서 슬라이스를 복사하도라도 레퍼런스로 참조하기 때문에 복사본에 수정된 내용은 원본 내용에도 반영됩니다. 다음 코드를 통해 확인하시길 바랍니다.

    originalArray := [...]int{1, 2, 3}
    var cloneArray [3]int

    cloneArray = originalArray
    cloneArray[0] = 4
    f.Println("배열 복사 : ", "original : ", originalArray, "clone : ", cloneArray)

    originalSlice := []int{1, 2, 3}
    var cloneSlice []int

    cloneSlice = originalSlice
    cloneSlice[0] = 4
    f.Println("슬라이스 복사 : ", "original : ", originalSlice, "clone : ", cloneSlice)

슬라이스를 사용한다면 슬라이싱을 뺄 수 없습니다. Go에서는 배열과 slice의 특정 영역을 slice 형태로 추출할 수 있도록 slicing이라는 기능을 제공합니다. 변수명[시작인덱스(포함):끝인덱스(불포함)] 을 반드시 기억합니다. 다음 예제를 통해 확인해보겠습니다.

    f.Println(os[0:2])
    f.Println(os[1:3])
    f.Println(os[:3])
    f.Println(os[3:])

이제까지 Go 언어에서 슬라이스 기능에 대해 살펴보았습니다. 얼핏 많은 내용이 나왔지만 다음 코드를 통해 지금까지 학습한 내용을 복습하고 값이 어떻게 표현되는지 확인하시길 바랍니다.

package main

import f "fmt"

func main() {

    var a []int = make([]int, 5)
    var b = make([]string, 5)
    c := make([]float64, 5, 10)

    f.Println(a, b, c)

    for i := 0; i < len(a); i++ {
        a[i] = i
    }
    f.Println(a)

    add := append(a, 6, 7, 8)
    f.Println("size :", len(add), ", components :", add)

    os := []string{"ios", "linux", "unix", "android", "windows"}
    f.Println(len(os), cap(os))
    f.Println(len(c), cap(c))

    f.Println(os[0:2])
    f.Println(os[1:3])
    f.Println(os[:3])
    f.Println(os[3:])

}
package main

import f "fmt"

func main() {

    originalArray := [...]int{1, 2, 3}
    var cloneArray [3]int

    cloneArray = originalArray
    cloneArray[0] = 4
    f.Println("배열 복사 : ", "original : ", originalArray, "clone : ", cloneArray)

    originalSlice := []int{1, 2, 3}
    var cloneSlice []int

    cloneSlice = originalSlice
    cloneSlice[0] = 4
    f.Println("슬라이스 복사 : ", "original : ", originalSlice, "clone : ", cloneSlice)

    f.Println("-------------------------")

    a := []string{"windows", "linux", "android", "ios"}
    b := make([]string, 3)

    copy(b, a)
    f.Println(a)
    f.Println(b)

    b[0] = "ubuntu"
    f.Println(a)
    f.Println(b)

}

맵 사용하기

맵(Map)은 키(Key)에 대응하는 값(Value)을 신속히 찾는 해시테이블(Hash table)을 구현한 자료구조입니다. 맵에 정의된 키는 중복되지 않아야 하며, Python언어의 딕셔너리와 매우 유사하지만, Nil map이 있다는 차이점을 가지고 있습니다. Nil map에는 어떤 데이터를 쓸 수 없는데, map을 초기화하기 위해 make()함수를 사용합니다.

    var test map[int]string // map 선언, Nil map 생성
    test = make(map[int]string)

맵을 선언하고 값을 저장하고 값을 출력하기 위해서 다음과 같이 사용할 수 있습니다.

    a := make(map[string]int)

    a["age"] = 27
    a["height"] = 175
    f.Println(a)

    b := map[string]float64{
        "pi":    3.141592,
        "sqrt2": 1.41421356,
    }

    f.Println(b["pi"], b["sqrt2"])

Go 언어에서 맵을 사용하는 방법은 다른 프로그래밍 언어와 크게 다르지 않습니다. 아래 코드는 map에 키와 값을 선언하고, 출력하며, 맵에 있는 값의 여부를 확인하거나, 순회하여 출력, 값을 삭제하는 예제 입니다.

package main

import f "fmt"

func main() {

    a := make(map[string]int)

    a["age"] = 27
    a["height"] = 175
    f.Println(a)

    b := map[string]float64{
        "pi":    3.141592,
        "sqrt2": 1.41421356,
    }

    f.Println(b["pi"], b["sqrt2"])

    f.Println("-------------------")

    capacityUnit := make(map[string]string)

    capacityUnit["1byte"] = "8 bit"
    capacityUnit["1MB"] = "1024 Byte"
    capacityUnit["1GB"] = "1024 MB"
    capacityUnit["1TB"] = "1024 GB"
    capacityUnit["1PB"] = "1024 TB"

    value, ok := capacityUnit["1YB"]
    f.Println(value, ok)

    if result, ok := capacityUnit["1GB"]; ok {
        f.Println(result, ok)
    }

    f.Println("-------------------")

    for key, result := range capacityUnit {
        f.Println(key, " : ", result)
    }

    f.Println("-------------------")

    for _, result := range capacityUnit {
        f.Println(result)
    }

    delete(capacityUnit, "1PB")
    f.Println(capacityUnit)
}

추가적으로 map안에 map을 여러번 선언하여 사용할 수 있습니다. 다음 방법을 사용해 보세요.

package main

import f "fmt"

func main() {

    terrestrialPlanet := map[string]map[string]float32{
        "Mercury": map[string]float32{
            "meanRadius":    2439.7,
            "mass":          3.3022e+23,
            "orbitalPeriod": 87.969,
        },
        "Venus": map[string]float32{
            "meanRadius":    6051.8,
            "mass":          4.8676e+24,
            "orbitalPeriod": 224.70069,
        },
        "Earth": map[string]float32{
            "meanRadius":    6371.0,
            "mass":          5.97219e+24,
            "orbitalPeriod": 365.25641,
        },
        "Mars": map[string]float32{
            "meanRadius":    3389.5,
            "mass":          6.4185e+23,
            "orbitalPeriod": 686.9600,
        },
    }

    f.Println(terrestrialPlanet["Mars"]["mass"])

}
Comments