Doodly IT

[Go 묘공단 스터디] 슬라이스 본문

프로그래밍/Go

[Go 묘공단 스터디] 슬라이스

DoodlyKim 2023. 11. 29. 20:28

C++에 vector처럼 동적으로 배열을 구성하는 것이 Go에도 존재한다. 슬라이스라고 부른다. 

 

1) 슬라이스 선언

var array [] int

 

 

배열을 선언하는 것이랑 크게 다르지 않다.  var 키워드를 쓰고 변수명 그 후에 [] 다음에 슬라이스 타입을 적어주면 된다.

슬라이스를 초기화 하지않는다면 슬라이스는 길이가 0인 슬라이스가 된다.

 

2) 슬라이스 초기화

 

 1. {} 를 이용해서 초기화 하기

var slice1 = []int{1,2,3} // slice1[0]=1, slice1[1]=2, slice1[2]=3인 슬라이스 생성
var slice2 = []int{1 , 5:2, 10:3} // slice2[0]=1, slice2[5]=2, slice[10]=3인 슬라이스 생성

 

2. make를 이용해서 초기화 하기

 

  var slice = make([]int, 3)

 

make내장함수를 이용해서 슬라이스 초기화를 할 수 있다. 첫번째 인수는 만들고자하는 타입을 적고, 두번쨰 인수로 길이를 적어준다. 

 

3) 슬라이스 요소 추가하기 - append() 함수

 

슬라이스는 배열과 다르게 유동적으로 요소를 추가할 수 있다.

 

package main

import "fmt"

func main() {

	var slice = []int{1, 2, 3}

	slice2 := append(slice, 4)
	slice3 := append(slice, 4, 5, 6, 7, 8)

	fmt.Println(slice)
	fmt.Println(slice2) // 4만 붙이기
	fmt.Println(slice3) // 4,5,6,7,8 붙이기 (여러개붙이기)
}

 

 

4) 슬라이스의 내부 동작 원리

 

슬라이스와 배열은 다르게 동작을 한다. 

배열이 일차원 메모리 레이어라면, 슬라이스는 포인터에 가깝다.  

슬라이스의 내부 구조는 아래와 같이 이뤄져있다.

 

type SliceHeader struct{

Data uintptr
Len int
Cap int 

}

 

Slice는 배열의 포인터 주소와 배열의 크기, 그리고 배열의 Cap(확장이 가능한 길이)을  가진다.

배열이 길이만 가지는 메모리 였다면 슬라이스는 배열을 가리키는 포인터주소와 메모리의 길이, 메모리의 여유분을 제공하는 내장 타입이다.

Slice는 배열의 주소, len, cap으로 이루어진다.

이는 슬라이스와 배열이 다르게 동작하는 이유가 되기도 한다.

package main

import "fmt"

func ChangeArray(array2 [5]int) {
	array2[2] = 200
}

func ChangeSlice(array2 []int) {
	array2[2] = 200
}

func main() {

	arr := [5]int{1, 2, 3, 4, 5}
	sli := []int{1, 2, 3, 4, 5}

	ChangeArray(arr)
	ChangeSlice(sli)

	fmt.Println(arr)
	fmt.Println(sli)

}

 

위에는 배열과 슬라이스를 각각 매개변수로 받을 때, 값이 변하는 지 확인하는 코드이다.

실행시키면 아래와 같이 결과가 나온다.

 

배열은 변하지 않았는데 슬라이스는 값이 변해져 있다.

이는 매개변수를 전달받는 과정에서 기본적으로 전달하는 인자의 복사가 이뤄지는데, 이때 배열은 전체 40 바이트 짜리 배열이 모두 복사 되어 ChangeArray라는 함수를 진행하는 한편, 실제 main함수의 배열은 변하지않는다.

그러나 슬라이스는 매개변수로 받는 것이 포인터 주소이기 때문에  전체 24바이트짜리(int형 3개-> 포인타, len, cap)  데이터 타입을 전달한다. 이 때 해당 포인터 주소가 가리키는 배열의 값이 변하게 된다. 

즉 슬라이스를 매개변수로 하는 건 포인터 주소를 주고받는 형식이다.

 

5) 슬라이스를 사용할때 생길 수 있는 문제점

 

1.  append 문

슬라이스를 사용해서 append문을 쓸때 변하는 슬라이스는 무엇일까?

package main

import "fmt"

func main() {

	slice1 := make([]int, 3, 5) //len: 3, cap:5 인 슬라이스 생성

	slice2 := append(slice1, 4, 5)

	fmt.Println("slice1: ", slice1, len(slice1), cap(slice1))
	fmt.Println("slice2: ", slice2, len(slice2), cap(slice2))

	slice1[2] = 100

	fmt.Println("After Change index 2")
	fmt.Println("slice1: ", slice1, len(slice1), cap(slice1))
	fmt.Println("slice2: ", slice2, len(slice2), cap(slice2))

	slice2[1] = 200
	fmt.Println("After Change index 1")
	fmt.Println("slice1: ", slice1, len(slice1), cap(slice1))
	fmt.Println("slice2: ", slice2, len(slice2), cap(slice2))

}

 

실제 위 코드를 돌려보면 실행결과는 아래와 같다. 

 

코드상으로는 slice1과 slice2 각각의 값을 바꾼것 같지만 append 함수를 통해 slice2에도 slice1이 가리키는 배열의 포인터가 들어가기 때문에 결과적으로는 slice1과 slice2 모두의 값이 바뀌게 된다. 

 

2. 슬라이스의 cap이 모자른 상태에서 값을 더할때 

 

  append 함수를 사용할 때 만약 cap이상의 길이를 가지게 된다면 에러가 일어날꺼같지만 그렇지 않다.

package main

import "fmt"

func main() {

	slice1 := []int{1, 2, 3}

	slice2 := append(slice1, 4, 5)

	fmt.Println(slice1)
	fmt.Println(slice2)

	slice1[1] = 100
	fmt.Println(slice1)
	fmt.Println(slice2)

	slice1 = append(slice1, 500)

	fmt.Println(slice1)
	fmt.Println(slice2)
}

위 코드를 돌리면 slice1과 slice2가 다르게 동작한다는 것을 알 수 있다. 

이 이유는 slice1이 cap:3 len:3인 슬라이스이지만 4,5를 더하면서 확장되면서  이를 "복제"하기 때문이다.

복제가 되면서 slice1과 slice2는 별개의 것이 된다. 

 

 

 

 

그렇지만 아래와같이 의도적으로 slice1에 대해서만 Out of Index 에러를 주면,

런타임 에러가 일어난다. (이건 어떤 언어라도 당연히 일어난다.)

package main

import "fmt"

func main() {

	slice1 := make([]int, 3, 5) //len: 3, cap:5 인 슬라이스 생성

	for i := 0; i < 10; i++ {
		slice1[i] = i
	}

	fmt.Println(slice1)

}

(

 

 

6) 슬라이싱

슬라이싱은 배열의 일부를 집어내는 기능을 뜻한다. 배열을 집어내면서 요소를 추가하고 삭제하는 것이 가능하다.

package main

import "fmt"

func main() {

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

	fmt.Println(slice1[0:3]) // 인덱스 0으로 부터 3번째까지 ( 1,2,3)
	fmt.Println(slice1[:3])  //처음부터 시작하는 것은 0을 제외해도 된다.

	slice2 := slice1[2:3] //슬라이스를 또 슬라이싱 인덱스 2부터 3번째 까지

	fmt.Println(slice2)

	slice3 := slice1[2:len(slice1)] //인덱스 2부터 끝까지
	fmt.Println(slice3)

	slice4 := slice1[:] //전체 슬라이싱

	fmt.Println(slice4)

	slice5 := append([]int{}, slice1...) //slice1 복제 (1)

	fmt.Println(slice5)

	var slice7 = []int{} //slice1 복제(2)
	cnt := copy(slice7, slice1)
	fmt.Println(cnt, slice7)

	idx := 2 //index 2 요소 삭제
	var slice8 = []int{}
	slice8 = append(slice1[:idx], slice1[idx+1:]...)
	fmt.Println(slice8)

	



}

 

+ copy 함수는 일차원 배열에 대해서만 쓰도록 

 

 

7) 슬라이스 정렬

sort패키지에는 슬라이스를 정렬하는 Ints 함수가 있다. 

package main

import (
	"fmt"
	"sort"
)

func main() {

	s := []int{5, 2, 6, 3, 1, 4}
	sort.Ints(s)
	fmt.Println(s)

}