Slice

컴파일타임에 데이터 크기가 고정되어 런타임에 변경이 되지 않는 일반 배열과 달리 변경이 가능한 동적 배열 타입을 slice라고 한다. 정확하게 얘기하면 go에서 제공하는 배열을 가리키는 포인터 타입이다.


1. 선언 방법

var a []int         //길이가 0인 slice
fmt.Println(len(a))   //0

b := []int{1,2,3}   //길이가 3인 slice
fmt.Println(len(b))   //3

var c = []int{1, 5:2, 9:3}    //길이가 10인 slice
fmt.Println(c)    //[1 0 0 0 0 2 0 0 0 3]

배열선언방식과 비슷하지만 [] 안에 배열크기를 지정하지 않으면 slice로 선언이 된다.


1) make()이용한 초기화

var a = make([]int,3)  //길이가 3인 slice
a[1] = 1
fmt.Println(a)    //[0 3 0]



2. 순회

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

for i :=0; i < len(slice); i++ {
  fmt.Print(slice[i]," ")   //1 2 3
}

fmt.Println()
for _,v := range slice{
  fmt.Print(v," ") //1 2 3
}



3. 슬라이스 요소 추가

배열과 달리 동적배열인 slice는 append()함수를 이용하여 배열에 값을 추가해줄 수 있다.

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

fmt.Println(slice)    //[1 2 3]
fmt.Println(slice2)   //[1 2 3 4]

//기존의 변수에 값을 추가해주고 싶다면 아래와 같이 이용
slice = append(slice,4)
fmt.Println(slice)    //[1 2 3 4]

append() 함수는 기존의 slice에 값을 추가해주는 것이 아니라 값을 추가한 slice를 반환해주는 함수라는 점을 명심해야한다.

append()는 빈공간이 충분하다면 빈공간에 요소가 추가되고 충분하지 않다면, 새로운 배열을 생성해서 반환하게 되고 이때 빈공간은 cap-len이다.



4. 동작원리

실제 값을 가르키는 pointer와 len으로 구성되어있다고 앞서 설명한 string처럼 slice도 실제 배열을 가르키는 포인터와 len으로 구성되어 있으며 cap이라고 capacity의 약자이며 최대 배열의 크기를 가르키는 변수가 한개가 더 존재하는 구조체로 구성되어 있다.

type SliceHeader struct{
    Data uintptr
    Len  int
    Cap  int
}

위 구조체가 slice의 구조로 실제 data는 배열포인터로 표현하는 것을 볼 수 있다. 그래서 *[10]int 와는 엄연히 다른 데이터형태이다.


var slice = make([]int,3)   // len | cap이 3인 slice
var slice2 = make([]int,3,5)  //len이 3, cap이 5인 slice

make를 이용해 slice를 초기화하면 위와 같이 len과 cap을 별도로 정의해줄 수 있다.


func changeArray(array [5]int){
  array[2] = 200
}

func changeSlice(slice []int){
  slice[2] = 200
}

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

  changeArray(arr)
  changeSlice(slice)

  fmt.Println(arr)      //[1 2 3 4 5]
  fmt.Println(slice)    //[1 2 200 4 5]
}

기본적으로 array타입을 함수의 매개변수로 주어지면 배열그대로를 복사해서 이용하는 call by value형태라서 값이 변하지 않지만, slice는 내부 구조를 이해하면 값이 변하는 이유를 이해할 수 있다.

slice는 내부에 배열을 직접 가지고 있는 것이 아니라 포인터형태로 값을 가르키고 있기 때문에 slice내부의 값이 변경되는 것이다. 따라서 실제 배열의 크기가 1024byte이더라고 slice변수의 데이터 크기는 24byte로 1024보다 현저히 적은 데이터 크기를 갖는다.



5. 슬라이싱

슬라이싱은 배열의 일부를 가리키는 기능으로 슬라이싱의 결과는 슬라이스이다. [:] 으로 표현할 수 있으며, :앞에는 시작 index가 오고 :이후에는 끝나는 index를 주는데 끝index는 포함하지 않는다.

array := [5]int{1,2,3,4,5}
slice := array[1:2]

fmt.Println(array)  //[1 2 3 4 5]
fmt.Println(slice, len(slice), cap(slice))  //[2] 1 4

slice[0] = 0
slice = array[1:2]
fmt.Println(array)  //[1 0 3 4 5]
fmt.Println(slice, len(slice), cap(slice))  //[0] 1 4

슬라이스는 특정 구간을 잘라낸 슬라이스를 반환하는 것이 아니기 때문에 slice의 값을 바꾸면 array의 값도 바뀌는 것을 볼 수 있다.

slice하게 되면 len은 요소 개수로 바뀌고 cap은 원본 배열의 len에서 시작 인덱스만큼 뺀 값으로 바뀌게 된다.

슬라이스도 배열과 마찬가지로 슬라이스를 수행할 수 있다.


1) 특정위치부터 끝까지 슬라이스

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

끝까지 슬라이스 하고 싶을때는 배열길이를 주면 되지만 이는 위 예시처럼 생략이 가능하다.


2) 전체 슬라이싱

array := [5]int{1,2,3,4,5}
slice := array[:]

배열을 슬라이스로 바꾸고 싶을때 사용할 수 있다.


3) 캡사이즈 조절 슬라이싱

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

위처럼 세번째 인자로 최대 인덱스를 입력하면 cap사이즈가 최대인덱스-시작인덱스로 조절이 된다.



6. 슬라이스 깊은 복사

1) for

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

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

2) append()

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

append가 슬라이스를 반환하는 점을 이용해서 빈 슬라이스에 기존의 slice를 append하면 깊은 복사를 수행할 수 있다.


3) copy()

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

go 내장 함수중에 copy()메서드를 이용하는 방법도 있다. 이때 copy() 리턴값은 복사한 개수이다.




Reference

『Tucker의 Go 언어 프로그래밍』 스터디 요약 노트

Tags :

Related Posts

PointRee 프로젝트 2 - front 개발환경 셋팅과 전체적인 디자인

PointRee 프로젝트 2 - front 개발환경 셋팅과 전체적인 디자인

가장 먼저 vscode를 통해 wsl2에 접속하고 wsl2에 폴더를 만들어 주고 react-create-app으로 간단하게 react프로젝트를 시작했다. 그리고 npm run start로 시작해보면 정상적으로 프로젝트가 실행되는 것을 확인 할 수 있다. 이때 나처럼 wsl2로 실행시킨 사람이라면 window의 브라우저에 localhost:3060을 타이핑해서 들어간다면 접속이 되지 않을 것이다....

Read More
Clean Code

Clean Code

  • Books
  • 2021년 1월 22일

1장. 깨끗한 코드 코드 품질을 측정하는 유일한 척도 = 분당 내지르는 &lsquo;WTF!&rsquo; 횟수 코드는 요구사항을 상세히 표현하는 수단 ( 기계가 실행할 정도로 상세하게 요구사항을 명시하는 작업 = 프로그래밍 ) 작성자가 아닌 사람도 읽고 고치기 쉽고 단순하고 직접적이다 중복을 피하고 한 기능만 수행하게 작제 추상화하기 프로그래밍은 코드를 읽는 시간 대 짜는 시간 비율이 9:1 잘 짠 코드도 시간이 지나면 레거시가 되니 조금씩 코드를 정리/개선하자 2장. 의미있는 이름 클래스/메서드 이름 클래스 : 명사, 명사구가 적합 메서드 : 동사, 동사구가 적합 의도를 명확하게 밝히자 ( 코드의 맥락이 코드자체에 명시적으로 드러내자) 잘못된 정보를 피하자 약어를 함부로 사용하지말자 0/O, l/1등과 같이 서로 헷갈리게 하는 변수명을 짓지말자 의미있게 구분하자 단순히 컴파일러나 인터프리터를 통과할 목적의 네이밍하지 말자 a1,a2와 같이 연속된 숫자나, 불용어를 지양하자 ex getAccount(),getAccounts(), getAccountInfo() 와 같은 메서드가 있다면 새로운 프로젝트 참가자는 메서드를 구분하기 힘들다 발음하기 쉬운 이름을 사용하자 검색하기 쉬운 이름을 사용하자 간단한 메서드에서 로컬 변수는 한 문자를 사용하더라도 상수나 대부분의 변수는 긴 이름이름이 검색하기에도 더 편하다 요즘 좋은 IDE들은 몇자 타이핑 안해도 자동 추천해주니 검색성능 면에서는 긴이름이 더 좋다 인코딩을 피하자 마찬가지로 요즘 IDE들은 코드를 컴파일하지 않고도 타입 오류를 감지할 정도로 똑똑하기 때문에 헝가리안 표기법을 지양 하자 한 개념에 한 단어를 사용하자 fetch/get/retrieve 나 controller/manager/driver와 같이 비슷한 의미의 단어를 혼용해서 사용하는 것을 지양하자 add/insert/append 와 같이 추가하는 메서드라고 하더라고 맥락이 다르면 다른 단어를 사용하자 ( 의도 를 명확하게 밝히는 것이 중요! ) 3장. 함수 가능한 한 작고 한가지 기능만 수행하도록 작성하자....

Read More
함수

함수

1. 함수 생성 방법 func키워드를 이용해서 함수를 선언할 수 있고 가장 기본적으로 만드는 main도 함수 이다. 2. 매개변수(인자) 1) 전달 방식 Java처럼 primitive자료형은 pass by value, reference자료형은 pass by refernece가 아닌 go는 C처럼 *을 이용해서 value인지 reference인지 정해서 넘겨줄 수 있다....

Read More