본문 바로가기
Language/Golang

Golang (Go언어) 슬라이스(Slice) 2/2

by HeeWorld 2024. 11. 20.

Tucker의 Go 언어 프로그래밍 책과 유튜브를 통해 학습 중입니다.

Golang 마스코트 Gopher(고퍼)

 

* 흔히 하는 실수

package main

import "fmt"

func addNum(slice []int) {
	slice = append(slice, 4)
}

func main() {
	slice := []int{1, 2, 3}
	addNum(slice)

	fmt.Println(slice)
}

// 결과

[1 2 3]

 

- slice  := []int{1, 2, 3}을 addNum의 인자를 새로 넣었는데, 이는 main의 slice와 다른 인스턴스임

- 호출 될 때 main slice 값으로 Num slice에 type size만큼 복사해서 main과 동일함. (포인터, len, cap 동일)

- main slice와 Num slice는 메모리 주소가 다름!!!

- append(slice, 4)를 수행하기 위해 새로운 공간을 할당하여 기존 값을 복사하고 4를 추가하여 반환(Num slice값 변경:포인터, len, cap)

- main slice는 영향이 없음 → 기존 배열을 그대로 가리키고 있음 → 그렇기 때문에 결과가 바뀌지 않음

 

이를 해소하기 위해 아래와 같이 코드를 변경할 수 있음.

 

package main

import "fmt"

func addNum(slice *[]int) {
	*slice = append(*slice, 4)
}

func main() {
	slice := []int{1, 2, 3}
	addNum(&slice)

	fmt.Println(slice)
}

// 결과

[1 2 3 4]

 

- Num slice를 메모리 공간이 가르키는 값과 포인터 주소로 변경 = main slice를 가리킴(같은 인스턴스를 가리킴)

 

package main

import "fmt"

func addNum(slice []int) []int {
	slice = append(slice, 4)
	return slice
}

func main() {
	slice := []int{1, 2, 3}
	slice = addNum(slice)

	fmt.Println(slice)
}

// 결과

[1 2 3 4]

 

- 새로운 슬라이스를 반환하는 방법으로 변경할 수도 있음(값을 넘겨서 새로운 값으로 반환)

 

 

슬라이싱(Slicing)

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

- 슬라이싱 기능을 사용하면 그 결과로 슬라이스를 반환

* startIdx: 시작 인덱스, endindex: 끝 인덱스

array[startIdx:endindex]

 

→ 배열의 일부를 집어낼 때는 대상이 되는 배열을 쓰고 대괄호[] 사이에 집어내고자 하는 시작인덱스:끝인덱스를 씀

→ 배열의 시작인덱스부터 끝인덱스-1까지의 배열 일부를 나타내는 슬라이스가 반환

※ 주의할 점, 끝 인덱스를 포함하지 않고 끝 인덱스 하나 전까지 포함.

인덱싱

 

✓ 슬라이싱은 새로운 배열이 만들어지는 게 아니라 배열의 일부를 포인터로 가르키는 슬라이스를 만들어냄!

 

package main

import "fmt"

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

	slice := array[1:2]
	fmt.Println("array:", array)
	fmt.Println("slice:", slice, len(slice), cap(slice))
}

// 결과

array: [1 2 3 4 5]
slice: [2] 1 4

 

- array의 두 번째부터 세 번째전까지 출력하고, len은 end - start 값, cap은 전체 배열에서 시작 주소를 뺀 값을 출력

 

package main

import "fmt"

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

	slice := array[1:2]
	fmt.Println("array:", array)
	fmt.Println("slice:", slice, len(slice), cap(slice))

	array[1] = 100
	fmt.Println("After change second element")
	fmt.Println("array:", array)
	fmt.Println("slice:", slice, len(slice), cap(slice))
}

// 결과

array: [1 2 3 4 5]
slice: [2] 1 4
After change second element
array: [1 100 3 4 5]
slice: [100] 1 4

 

- array의 첫 번째 자리를 100으로 바꾸면, 슬라이싱은 새로운 배열을 만드는 것이 아니고 가르키는 것이기 때문에 100을 출력

 

package main

import "fmt"

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

	slice := array[1:2]
	fmt.Println("array:", array)
	fmt.Println("slice:", slice, len(slice), cap(slice))

	array[1] = 100
	fmt.Println("After change second element")
	fmt.Println("array:", array)
	fmt.Println("slice:", slice, len(slice), cap(slice))

	slice = append(slice, 500)
	fmt.Println("After append 500")
	fmt.Println("array:", array)
	fmt.Println("slice:", slice, len(slice), cap(slice))
}

// 결과

array: [1 2 3 4 5]
slice: [2] 1 4
After change second element
array: [1 100 3 4 5]
slice: [100] 1 4
After append 500
array: [1 100 500 4 5]
slice: [100 500] 2 4

 

- array에 두 번째와 세 번째 값을 100, 500 slice에 100, 500을 출력

- 처음에 만들었을 때 len이 1이고, cap이 4라서 빈공간이 충분하다고 생각하여 500을 추가

- slice의 메모리 배열은 기존 배열이기 때문에 array의 값도 같이 바뀜

 

 

슬라이스를 슬라이싱하기

- 슬라이싱은 슬라이스 일부를 집어낼 때도 사용할 수 있음

slice1 := []int{1, 2, 3, 4, 5}
slice2 := slice1[1:2] // slice2는 [2]

 

- slice1은 [1, 2, 3, 4, 5]의 5개 요소를 갖는 배열을 가리키고,

   slice2는 slice1이 가리키는 배열의 시작 인덱스 1인 두 번째 요소를 가르키는 슬라이스가 됨.

- slice2의 len은 1로 2값을 갖는 요소 하나만 갖고, cap은 4로 전체 길이에서 시작 인덱스를 뺀 값이 됨.

슬라이싱

 

 

* Python의 슬라이싱과 Go의 슬라이싱은 다르다!

a = [1,2,3]
b = a[0:2]
b[0] = 100

print(a)
print(b)

// 결과
[1,2,3]
[100,2]

 

※ python에서는 슬라이싱을 하면 새로운 배열이 나오기 때문에 Go와 다름!!

 

 

<처음부터 슬라이싱>

slice1 := []int{1, 2, 3, 4, 5}
slice2 := slice1[0:3] // slice2는 [1, 2, 3]

 

- slice2는 slice1의 첫 번째부터 세 번째까지 집어냄.

- cap 값은 slice1이 가리키는 배열의 전체 길이 5에서 시작인덱스인 0을 뺀 결과인 5가 됨.

- 처음부터 슬라이싱할 때는 아래와 같이 0을 제외하고 사용할 수 있음.

// 두 개가 같은 구문

slice2 := slice[0:3]
slice2 := slice1[:3]  // 처음부터 슬라이싱할 때 0을 뺄 수 있음

 

<끝까지 슬라이싱>

slice1 := []int{1, 2, 3, 4, 5}
slice2 := slice1[2:len(slice1)] // slice2는 [3, 4, 5]

 

- slice2는 slice1의 세 번째부터 끝까지 집어냄

- cap은 배열 전체 길이 5에서 시작 인덱스 2를 뺀 3을 갖음.

- 끝까지 슬라이싱 하는 경우 끝 인덱스를 생략할 수 있음.

 

★ python에서는 [2:-1] 끝에서 하나 전까지의 의미로 음수 사용이 가능하지만, Go는 지원 안함!

     Go는 [2: len(slice)-1]의 형태로 사용해야 함!

// 두 개가 같은 구문

slice2 := slice[2:len(slice1)]
slice2 := slice1[2:]  // 끝까지 슬라이싱할 때

 

<전체 슬라이싱>

// 두 개가 같은 구문

array := [5]int{1, 2, 3, 4, 5}
slice := array[:]   // 전체 슬라이스

 

- array 배열을 슬라이싱 하는데 시작과 끝 인덱스를 생략했을 때, 처음부터 끝까지 슬라이싱하는 것을 의미

- 전체 슬라이싱은 배열 전체를 가리키는 슬라이스를 만들고 싶을 때 주로 사용

 

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

- 인덱스를 2개만 사용할 때 cap은 배열의 전체 길이에서 시작 인덱스를 뺀 값이 됨.

- 슬라이싱 할 때 인덱스 3개를 사용해서 cap까지 조절할 수 있음

slice[ 시작 인덱스 : 끝 인덱스 : 최대 인덱스 ]

 

- 시작 인덱스부터 끝 인덱스 하나 전까지 집어내고 최대 인덱스까지만 배열을 사용함

- 집어낸 슬라이스의 cap값은 최대 인덱스 - 시작 인덱스가 됨.

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

 

→ slice[1:3:4]를 한 경우 slice1이 가리키는 전체 배열 길이(즉 5개)를 다 쓰는게 아니라 인덱스 4까지만 배열을 사용

     즉, 배열의 두 번째부터 네 번째까지만 배열을 사용하게 됨. (3개의 공간에 2개를 사용하고 있는 슬라이스가 됨)

   

 

슬라이스 복제

- slice1이 가리키는 배열과 똑같은 배열을 복제한 뒤 slice2가 가리키게하면 서로 다른 배열을 가리킴

- slice1의 요솟값을 변경해도 slice2값은 변경되지 않음.

package main

import "fmt"

func main() {
	slice1 := []int{1, 2, 3, 4, 5}
	slice2 := make([]int, len(slice1))

	for i, v := range slice1 {
		slice2[i] = v
	}

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

// 결과

slice1 [1 2 3 4 5]
slice2 [1 100 3 4 5]

 

- slice1과 똑같은 길이의 다른 슬라이스를 만들어 요솟값을 하나씩 돌면서 복사함.

 

package main

import "fmt"

func main() {
	slice1 := []int{1, 2, 3, 4, 5}
	slice2 := append([]int{}, slice1...)

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

// 결과

slice1 [1 2 3 4 5]
slice2 [1 100 3 4 5]

 

- append() 함수를 사용하여 slice1의 모든 값을 복제한 새로운 슬라이스를 만듦.

- 배열이나 슬라이스 뒤에 ...을 사용하면 모든 요솟값을 넣어준 것과 같음

slice2 := append([]int{} slice1[0], slice1[1], slice1[2], slice1[3], slice1[4])

 

package main

import "fmt"

func main() {
	slice1 := []int{1, 2, 3, 4, 5}
	slice2 := make([]int, len(slice1))
	copy(slice2, slice1)

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

// 결과

slice1 [1 2 3 4 5]
slice2 [1 100 3 4 5]

 

- 내장함수 copy()를 사용해 복사할 수 있음

- 첫 번째 인수로 복사한 결과를 저장하는 슬라이스 변수를 넣고, 두 번째 인수로 복사 대상이 되는 슬라이스 변수를 넣음

- 반환값은 실제로 복사된 요소 개수

- 실제 복사되는 요소 개수는 목적지의 슬라이스 길이와 대상의 슬라이스 길이 중 작은 개수만큼 복사

func copy(dst, src []Type) int

 

 

요소 삭제

- 슬라이스 중간의 요소를 삭제할 수 있음

- 중간 요소를 삭제하고, 중간 요소 이후의 값을 앞당겨서 삭제된 요소를 채우고 마지막 값을 지워야함

요소 삭제

package main

import "fmt"

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

	for i := idx + 1; i < len(slice); i++ {
		slice[i-1] = slice[i]
	}

	slice = slice[:len(slice)-1]

	fmt.Println(slice)
}

// 결과

[1 2 4 5 6]

 

- 삭제할 인덱스(idx)를 2로 그 다음부터 끝까지 뒤에부터 앞으로 하나씩 복사

- 맨 마지막은 -1로 잘라내면 요소를 앞당길 수 있음.

 

package main

import "fmt"

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

	slice = append(slice[:idx], slice[idx+1:]...)

	//for i := idx + 1; i < len(slice); i++ {
	//	slice[i-1] = slice[i]
	//}

	//slice = slice[:len(slice)-1]

	fmt.Println(slice)
}

// 결과

[1 2 4 5 6]

 

- append를 사용하여 한 줄로 줄여서 사용할 수 있음.

slice = append(slice[:idx], slice[idx+1:]...)

 

- slice[:idx]는 slice 처음부터 idx 하나 전까지 집어낸 슬라이스. 즉, 지우고자 하는 인덱스의 요소는 포함하지 않음

   = [1, 2]

- slice[idx+1:]는 idx 하나 뒤의 값부터 끝까지 슬라이스. 

   = [1, 2, 4, 5, 6]

- append()함수로 [1, 2] 슬라이스 뒤에 [4, 5, 6]을 붙이면 [1, 2, 4, 5, 6]이 됨.

 

package main

import "fmt"

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

	copy(slice[idx:], slice[idx+1:])

	// slice = slice[:len(slice)-1]

	fmt.Println(slice)
}

// 결과

[1 2 4 5 6]

 

- copy(dst, src) 구조로 copy 함수를 통해 요소를 삭제할 수 있음

 

 

요소 삽입

- 슬라이스 중간에 요소를 추가할 수 있음

- 슬라이스 맨 뒤에 요소를 하나 추가하고, 맨 뒤값부터 삽입하려는 위치까지 한 칸씩 뒤로 밀어주고 삽입하는 위치의 값을 바꿈

요소 삽입

package main

import "fmt"

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

	slice = append(slice, 0)
	idx := 2

	for i := len(slice) - 2; i >= idx; i-- {
		slice[i+1] = slice[i]
	}

	slice[idx] = 100
	fmt.Println(slice)
}

// 결과

[1 2 100 3 4 5 6]

 

- 맨 뒤에 요소를 추가하는데 추가하는 값은 중요하지 않아 0으로 처리

- 맨 뒤부터 삽입하는 자리까지 하나씩 뒤로 밀고, idx 위치의 값을 바꿈

 

package main

import "fmt"

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

	slice = append(slice[:idx], append([]int{100}, slice[idx:]...)...)

	fmt.Println(slice)
}

// 결과

[1 2 100 3 4 5 6]

 

- append()를 중첩으로 사용할 수 있음

- slice[:idx]는 처음부터 삽입하는 위치 전까지

   = [1, 2]

- []int{100}은 삽입하려는 값으로 100 한 개만 갖는 슬라이스

- slice[idx:]는 삽입하려는 위치부터 끝까지의 슬라이스

   = [3, 4, 5, 6]

- append() 함수로 []int{100}에 이 슬라이스를 합쳐 [100, 3, 4, 5, 6]을 만듦

- append() 함수로 [1, 2]에 [100, 3, 4, 5, 6]을 합쳐서 [1, 2, 100, 3, 4, 5, 6]을 만듦

 

package main

import "fmt"

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

	slice = append(slice, 0)
	copy(slice[idx+1:], slice[idx:])
	slice[idx] = 100

	fmt.Println(slice)
}

// 결과

[1 2 100 3 4 5 6]

 

- copy()를 통해 슬라이스 값을 복사(for문과 비슷한 형식)

- 해당 방법이 불필요한 메모리 사용이 없음!

 

 

슬라이스 정렬

- Go 언어에서 기본 제공하는 sort 패키지를 사용해 슬라이스 정렬을 할 수 있음

package main

import (
	"fmt"
	"sort"
)

func main() {
	slice := []int{5, 2, 6, 3, 1, 4}
	sort.Ints(slice)

	fmt.Println(slice)
}

// 결과

[1 2 3 4 5 6]

 

 

구조체 슬라이스 정렬

- sort() 함수를 이용하기 위해 len(), less(), swap() 세 메서드가 필요함.

- 해당 메서드만 구현하면, 정의한 구조체도 정렬을 할 수가 있음.

package main

import (
	"fmt"
	"sort"
)

type Student struct {
	Name string
	Age  int
}

type Students []Student

func (s Students) Len() int           { return len(s) }
func (s Students) Less(i, j int) bool { return s[i].Age < s[j].Age }
func (s Students) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }

func main() {
	s := []Student{
		{"화랑", 32}, {"백두산", 53}, {"류", 43}, {"켄", 38}, {"송하나", 18}}

	sort.Sort(Students(s))
	fmt.Println(s)
}

// 결과

[{송하나 18} {화랑 32} {켄 38} {류 43} {백두산 53}]