한 눈에 끝내는 고랭 기초
    • 01
      시작하기에 앞서
    • 강의대상
      Go란?
      실습 환경 구축 - 구름IDE
      Hello goorm! 출력해보기
    • 02
      변수와 상수
    • 콘솔 출력 함수의 기본
      변수의 선언과 초기화
      상수의 선언과 초기화
      간단한 덧셈
      잘못된 신상정보
    • 03
      연산자
    • 연산자 종류
      연산자 우선순위
      콘솔 입력 함수의 기본
      간단한 덧셈과 곱셈
      몫과 나머지
    • 04
      자료형
    • 자료형의 종류와 특징
      문자열의 표현
      자료형의 변환
      강제 형 변환
    • 05
      콘솔 출력과 입력 함수
    • 콘솔 출력 함수(Print)
      콘솔 입력 함수(Scan)
      정돈된 표
      신상정보 입력과 출력
    • 06
      반복문 - for
    • 오로지 for
      구구단
      빛나는 이등변삼각형
    • 07
      조건문 - if/else
    • 조건에 따른 실행과 흐름 분기
      7과 9의 배수
      두 수의 차
    • 08
      분기문 - switch
    • swich문에 의한 선택적 실행
      안좋은 계산기
    • 09
      제어문
    • break, continue, goto문
      구구단2
      두 수를 더하면 99
    • 10
      컬렉션
    • 배열(Array)
      슬라이스(Slice)
      맵(Map)
      역행렬
      가장 긴 이름
      중간고사 평균 점수
    • 11
      함수
    • Go언어에서의 함수
      전역변수와 지역변수
      매개변수
      반환값(리턴값)
      익명 함수
      오름차순 정렬
      아이패드를 사주는 조건
      역학적 에너지
    • 12
      클로저
    • 외부 변수 접근 : 클로저
      동전 정리
    • 13
      구조체와 메소드
    • 구조체
      메소드
      성적 저장 프로그램
      역학적 에너지2
    • 14
      인터페이스(interface)
    • 메소드의 집합 인터페이스
      직육면체와 원기둥
    • 15
      defer와 panic()
    • 지연 처리 defer
      종료하는 panic(), 복구하는 recover()
      엘리베이터
      중간고사 평균 점수2
    • 16
      에러 처리
    • 에러 처리의 기본
      에러 출력 및 처리
      중간고사 평균 점수3
    • 17
      고루틴(Goroutine)
    • 비동기 프로세스의 기본
      고루틴의 활용
      고루틴 실습
    • 18
      채널(Channel)
    • 고루틴의 데이터 통로 : 채널
      비동기 채널과 버퍼
      동기 채널
      채널 닫기
      송신 전용, 수신 전용 채널
      채널 select문
      고루틴 실습2
      메시지 전송
      동기 채널 실습
      비동기 채널 실습
    • 19
      패키지(package)
    • 패키지의 개념
    • 20
      실전 프로젝트 - 마일리지 상점
    • 프로젝트를 시작하기 앞서
      실습모드에서 실습
      마일리지 상점의 주요 기능
      필수 구조체와 매인 메뉴
      잔여 수량, 잔여 포인트 구현
    • 21
      실전 프로젝트 - 주문하기, 장바구니 담기
    • 구매 구현(1)
      구매 구현(2)
      장바구니에 담기
      장바구니 확인(1)
      장바구니 확인(2)
    • 22
      실전 프로젝트 - 고루틴으로 배송하기
    • 배송 상태 확인(1)
      배송 상태 확인(2)
      배송 상태 확인(3)
      최종 코드
    슬라이스(Slice)
    10 컬렉션
    슬라이스(Slice)

    Go언어에서 배열은 고정된 크기 안에 동일한 데이터를 연속으로 저장해 배열의 크기를 필요에 따라 동적으로 증가시키거나 부분 배열을 발췌하는 등의 기능을 가지고있지 않습니다. 그런데 'Slice(이하 슬라이스)'는 배열과 다르게 고정된 크기를 미리 지정하지 않고 이후에 필요에 따라 크기를 동적으로 변경할 수 있고, 부분 발췌가 가능합니다. 그리고 다차원 선언을 비롯한 배열의 모든 기능을 똑같이 구현할 수 있습니다. 따라서 슬라이스는 배열의 여러 제약점들을 넘어 여러 값을 다룰 때 개발자에게 주로 쓰입니다.


    이러한 장점을 가진 (상대적으로)좋은 자료형인 슬라이스는 지금까지 배운 자료형과 내부적인 구조가 다르기때문에 선언 및 초기화를 할 때 주의해야합니다. 지금까지 배운 자료형의 선언 및 초기화방법과 비교해서 설명하겠습니다.

    지금까지 배운 정수형(int, int32 ...등등)과 실수형(float32, float64...등등), 배열 등과 같은 자료형을 선언할때 "var 변수이름 자료형" 형식으로 선언했습니다. 예를들어 int형 변수 num을 선언한다면 var num int와 같은 형태로 입력했습니다. 이 선언의 뜻은 "한개의 int형의 변수가 들어갈 메모리를 만들었다."는 말입니다. 그런데 Go언어에서는 아무런 값을 초기화 하지 않고 선언만 해도 정수나 실수형은 0, 문자열형은 ""(빈칸)이 자동 할당된다고 자료형 챕터에서 배웠습니다. 따라서 정확히 말한다면 선언과 동시에 자동 초기화도 되는 것입니다(자동으로 0, ""이 할당되기때문에). 

    다른 자료형과 마찬가지로, 배열도 크기를 지정하고 선언하기 때문에 명시한 개수만큼의 메모리를 만듭니다. 예를들어 아래 그림처럼 var arr2 [3]int라고 입력하면 3개의 int타입의 변수가 들어갈 메모리를 만들고, 초기화하지 않았기 때문에 자동으로 0이 할당됩니다. 따라서 선언만 했을 뿐인데 len() 함수를 이용해 배열의 크기가 3이라는 것을 확인할 수 있습니다.

    default

    하지만 슬라이스를 위에서 설명한 것과 같은 방법으로 var a []int와 같이 선언한다면 배열의 일부분을 가리키는 포인터를 만듭니다. 선언만 하고 초기화를 하지 않아서 슬라이스의 정보만 있는 배열만 생성되고, 실질적으로 어떠한 변수가 들어갈 공간(메모리)은 생성되지 않습니다. 그렇다면 '다른 자료형은 메모리를 만들고 자동으로 0이나 ""을 할당하는데, 왜 슬라이스는 만들지 않을까?'와 같은 궁금증이 생길 수 있습니다.

    정말 간단합니다. 왜냐하면 슬라이스는 크기를 미리 지정하지 않기 때문에 컴퓨터가 어디서부터 어디까지 0이나 ""으로 채워야하는지 알 수 없기 때문입니다. 따라서 슬라이스의 초기 값을 지정하지 않고 선언만 한다면 'Nil silce'가 됩니다. 이것은 크기도 용량도 없는 상태를 의미합니다. 당연히 메모리를 만들지 않아서 존재하지도 않기 때문에 a[0] = 1과 같이 값을 지정할 수 없습니다.

    아래 그림을 봅시다.


    기본적으로 슬라이스는 아무런 값도 초기화하지 않아도 배열의 위치를 가리키는 ptr과 배열의 길이인 len, 전체크기인 cap 메모리를 가지고 있습니다. 

    그렇기 때문에 슬라이스를 var a []int와 같이 선언을 할 때는 주로 var a []int = []int{1, 2, 3, 4}같이 선언과 동시에 값을 초기화할 때만 사용합니다. 이는 슬라이스를 선언함과 동시에 1, 2, 3, 4를 위한 메모리를 만든다는 뜻입니다. 이때부터 a[1] =18과 같이 메모리에 저장돼있는 값을 바꿀 수 있고, 슬라이스의 길이와 용량을 확인하는 함수를 사용할 수 있습니다.


    이러한 내부 구조를 이해한다면 슬라이스 복사를 쉽게 이해할 수 있습니다. 배열은 다른 배열의 값을 대입하면 값 자체가 대입됩니다. 하지만 슬라이스는 참조 타입이기 때문에 슬라이스를 복사해온다는 것은 사실 같은 주소를 참조한다는 것과 같은 말입니다. 예를 들어, 슬라이스는 다른 슬라이스를 부분 복사할 수 있는 기능이 있는데 슬라이스 a를 부분 복제하려고 하는 슬라이스 l은  l = a[2:5]를 입력함으로써 슬라이스 a의 인덱스2 요소부터 4요소까지 참조합니다. 그렇기 때문에 슬라이스는 데이터의 복사 없이 데이터를 사용 할 수 있다는 장점이 있습니다. 이는 아래 그림과 같이 묘사 할 수 있습니다. 

    여기에 배열이 제공하지 않는 기능들을 사용하고 있으니 Go언어의 장점인 슬라이스를 잘 활용하는 것이 좋습니다.

    default

    참고로 슬라이스의 길이와 용량을 지정하지 않고 슬라이스를 선언만 해서 Nil slice만들면 nil과 비교할 수 있고 true을 반환합니다.  

    go
    실행 결과를 확인해보세요!

    make() 함수를 이용한 슬라이스 선언


    지금까지 슬라이스의 내부 구조에 대해 배우고 이에따른 선언과 초기화의 원리를 배웠습니다. 그렇다면 지금부터 슬라이스를 선언만 하면서 크기를 미리 지정할 수 있는 방법(즉, 값을 저장할 수 있는 메모리를 선언만 함으로써 생성)에 대해 배우겠습니다. 슬라이스를 생성하는 또 다른 방법으로는 Go언어의 내장 함수인 make() 함수를 이용한 선언입니다. 이 함수는 개발자가 슬라이스를 생성함과 동시에 슬라이스의 길이(len), 슬라이스의 용량(cap)을 저장할 수 있습니다. make() 함수는 "make(슬라이스 타입, 슬라이스 길이, 슬라이스의 용량)" 형태로 선언합니다.여기서 용량(Capacity)은 생략해서 선언할 수 있습니다. 용량을 생략한다면 슬라이스의 길이와 똑같은 값으로 선언됩니다. 이렇게 make() 함수를 이용해 선언한다면 비로소 모든 요소가 0인 슬라이스를 만들게 됩니다.

    여기서 슬라이스의 길이와 용량의 개념이 헷갈릴 수 있습니다. 

    • 길이 : 초기화된 슬라이스의 요소 개수 즉, 슬라이스에 5개의 값이 초기화된다면 길이는 5가 됩니다. 그 후에 값을 추가하거나 삭제한다면 그만큼 길이가 바뀌게 됩니다. "len(컬렉션이름)"으로 길이를 알 수 있습니다.
    • 용량 : 슬라이스는 배열의 길이가 동적으로 늘어날 수 있기 때문에 길이와 용량을 구분합니다. 예를 들어, 동호회에서 야유회를 가기위해 버스를 대절한다고 생각해봅시다. 야유회를 가기 위해 모인 인원은 125명이고 버스는 25인승입니다. 125명은 배정이 완료 되어서 버스를 5대를 대절했는데, 11명이 추가로 가고싶다고 합니다. 그래서 추가로 25인승짜리 버스 한 대를 대절했습니다. 여기서 총 승객 136명은 "길이"입니다. 그리고 버스가 한번에 태울 수 있는 승객은 "용량"입니다. 다시 Go언어로 돌아와서 make() 함수를 이용해 슬라이스를 선언한다고 생각해봅시다. 선언한 슬라이스의 용량이 25인데 101개의 값을 초기화하기 위해서는 125의 용량이 필요하게됩니다. 이러한 방식으로 메모리를 관리하는 것입니다. 용량은 "cap(컬렉션이름)"으로 용량을 알 수 있습니다.

    그리고 주의해야할 점은 make() 함수를 이용해 슬라이스의 메모리를 할당하고 난 후에 []int{1,2,3,4}와 같은 식으로 입력하여 값을 초기화하면 새로운 메모리를 할당하면서 그 전의 값은 없어집니다. 어느 부분에서든 동일하게 적용되는 당연한 것입니다. 기존의 메모리를 사용하고 값을 추가하기 위해서는 아래에서 배우는 append() 함수를 사용해야합니다.


    아래 코드를 바로 실행해보세요.

    go
    실행 결과를 확인해보세요!

    슬라이스 추가, 병합, 복사


    또한 append() 함수를 이용해서 슬라이스에 데이터를 추가할 수 있습니다. 위 예시를 보고 이미 눈치챘을 겁니다. 슬라이스 용량이 남아있는 경우에는 그 용량 내에서 슬라이스의 길이를 변경하여 데이터를 추가하고, 용량이 초과하는 경우에는 설정한 용량만큼 새로운 배열을 생성하고 기존 배열 값들을 모두 새 배열에 복제한 후 다시 슬라이스를 할당하는 방식입니다.

    그리고 데이터를 추가할수 있을 뿐만이 아니라 슬라이스에 슬라이스를 추가해서 붙일 수 있습니다. 여기서 슬라이스에 슬라이스를 추가하기 위해 주의할 점은 추가하는 슬라이스 뒤에 "..."을 입력해야 한다는 것입니다. ...은 슬라이스의 모든 요소들의 집합을 표현하는 것으로 아래 예제의 "sliceB..."은 슬라이스의 요소 집합인 {4, 5, 6}으로 치환되는 것입니다. 따라서 사실상 슬라이스에 슬라이스를 추가하는 것이 아니라, sliceA에 {4, 5, 6}이라는 요소들이 추가되는 것입니다.


    아래 코드를 바로 실행해보세요.

    go
    실행 결과를 확인해보세요!

    그리고 copy() 함수를 이용해 한 슬라이스를 다른 슬라이스로 복사할 수 있습니다. copy() 함수는 "copy(붙여넣을 슬라이스, 복사할 슬라이스)" 형식으로 사용합니다. 당연히 복사할 슬라이스와 붙여넣을 슬라이스 모두 선언이 선행돼야 합니다.


    아래 슬라이스를 다른 슬라이스로 복사하는 예시 코드가 있습니다. 바로 실행해보세요.

    go
    실행 결과를 확인해보세요!

    슬라이스는 원래 '자르기'라는 뜻입니다.  그래서인지 슬라이스는 슬라이스의 부분만 잘라서 복사할 수도 있습니다. 이때 "붙여넣을 슬라이스 := 복사할 슬라이스[복사할 첫 인덱스:복사할 마지막 인덱스+1]"이라고 하면 잘라서 복사할 수 있습니다(':=' 용법을 이용해 바로 선언과 동시에 값을 저장함). 예를 들어 l := sliceA[2:5]라고 한다면 슬라이스 l에 sliceA의 인덱스 2요소부터 4요소까지 잘라서 복사한다는 것입니다. 마지막 요소는 복사하지 않습니다. 그리고 처음과 마지막 인덱스를 생략하면 첫 요소와 맨 마지막 요소를 의미합니다. 예를 들어 l := sliceA[:5]라면 sliceA의 처음부터 인덱스 4의 요소까지 복사한다는 것입니다. 반대의 경우도 마찬가지로 쓸 수 있습니다.


    아래 코드를 바로 실행해보세요.

    go
    실행 결과를 확인해보세요!

    위 코드를 눈여겨 봤으면 특이한 점을 알았을 것입니다. 복사해온 슬라이스의 값을 바꿨는데 기존 복사한 슬라이스의 값도 바뀐 것을 확인할 수 있습니다. 왜냐하면 앞서 말했듯이, 슬라이스는 배열과 다르게 값을 복사해오는 것이 아니라 슬라이스 자체가 참조하고있는 주소값을 같이 참조하는 것을 의미하기 때문입니다. 하지만 같은 상황이라면 배열은 단순히 값을 복사해서 초기화합니다

    질문하기