Go

고(Go) 슬라이스 복사, 이것만 알면 끝! copy와 append 완벽 활용법 (초보자 필독)

드리프트2 2025. 5. 30. 22:51

고(Go) 슬라이스 복사, 이것만 알면 끝! copy와 append 완벽 활용법 (초보자 필독)


슬라이스 복사, 왜 신경 써야 할까요? 핵심은 '참조'의 이해!

고(Go) 언어에서 슬라이스는 데이터를 담는 아주 유용한 도구인데요, 마치 여러 개의 칸을 가진 수납함과 비슷합니다.

그런데 이 슬라이스는 조금 특별한 성질을 가지고 있습니다.

바로 '참조(Reference)'라는 방식으로 작동한다는 점인데요.

쉽게 말해, 슬라이스 변수 자체는 진짜 데이터 덩어리를 직접 들고 있는 게 아니라, 데이터가 실제로 저장된 곳의 '주소'만 가지고 있는 것과 같습니다.

그래서 슬라이스를 단순하게 다른 변수에 할당(=)해서 복사하려고 하면, 겉보기에는 복사가 된 것 같지만 실제로는 같은 주소를 가리키게 됩니다.

이러면 복사본에서 값을 바꿨는데 원본까지 바뀌어버리는, 마치 마법 같은 (하지만 프로그래머에겐 악몽 같은!) 일이 벌어질 수 있답니다.

따라서 고(Go)에서는 슬라이스를 제대로, 안전하게 복사하는 방법을 아는 것이 매우 중요합니다.

자, 그럼 지금부터 그 방법들을 하나씩 살펴볼까요?.

첫 번째 방법: 똑똑한 내장 함수 copy 활용하기!

고(Go) 언어는 슬라이스를 안전하게 복사할 수 있도록 copy라는 아주 편리한 내장 함수를 제공합니다.

이 함수는 이름 그대로 원본 슬라이스의 요소들을 새로운 대상 슬라이스로 깔끔하게 복사해주는 역할을 하는데요.

어떻게 사용하는지 예제 코드를 통해 자세히 살펴보겠습니다.

package main

import "fmt"

func main() {
    // 복사하고 싶은 원본 슬라이스
    original := []int{1, 2, 3, 4, 5}

    // 원본 슬라이스와 똑같은 길이로 새로운 슬라이스를 만들어줍니다.
    // make 함수는 슬라이스, 맵, 채널 등을 초기화할 때 사용해요.
    copySlice := make([]int, len(original))

    // copy 함수를 사용해서 original 슬라이스의 내용을 copySlice로 복사합니다.
    // 첫 번째 인자가 복사 받을 슬라이스, 두 번째 인자가 원본 슬라이스입니다.
    copy(copySlice, original)

    // 복사된 슬라이스의 내용을 출력해볼까요?
    fmt.Println(copySlice) // 결과: [1 2 3 4 5]
}



위 예제 코드를 자세히 뜯어볼까요?.

  1. 먼저 original이라는 이름으로 정수(int)들을 담고 있는 슬라이스를 만들었습니다.

  2. 다음으로, copySlice라는 이름의 새로운 슬라이스를 만들었는데요, 이때 make 함수와 len(original)을 사용해서 원본 슬라이스와 똑같은 길이로 만들어주는 것이 중요합니다.

    copy 함수는 복사 받을 슬라이스의 길이만큼만 요소를 복사하기 때문입니다.

  3. 마지막으로 copy(copySlice, original) 코드를 통해 original 슬라이스의 요소들을 copySlice로 복사했습니다.

결과적으로 copySliceoriginal과 똑같은 내용을 가지면서도, 서로 다른 메모리 공간을 사용하는 완전히 독립적인 슬라이스가 되었습니다!

슬라이스의 특정 부분만 쏙 빼서 복사하고 싶다면?

copy 함수는 슬라이스 전체뿐만 아니라, 원하는 부분만 쏙 골라서 복사하는 것도 가능합니다.

예를 들어, 원본 슬라이스의 중간 부분만 복사하고 싶을 때 아주 유용한데요.

package main

import "fmt"

func main() {
    // 원본 슬라이스
    original := []int{1, 2, 3, 4, 5}

    // 원본 슬라이스의 일부분을 담을 새로운 슬라이스를 만듭니다.
    // 여기서는 3개의 요소만 복사할 거라 길이를 3으로 지정했어요.
    copySlice := make([]int, 3)

    // original 슬라이스의 1번 인덱스부터 4번 인덱스 *이전까지* (즉, 1, 2, 3번 인덱스) 요소를 복사합니다.
    // 슬라이스[시작인덱스:끝인덱스] 문법에서 끝인덱스는 포함되지 않는다는 점을 기억해주세요!
    copy(copySlice, original[1:4])

    // 복사된 슬라이스를 확인해볼까요?
    fmt.Println(copySlice) // 결과: [2 3 4]
}



이 예제에서는 original[1:4]라는 표현을 사용해서 original 슬라이스의 1번 인덱스(값 2)부터 3번 인덱스(값 4)까지의 요소들을 copySlice로 복사했습니다.

이렇게 하면 필요한 부분만 효율적으로 복사할 수 있겠죠?.

두 번째 방법: 만능 재주꾼 append 함수로 복사하기!

슬라이스를 복사하는 또 다른 방법으로는 append 함수를 활용하는 것이 있습니다.

append 함수는 원래 슬라이스에 새로운 요소들을 추가하는 데 사용되지만, 조금만 응용하면 슬라이스 전체를 복사하는 데에도 쓸 수 있답니다.

package main

import "fmt"

func main() {
    // 원본 슬라이스
    original := []int{1, 2, 3, 4, 5}

    // append 함수를 사용해서 슬라이스를 복사합니다.
    // 텅 빈 슬라이스에 원본 슬라이스의 모든 요소를 추가하는 방식이에요.
    copySlice := append([]int{}, original...)

    // 복사된 슬라이스를 확인해봅시다.
    fmt.Println(copySlice) // 결과: [1 2 3 4 5]
}



이 코드가 어떻게 작동하는지 살펴볼까요?.

  1. []int{}는 아무 요소도 없는, 텅 빈 정수형 슬라이스를 의미합니다.

  2. append 함수에 이 텅 빈 슬라이스와 함께 original...을 전달했는데요.

    여기서 original...original 슬라이스 안에 있는 모든 요소들을 하나씩 꺼내서 펼쳐놓는다는 특별한 의미를 가집니다.

    (마치 "original 슬라이스에 있는 거 전부 다!" 라고 외치는 것과 같아요.)

결국, 텅 빈 슬라이스에 original 슬라이스의 모든 요소들이 순서대로 추가되면서, copySliceoriginal의 완벽한 복사본이 되는 것입니다.

이 방법도 꽤 간결하고 직관적이죠?.

슬라이스 복사, 이것만은 꼭 기억하세요! (중요 주의사항)

슬라이스를 복사하는 방법을 배웠으니 이제 마음껏 사용하면 될까요? 잠깐! 몇 가지 중요한 주의사항을 꼭 기억해야 합니다.

얕은 복사(Shallow Copy) vs 깊은 복사(Deep Copy): 뭐가 다를까요?

위에서 소개한 copy 함수나 append 함수를 이용한 방법은 기본적으로 '얕은 복사(Shallow Copy)'를 수행합니다.

이게 무슨 말이냐고요?

만약 슬라이스 안에 들어있는 요소들이 단순한 숫자나 글자가 아니라, 또 다른 슬라이스나 포인터(메모리 주소를 가리키는 특별한 변수)처럼 '주소 값'을 가지고 있는 경우를 생각해봅시다.

얕은 복사는 슬라이스라는 껍데기와 그 안에 담긴 주소 값들만 복사합니다.

그래서 복사된 슬라이스 안의 요소들이 가리키는 실제 데이터는 여전히 원본과 같은 곳을 바라보게 되는 건데요.

마치 한 집에 사는 두 가족이 같은 TV를 보는 것과 비슷하다고 할 수 있습니다.

한쪽에서 채널을 돌리면 다른 쪽도 영향을 받는 것처럼, 복사본에서 내부 요소의 값을 변경하면 원본의 내부 요소 값도 함께 바뀔 수 있다는 의미입니다.

만약 슬라이스 안에 담긴 참조 타입의 요소들까지 완전히 새롭게 복사해서, 원본과 복사본이 서로 전혀 영향을 주지 않도록 만들고 싶다면 '깊은 복사(Deep Copy)'를 해야 합니다.

깊은 복사는 조금 더 복잡해서, 슬라이스 안의 각 요소들을 하나하나 직접 새로 만들어서 복사해주는 과정이 필요한데요.

이는 상황에 따라 직접 코드를 작성해야 할 수도 있습니다.

새로운 슬라이스의 용량(Capacity)도 신경 써주세요!

make 함수로 새로운 슬라이스를 만들 때, 길이(length)뿐만 아니라 용량(capacity)도 지정할 수 있다는 사실, 알고 계셨나요?.

용량은 슬라이스가 실제로 메모리에 확보해둔 공간의 크기를 의미하는데요.

만약 copy 함수로 슬라이스를 복사할 때, 복사 받을 슬라이스의 용량을 길이보다 더 크게 만들어두면, 나중에 새로운 요소들을 추가할 때 메모리를 다시 할당하는 번거로운 과정을 줄일 수 있어서 성능에 약간의 이점을 줄 수도 있습니다.

물론, 단순 복사가 목적이라면 길이와 용량을 같게 만들어도 충분합니다.

마무리하며: 슬라이스 복사, 이제 자신감을 가지세요!

지금까지 고(Go) 언어에서 슬라이스를 효과적으로 복사하는 두 가지 주요 방법(copy 함수와 append 함수 활용)과 함께 몇 가지 중요한 주의사항들을 살펴봤습니다.

슬라이스가 참조 타입이라는 점, 그리고 얕은 복사와 깊은 복사의 차이점을 잘 이해하는 것이 안전하고 정확한 프로그래밍의 첫걸음인데요.

오늘 배운 내용들을 바탕으로 직접 코드를 작성하고 실행해보면서 슬라이스 복사에 대한 감을 익혀보시는 건 어떨까요?.

분명 고(Go) 언어를 다루는 여러분의 실력이 한층 더 성장하는 것을 느끼실 수 있을 겁니다!