슬라이스와 배열은 Go에서 가장 많이 사용되는 데이터 구조 중 하나입니다. 비슷한 구문을 가지고 있지만, 이들의 구성과 동작의 차이는 성능, 메모리 및 의미에 대한 오류를 초래할 수 있습니다.
문제의 히스토리:
Go는 처음부터 명시적인 메모리 관리 모델을 선택했으며, 이 모델에서 배열(arrays)은 고정 크기의 요소 시퀀스이고, 슬라이스(slices)는 배열에 대한 동적 뷰입니다. 이러한 구분은 작업 비용과 코드의 동작을 제어할 수 있게 합니다.
문제:
주요 어려움은 배열 복사(value semantics)와 슬라이스의 "참조성" 간의 혼란입니다. 이러한 타입을 함수에 전달하고 값을 변경할 때 종종 오류가 발생하여 예기치 않은 부작용을 초래합니다.
해결책:
배열은 항상 값으로 전달될 때 복사됩니다: 함수는 모든 콘텐츠의 복사본을 받습니다. 슬라이스는 배열에 대한 포인터, 길이 및 용량을 포함하는 작은 구조체입니다. 배열의 내용을 변경하면 슬라이스 내에서 변경이 외부에 보이지만, 함수 내에서 슬라이스가 새 배열로 리디렉션 될 경우에는 보이지 않습니다.
코드 예시:
func updateArray(arr [3]int) { arr[0] = 10 } func updateSlice(slc []int) { slc[0] = 10 } func main() { a := [3]int{1,2,3} b := []int{1,2,3} updateArray(a) updateSlice(b) fmt.Println(a) // [1 2 3] fmt.Println(b) // [10 2 3] }
주요 특징:
함수 내에서 슬라이스 길이를 변경하면 어떻게 되나요? 원본 슬라이스에 영향을 미치나요?
아니요, 예를 들어 slc = slc[:2]와 같은 방식으로 함수 내에서 슬라이스 길이를 변경하면 로컬 복사본 헤더에만 영향을 미칩니다. 원본 슬라이스는 그대로 유지됩니다.
append 연산자는 수정된 슬라이스를 같은 메모리 영역에서 반환하나요?
반드시 그렇지는 않습니다. 용량이 부족하면 새로운 배열이 생성되고 새 배열에 대한 포인터가 반환됩니다. 이전 배열은 손상되지 않습니다.
코드 예시:
s := []int{1,2,3} s2 := append(s, 4, 5, 6) // s2는 새 메모리 영역에 있을 수 있습니다.
배열을 슬라이스에 할당하거나 그 반대로 가능할까요?
아니요. []int와 [5]int는 서로 다른 타입입니다. 배열을 슬라이스로 전달하려면 arr[:]와 같은 변환을 사용해야 합니다. 반대는 불가능합니다.
주니어 개발자가 배열을 전달하면서 원본 배열에 변경 사항이 적용될 것이라는 기대 속에 테이블 업데이트 기능을 구현했습니다. 수정 사항이 "저장되지" 않았습니다.
장점:
단점:
기능이 슬라이스를 받아들이고 변경된 복사본을 명시적으로 반환하여 효과의 예측 가능성을 높였습니다. 모든 변경이 자각적으로 이루어졌고, 데이터가 "유출"되거나 비밀리에 변경되지 않았습니다.
장점:
단점: