[Tucker의 Go 언어 프로그래밍] 18장 슬라이스

Coen·2023년 11월 1일
1

tucker-go

목록 보기
13/18
post-thumbnail
post-custom-banner

이 글은 골든래빗 《Tucker의 Go 언어 프로그래밍》의 18장 써머리입니다.

18장 슬라이스

18.1 슬라이스

18.1.1 슬라이스 선언

  • 일반적인 배열은 처음 배열을 만들 때 정한 길이에서 더 이상 늘어나지 않는 문제가 있다.
  • 슬라이스를 사용하면 배열과 다르게 []안에 배열의 갯수를 적지 않고 선언한다.
var slice []int // 길이가 0인 슬라이스 생성

슬라이스를 초기화 하는 방법

  • {}를 사용해 초기화
var slice1 = []int{1, 2, 3} //[1 2 3]
var slice1 = []int{1, 5:2, 10:3} //[1 0 0 0 2 0 0 0 0 3]
  • make()를 사용한 초기화
var slice = make([]int, 3) //길이가 3인 int 슬라이스를 갖는다. [0 0 0]

18.1.2 슬라이스 요소 접근

  • 배열과 같음

18.1.3 슬라이스 순회

  • 배열과 같음

18.1.4 슬라이스 요소 추가 - append() (가장 많이 사용하는듯?)

  • 배열은 길이를 늘릴 수 없지만 슬라이스는 요소를 추가해 길이를 늘릴 수 있다.
func Test_append(t *testing.T) {
	arr := []int{1, 2, 3, 4, 5}
	arr2 := append(arr, 6)
	t.Log(arr, arr2) //[1 2 3 4 5] [1 2 3 4 5 6]
}

18.1.5 여러 값 추가

  • append()를 이용해 여러 값을 추가할 수 있다.
func Test_appendMoreThanTwo(t *testing.T) {
	arr := []int{1, 2, 3, 4, 5}
	arr2 := []int{6, 7, 8, 9, 10}
	arrAppend := append(arr, arr2...)
	t.Log(arrAppend) //[1 2 3 4 5 6 7 8 9 10]
}

18.2 슬라이스 동작 원리

  • 슬라이스는 내장 타입으로 내부 구현이 감춰져 있지만 reflect 패키지의 SliceHeader 구조체를 사용해 내부 구현을 살펴볼 수 있다.
type SliceHeader struct {
	Data uintptr
	Len  int
	Cap  int
}

func Test_SliceHeader(t *testing.T) {
	arr := []int{1, 2, 3, 4, 5}
	header := (*reflect.SliceHeader)(unsafe.Pointer(&arr))
	log.Println(header.Data, header.Len, header.Cap)
}
  • 슬라이스 구현은 배열을 가리키는 포인터와 요소 개수를 나타내는 len, 전체 배열 길이를 나타내는 cap 필드로 구성된 구조체다.
  • 슬라이스 변수 대입 시 배열에 비해 사용되는 메모리나 속도에 이점이 있음.

18.2.1 make() 함수를 이용한 선언

  • make() 함수를 사용해 슬라이스를 만들 때 인수를 2개 혹은 3개를 넣는다.
func Test_makeSlice(t *testing.T) {
	arr := make([]int, 3)
	header := (*reflect.SliceHeader)(unsafe.Pointer(&arr))
	log.Println(header.Data, header.Len, header.Cap) //1374393540056 3 3
}

func Test_makeSlice2(t *testing.T) {
	arr := make([]int, 3, 5)
	header := (*reflect.SliceHeader)(unsafe.Pointer(&arr))
	log.Println(header.Data, header.Len, header.Cap) //1374396415432 3 5
}

18.2.2 슬라이스와 배열의 동작 차이

  • 슬라이스 내부 구현이 배열과 다르기 때문에 동작도 매우 다르다.
func Test_betweenSliceAndArray(t *testing.T) {
	arr := [5]int{1, 2, 3, 4, 5}
	slice := []int{1, 2, 3, 4, 5}
	func(arr [5]int) {
		arr[2] = 10
	}(arr)
	func(slice []int) {
		slice[2] = 10
	}(slice)
	log.Println(arr) //[1 2 3 4 5]
	log.Println(slice) //[1 2 10 4 5]
}

18.2.3 동작 차이의 원인

  • Go 언어에서는 모든 값의 대입은 복사로 일어난다.
  • Arr는 모든 배열값이 복사된다.
  • Slice는 실제 배열은 복사되지 않고, SliceHeader가 복사된다.

18.2.4 append()를 사용할 때 발생하는 예기치 못한 문제1

남은 빈 공간 = cap - len

18.3 슬라이싱

  • 배열의 일부를 집어내는 기능

array[startIdx:endIdx]
startIdx, endIdx 생략 가능

  • startIdx <= slice < endIdx
  • 슬라이싱을 하면 배열 일부를 가리키는 슬라이스를 반환한다.
  • 새로운 배열이 만들어지는게 아니라 배열의 일부를 포인터로 가리키는 슬라이스를 만들어 냄.
func Test_SlicingPointer(t *testing.T) {
	arr := []int{1, 2, 3, 4, 5}
	slice := arr[1:3]
	arr[1] = 10
	log.Println(slice) //[10 3]
	slice[1] = 100
	log.Println(arr) //[1 10 100 4 5]
}
  • 같은 포인터를 공유하고 있기 때문에 위와 같은 결과가 나온다. 사용시 주의가 필요!

18.3.1 슬라이싱으로 배열 일부를 가리키는 슬라이스 만들기

func Test_SlicingCapLen(t *testing.T) {
	arr := []int{1, 2, 3, 4, 5}
	slice := arr[1:2]
	log.Println(len(arr), cap(arr)) //5 5
	log.Println(len(slice), cap(slice))//1 4
	slice = arr[2:3]
	log.Println(len(arr), cap(arr)) //5 5
	log.Println(len(slice), cap(slice)) //1 3
}
  1. 인덱스 1이상 2미만의 슬라이스를 반환
  2. len 1의 요소가 하나인 슬라이스
  3. cap은 배열의 총길이에서 시작 인덱스를 뺀 만큼 가지게 된다.
  4. cap 5 - 1 == 4
  • slicing한 슬라이스에 append()를 하면?
func Test_SlicingAppend(t *testing.T) {
	arr := []int{1, 2, 3, 4, 5}
	slice := arr[1:2]
	slice = append(slice, 10)
	t.Log(arr) //[1 2 10 4 5]
	t.Log(slice) //[2 10]
}

18.3.2 슬라이스를 슬라이싱

func Test_SlicedSliceSlicing(t *testing.T) {
	arr := []int{1, 2, 3, 4, 5}
	slice := arr[1:3] //[2 3]
	slice1 := slice[1:2] //[3]
	t.Log(slice1) //[3]
}

처음부터 슬라이싱
slice2 := slice1[0:3]
slice2 := slice1[:3]

끝까지 슬라이싱
slice2 := slice1[3:len(slice1)]
slice2 := slice1[3:]

전체 슬라이싱
slice2 := slice1[0:len(slice1)]
slice2 := slice1[:]

인덱스 3개로 슬라이싱해 cap 크기 조절

  • slice[시작인덱스:끝인덱스:최대인덱스]
func Test_SlicingThreeIndex(t *testing.T) {
	arr := []int{1, 2, 3, 4, 5}
	slice := arr[1:3:4]
	t.Log(slice) //[2 3]
	t.Log(len(slice), cap(slice)) //2 3
}

18.4 유용한 슬라이싱 기능 활용

18.4.1 슬라이스 복제

  • 슬라이스 복제는 몇가지 방법이 있다.

For문 사용

func Test_SliceCopyByFor(t *testing.T) {
	arr := []int{1, 2, 3, 4, 5}
	slice := make([]int, 5, 5)
	for i := 0; i < len(arr); i++ {
		slice[i] = arr[i]
	}
	arr = nil
	t.Log(arr) //[]
	t.Log(slice) //[1 2 3 4 5]
}

append() 내장함수 사용

func Test_SliceCopyByAppend(t *testing.T) {
	arr := []int{1, 2, 3, 4, 5}
	var slice []int
	slice = append(slice, arr...)
	arr = nil
	t.Log(arr) //[]
	t.Log(slice) //[1 2 3 4 5]
}

copy() 내장함수 사용

func Test_SliceCopy(t *testing.T) {
	arr := []int{1, 2, 3, 4, 5}
	slice := make([]int, 5, 5)
	copy(slice, arr)
	arr = nil
	t.Log(arr) //[]
	t.Log(slice) //[1 2 3 4 5]
}

18.4.2 요소 삭제

  • 요소 삭제도 여러 방법이 있으나 제일 간단한 방법 몇가지만 적겠다.
//[]int{1,2,3,4,5}의 2번 인덱스에 10추가하는 경우
func Test_SliceAddElementByAppend(t *testing.T) {
	arr := []int{1, 2, 3, 4, 5}
	targetIndex := 2
	var slice []int
	slice = append(arr[:targetIndex], append([]int{10}, arr[targetIndex:]...)...)
	t.Log(slice) //[1 2 10 3 4 5]
}

func Test_SliceAddElementByCopy(t *testing.T) {
	arr := []int{1, 2, 3, 4, 5}
	targetIndex := 2
	slice := make([]int, len(arr)+1, cap(arr)+1)
	copy(slice[:targetIndex], arr[:targetIndex])
	copy(slice[targetIndex+1:], arr[targetIndex:])
	slice[targetIndex] = 10
	t.Log(slice) //[1 2 10 3 4 5]
}

18.5 슬라이스 정렬

18.5.1 int 슬라이스 정렬

  • 간단하게 sort package 사용으로 정렬이 가능하다.
func Test_sortSlice(t *testing.T) {
	ints := []int{5, 1, 2, 3, 4}
	strings := []string{"나", "라", "다", "가"}
	sort.Ints(ints)
	sort.Strings(strings)
	t.Log(ints)
	t.Log(strings)
}

18.5.2 구조체 슬라이스 정렬

  • sort 패키지의 Sort()함수를 사용하기 위해서는 Len(), Less(), Swap() 세 메서드가 필요하다.
//나이 오름차순, 나이 같을경우 이름 내림차순
type User struct {
	Name string
	Age  int
}
type Users []User

func (u Users) Len() int { return len(u) }
func (u Users) Less(i, j int) bool {
	if u[i].Age < u[j].Age {
		return true
	}
	return u[i].Name > u[j].Name
}
func (u Users) Swap(i, j int) { u[i], u[j] = u[j], u[i] }
func Test_sortStructBySort(t *testing.T) {
	users := []User{
		{"Coen", 30},
		{"Alice", 20},
		{"Bob", 30},
	}
	sort.Sort(Users(users))
	t.Log(users) //[{Alice 20} {Coen 30} {Bob 30}]
}
  • golang 1.8 이후부터는 더 간단하게 정렬이 가능하다.
//나이 오름차순, 나이 같을경우 이름 내림차순
func Test_sortStructBySlice(t *testing.T) {
	type User struct {
		Name string
		Age  int
	}
	users := []User{
		{"Coen", 34},
		{"Alice", 20},
		{"Bob", 30},
	}
	sort.Slice(users, func(i, j int) bool {
		return users[i].Age < users[j].Age
	})
	t.Log(users) //[{Alice 20} {Coen 30} {Bob 30}]
}
profile
백엔드 프로그래머
post-custom-banner

0개의 댓글