[Golang] 컬렉션 - 슬라이스

이정훈·2023년 5월 26일
0

Golang

목록 보기
22/24
post-thumbnail

슬라이스

  • Go언어에서 배열은 고정된 크기 안에 동일한 데이터를 연속으로 저장해 배열의 크기를 필요에 따라 동적으로 증가시키거나 부분 배열을 발췌하는 등의 기능을 가지고있지 않다.

  • 그런데 Slice는 배열과 다르게 고정된 크기를 미리 지정하지 않고 이후에 필요에 따라 크기를 동적으로 변경할 수 있고, 부분 발췌가 가능

  • 그리고 다차원 선언을 비롯한 배열의 모든 기능을 똑같이 구현할 수 있습니다.

  • 따라서 슬라이스는 배열의 여러 제약점들을 넘어 여러 값을 다룰 때 개발자에게 주로 쓰입니다.

  • 이러한 장점을 가진 (상대적으로)좋은 자료형인 슬라이스는 지금까지 배운 자료형과 내부적인 구조가 다르기때문에 선언 및 초기화를 할 때 주의!!

  • 지금까지 배운 자료형의 선언 및 초기화방법과 비교해서 설명하겠습니다.

  • 정수형(int, int32 ...등등)과 실수형(float32, float64...등등), 배열 등과 같은 자료형을 선언할때 "var 변수이름 자료형" 형식으로 선언

    • int형 변수 num을 선언한다면 var num int와 같은 형태로 입력
      이 선언의 뜻은 "한개의 int형의 변수가 들어갈 메모리를 만들었다."
    • 그런데 Go언어에서는 아무런 값을 초기화 하지 않고 선언만 해도 정수나 실수형은 0, 문자열형은 ""(빈칸)이 자동 할당된다
    • 따라서 정확히 말한다면 선언과 동시에 자동 초기화도 되는 것입니다
      (자동으로 0, ""이 할당되기때문에).
  • 다른 자료형과 마찬가지로, 배열도 크기를 지정하고 선언하기 때문에 명시한 개수만큼의 메모리를 만든다.

    • 예를들어 아래 그림처럼 var arr2 [3]int라고 입력하면 3개의 int타입의 변수가 들어갈 메모리를 만들고, 초기화하지 않았기 때문에 자동으로 0이 할당됩니다. 따라서 선언만 했을 뿐인데 len() 함수를 이용해 배열의 크기가 3이라는 것을 확인할 수 있다.

  • 하지만 슬라이스를 위에서 설명한 것과 같은 방법으로 var a []int와 같이 선언한다면 배열의 일부분을 가리키는 포인터를 만든다.

  • 선언만 하고 초기화를 하지 않아서 슬라이스의 정보만 있는 배열만 생성되고, 실질적으로 어떠한 변수가 들어갈 공간(메모리)은 생성되지 않는다.

  • 그렇다면 '다른 자료형은 메모리를 만들고 자동으로 0이나 ""을 할당하는데, 왜 슬라이스는 만들지 않을까?'와 같은 궁금증이 생길 수 있습니다.

  • 정말 간단합니다. why? 슬라이스는 크기를 미리 지정하지 않기 때문에 컴퓨터가 어디서부터 어디까지 0이나 ""으로 채워야하는지 알 수 없기 때문입니다.

  • 따라서 슬라이스의 초기 값을 지정하지 않고 선언만 한다면 'Nil silce'가 된다.

  • 이것은 크기도 용량도 없는 상태를 의미합니다. 당연히 메모리를 만들지 않아서 존재하지도 않기 때문에 a[0] = 1과 같이 값을 지정할 수 없다.

  • 기본적으로 슬라이스는 아무런 값도 초기화하지 않아도 배열의 위치를 가리키는 ptr과 배열의 길이인 len, 전체크기인 cap 메모리를 가지고 있습니다.

  • 그렇기 때문에 슬라이스를 var a []int와 같이 선언을 할 때는 주로 var a []int = []int{1, 2, 3, 4}같이 선언과 동시에 값을 초기화할 때만 사용합니다. 이는 슬라이스를 선언함과 동시에 1, 2, 3, 4를 위한 메모리를 만든다는 뜻입니다. 이때부터 a[1] =18과 같이 메모리에 저장돼있는 값을 바꿀 수 있고, 슬라이스의 길이와 용량을 확인하는 함수를 사용할 수 있습니다.

  • 이러한 내부 구조를 이해한다면 슬라이스 복사를 쉽게 이해할 수 있다.

  • 배열은 다른 배열의 값을 대입하면 값 자체가 대입됩니다.

  • 하지만 슬라이스는 참조 타입이기 때문에 슬라이스를 복사해온다는 것은 사실 같은 주소를 참조한다는 것과 같은 말입니다.

    • 예를 들어, 슬라이스는 다른 슬라이스를 부분 복사할 수 있는 기능이 있는데 슬라이스 a를 부분 복제하려고 하는 슬라이스 ll = a[2:5]를 입력함으로써 슬라이스 a의 인덱스2 요소부터 4요소까지 참조합니다. 그렇기 때문에 슬라이스는 데이터의 복사 없이 데이터를 사용 할 수 있다는 장점이 있습니다. 이는 아래 그림과 같이 묘사 할 수 있습니다.
  • 여기에 배열이 제공하지 않는 기능들을 사용하고 있으니 Go언어의 장점인 슬라이스를 잘 활용하는 것이 좋다.

  • 슬라이스의 길이와 용량을 지정하지 않고 슬라이스를 선언만 해서 Nil slice만들면 nil과 비교할 수 있고 true를 반환!

코드

package main

import "fmt"

func main() {
	var a []int        //슬라이스 변수 선언 아무것도 초기화 되지 않은 상태
	a = []int{1, 2, 3} //슬라이스에 리터럴값 지정

	a[1] = 10 //값이 할당되어 메모리가 생겼기 때문에 이렇게 접근 가능

	fmt.Println(a)

	var b []int //nil slice 선언

	if b == nil {
		fmt.Println("용량이", cap(b), "길이가", len(b), " Nil Slice입니다.")
	}
}

출력

[1 10 3]
용량이 0 길이가 0  Nil Slice입니다.

make() 함수를 이용한 슬라이스 선언

  • 슬라이스를 선언만 하면서 크기를 미리 지정할 수 있는 방법
    즉, 값으 저장할 수 있는 메모리를 선언만 함으로써 생성

  • 내장함수인 make()함수를 이용하여 선언!

  • 슬라이스를 생성함과 동시에 슬라이스의 길이(len), 슬라이스의 용량(cap)을 저장할 수 있다.

  • "make(슬라이스 타입, 슬라이스 길이, 슬라이스의 용량)" 슬라이스 용량(Capacity)는 생략해서 선언 가능

  • make()함수를 이용해 선언한다면 비로소 모든 요소가 0인 슬라이스를 만들게 된다!

슬라이스의 길이와 용량의 개념

길이

  • 초기화된 슬라이스의 요소 개수
  • 즉, 슬라이스에 5개의 값이 초기화된다면 길이는 5!
  • 그 후에 값을 추가하거나 삭제한다면 그만큼 길이가 바뀌게 된다.
  • len(컬렉션이름)으로 길이를 알 수 있다.

용량

  • 슬라이스는 배열의 길이가 동적으로 늘어날 수 있다. 그렇기 떄문에 길이와 용량으로 구분한다.
  • 예를 들어 동호회에서 야유회를 가기위해 버스를 태절한다고 생각해보자, 야유회를 가기 위해 모인 인원은 125명이고 버슨 25인승, 125명은 배정이 완료되어서 버스 5대를 대절했다. 그런데 11명이 추가로 가고 싶다한다. 그래서 추가로 25인승짜리 버스 한 대를 대절 -> 여기서 총 승객 136명은 길이 그리고 버스가 한번에 태울 수 있는 승객은 용량
  • make() 함수를 이용해 슬라이스를 선언한다면, 선언한 슬라이스의 용량이 25인데 101개의 값을 초기화하기 위해서는 125용량이 필요!!
  • 이러한 방식으로 메모리를 괸리하는 것!, 용량은cpa(컬렉션이름)으로 용량을 알 수 있다.
  • 주의!! make()함수를 이용해 슬라이스의 메모리를 할당하고 난 후에 []int{1,2,3,4}와 같은 식으로 입력하여 값을 초기화하면 새로운 메모리를 할당하면서 그 전의 값은 없어집니다.

  • 이건 어느 부분에서든 동일하게 적용되는 당연한 것!!

  • 기존의 메모리를 사용하고 값을 추가하기 위해서는 아래에서 배우는 append()함수를 사용!!

코드

package main

import "fmt"

func main() {
	s := make([]int, 0, 3) // len=0, cap=3 인 슬라이스 선언

	for i := 1; i <= 10; i++ { // 1부터 차례대로 한 요소씩 추가
		s = append(s, i)

		fmt.Println(len(s), cap(s)) // 슬라이스 길이와 용량 확인
	}

	fmt.Println(s) // 최종 슬라이스 출력
}

출력

1 3
2 3
3 3
4 6
5 6
6 6
7 12
8 12
9 12
10 12
[1 2 3 4 5 6 7 8 9 10]

슬라이스 추가, 병합, 복사

  • append()함수를 이용해서 슬라이스에 데이터를 추가할 수 있다.

  • 슬라이스 용량이 남아있는 경우에는 그 용량 내에서 슬라이스의 길이를 변경하여 데이터를 추가하고, 용량이 초과하는 경우에는 설정한 욜얄ㅇ만큼 새로운 배열을 생성하고 기존 배열 값들을 모두 새 배열에 복제한 후 다시 슬라이스를 할당하는 방식!

  • 그리고 데이터를 추가할 수 있을 뿐 아니라, 슬라이스에 슬라이스를 추가해서 붙일 수 있다!

  • 주의점! 추가하는 슬라이스 뒤에 "..."을 입력! ...은 슬라이스의 모든 요소들의 집합을 표현하는 것으로, 사실상 슬라이스에 슬라이스를 추가하는 것이 아니라, silceA에 {요소값}들이 차가되는 것!

코드

package main

import "fmt"

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

	sliceA = append(sliceA, sliceB...)
	//sliceA = append(sliceA, 4, 5, 6)

	fmt.Println(sliceA) // [1 2 3 4 5 6] 출력
}

출력

[1 2 3 4 5 6]
  • copy() 함술흘 이용해 한 슬라이스를 다른 슬라이스로 복사할 수 있다.

  • copy(붙여넣을 슬라이스, 복사할 슬라이스) 형식으로 사용!

  • 복사할 슬라이스와 붙여넣을 슬라이스 모두 선언이 선행되어 있어야 한다!

코드

package main

import "fmt"

func main() {
	sliceA := []int{0, 1, 2}
	sliceB := make([]int, len(sliceA), cap(sliceA)*2) //sliceA에 2배 용량인 슬라이스 선언

	copy(sliceB, sliceA)                              //A를 B에 붙여넣는다

	fmt.Println(sliceB)                               // [0 1 2 ] 출력
	println(len(sliceB), cap(sliceB))                 // 3, 6 출력
}

출력

[0 1 2]
3 6
  • 슬라이스는 원래 자르기라는 뜻!

  • 슬라이스는 슬라이스의 부분만 잘라서 복사할 수 있다.

  • 이때 붙여넣을 슬라이스 := 복사할 슬라이스[복사할 첫 인덱스:복사할 마지막 인덱스+1]

    • 예를 들어, l := sliceA([2:5]라고 한다면 슬라이스 l에 sliceA의 인덱스 2요소부터 4요소까지 잘라서 복사! 마지막 요소는 복사하지 않는다!!
    • 처음과 마지막 인덱스를 생략하면 첫 요소와 맨 마지막 요소를 의미, 예를 들어 l := sliceA[:5]라면 sliceA의 처음부터 인데스 4의 요소까지 복사한다는 것!
    • 반대의 경우도 마찬가지로 사용할 수 있다.

코드

package main

import "fmt"

func main() {
	c := make([]int, 0, 3) //용량이 3이고 길이가0인 정수형 슬라이스 선언
	c = append(c, 1, 2, 3, 4, 5, 6, 7)
	fmt.Println(len(c), cap(c))

	l := c[1:3] //인덱스 1요소부터 2요소까지 복사
	fmt.Println(l)

	l = c[2:] //인덱스 2요소부터 끝까지 복사
	fmt.Println(l)

	l[0] = 6

	fmt.Println(c) //슬라이스 l의 값을 바꿨는데 c의 값도 바뀜
	//값을 복사해온 것이 아니라 기존 슬라이스 주솟값을 참조
}

출력

7 8
[2 3]
[3 4 5 6 7]  // 여기서 첫번째 인덱스[0]을 6으로!
[1 2 6 4 5 6 7]
  • 특이한 점이 보인다.

  • 복사해온 슬라이스의 값을 바꿨는데 기존 복사한 슬라이스의 값도 바뀐 것을 확인할 수 있습니다.

  • why? 슬라이스는 배열과 다르게 값을 복사해오는 것이 아니라 슬라이스 자체가 참조하고있는 주소값을 같이 참조하는 것을 의미

  • 하지만 같은 상황이라면 배열은 단순히 값을 복사해서 초기화합니다

어렵다!

한 눈에 끝내는 고랭 기초 !!

profile
싱숭생숭늉

0개의 댓글