Go 언어 배워보기 - 기초문법 3

2023.01.01

포인터

C언어에서 포인터를 제대로 배워본적이 없어서 go와 c언어의 포인터 차이점은 잘 모르겠지만 go에서는 이것만 기억하면 되는거같다.

  • 변수앞에 &를 붙이면 해당 변수는 메모리 주소를 가지며 이를 포인터 라고 부른다.
  • 포인터에 * 연산자를 사용하면 해당 주소의 실제 값을 참조한다. 이를 역참조 또는 간접참조 라고 부른다.
  • C 와는 다르게 포인터 연산을 지원하지 않는다.

포인터 연산이 무엇인지 궁금하다면 이걸 읽어보자.

var num = 10 int            
var pointer = &num *int     //변수 num의 주소값을 가진 포인터를 생성했다. 타입을 정의할때도 *(타입) 과 같이 포인터임을 명시한다.

fmt.Println(pointer)        //num의 주소값을 나타낸다.
fmt.Println(*pointer)       //num의 실제 값을 나타낸다.

*pointer=20    //num의 값을 직접 바꿀수도 있다.

구조체

구조체(struct)는 여러개의 변수를 묶은 집합체인데 별도의 메소드같은 기능이 없는 오직 데이터만 저장할수있는 객체이다.
구조체를 정의하기 위해 type 키워드를 사용한다.

type player struct{
    id string
    level int
}

구조체의 요소는 . 으로 접근가능하다.

p1 := player{}    //p1 변수 선언
p1.id="user1"
p1.level=3
      
println(p1.level)
println(p1.id)

구조체 선언 방법

p1 := player{"user1",3}    //player 구조체를 선언할때와 똑같은 순서대로 값을 입력하거나 
p2 := player{              //key값으로 직접 정의할수도 있다.
             id:"user2",
             level:4
        }

//또는 go의 new 연산자를 사용하여 선언할수도있다.

p3 := new(player)
p3.id="uesr3"

구조체 포인터

구조체 또한 포인터로 접근할수있다.

p1 := player{}
pointer := &p1

(*pointer).id="user1"
pointer.level=1        // (*pointer).id 와 같이 접근하는 문법도 있지만 이렇게 생략도 가능하다.

배열

배열을 선언할때는 [길이]타입 으로 선언한다.

array := [10]int    //길이가 10인 int 타입 배열
array[0]=1
array[1]=2

go에서 배열 길이는 배열 타입의 일부분이다. ([3]int 와 [4]int가 서로 다른타입 이라는 뜻)
그리고 한번 선언한 배열의 길이는 고정값이며 수정이 불가능하다.

배열 초기화

배열 선언후 중괄호({}) 로 묶어서 초기값을 지정할수 있다. 배열의 길이를 직접 지정하지 않고 [...] 으로 자동으로 초기값의 길이만큼 정할수도있다.

array := [3]int {1,2,3}
array2 := [...]int {1,2,3,4}    //자동으로 길이가 4가 됨

슬라이스

slice는 배열과 달리 동적인 크기를 가질수있다. 슬라이스를 생성하는 방법은 배열을 선언할때 길이를 정하지 않으면 slice로 생성된다.

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

또는 make() 메소드로 생성할수도 있다.
make의 첫번째 인자로 배열타입과 두번째 인자로 길이, 세번째 인자로 용량(capacity)를 전달해 배열을 생성한다.
(용량이 무엇인지 궁금하다면 여길 참조해보자)

s := make([]int,10)    //capacity 를 생략할경우 자동으로 길이와 같은 용량으로 생성된다.
s2 := make([]int,10,20)    //길이가 10이고 용량이 20인 슬라이스를 생성한다.

println(len(s),cap(s2))    //슬라이스의 길이와 용량은 각각 len() 과 cap() 메소드로 확인할수있다.

슬라이스 자르기

슬라이스의 특정부분을 잘라 새로운 슬라이스를 만들수있다. 이를 sub slice 라고 한다.
서브슬라이스는 [시작 인덱스:끝 인덱스+1] 로 생성한다..

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

sub := s[1:3]    //시작 인덱스는 0부터 시작하며 끝나는 인덱스는 1부터 시작한다.
                 //즉, 이 서브슬라이스는 [2,3] 이 된다.
                 
//첫번째 또는 마지막 인덱스 생략이 가능하다. 인덱스를 생략하면 자동으로 맨 처음와 맨 끝을 가리킨다.
sub = s[:3]    //[1,2,3]
sub = s[1:]    //[2,3,4,5]

슬라이스는 사실 포인터임 ㅎ

우리가 방금까지 사용했던 슬라이스는 사실 배열이 아닌, 단지 배열을 가르키고있는 포인터이다. 정확히 얘기하자면 슬라이스는 배열을 가리키는 포인터길이(length),용량(capacity) 이 3개로 이루어져있다.

slice 내부 [이미지 출처: 예제로 배우는 Go 프로그래밍]


이 이미지의 첫번째 부분을 보자. S라는 슬라이스를 생성하면 메모리상에 배열이 만들어지고 실제로 S는 배열의 형태가 아닌, 단지 만들어진 배열의 주소값과 길이,용량을 가지게 된다. 그리고 이미지의 아래부분과 같이 S의 값을 S[2:5] 와 같이 변경했을때 슬라이스는 배열의 2번부터 4번 인덱스까지를 가리키는 주소값과 길이, 용량을 가지게 된다.

실제로 아래의 코드를 실행해보면 서브슬라이스로 잘라낸 배열은 복사가된 배열이 아닌 원본 배열을 그대로 가리키고 있는 상태며 원본 또는 서브슬라이스를 변경하게 되면 서로 변경된 내용이 반영되는걸 볼수있다.

func main() {
	s := []int{0, 1, 2, 3, 4, 5}
	s2 := s[2:5]
	fmt.Println(s)
	fmt.Println(s2) //[2,3,4]
	s2[0] = 7       //이제 원본 배열은 [0,1,7,3,4,5] 가 되었다.
	s[4] = 8        //[0,1,7,3,8,5]
	fmt.Println(s)
	fmt.Println(s2)		//[7,3,8] 원본을 수정해도 서브슬라이스는 원본 배열을 가리키고 있기때문에 바뀐값을 보여준다.
}

슬라이스에 데이터 추가하기(append)

append() 메소드로 슬라이스에 새로운 데이터를 추가할수 있다.
첫번째 요소로 데이터를 추가할 슬라이스를 그리고 두번째 이후의 파라미터는 추가할 요소들이다.

s := []int{}
append(s,1)    //하나를 추가할수도 있고
append(s,2,3,4,5) //여러개를 한번에 추가할수도 있다.

슬라이스 복사하기(copy)

copy() 메소드는 첫번째 인자(목표)인 슬라이스에 두번째 인자(복사할 슬라이스)인 슬라이스의 내용들을 복사하여 붙여넣는 메소드이다.

source := []int{0, 1, 2}
target := make([]int, len(source), cap(source)*2)
copy(target, source)        //원본 슬라이스의 내용을 다른 슬라이스인 target에 복사한다.

슬라이스에 대해 좀더 알아보기

슬라이스를 공부하며 느낀건 슬라이스가 내부적으로 어떻게 이루어지고 동작하는지에 대한 내용이 상당히 길고 복잡했다.
이 포스트에 한번에 작성하기엔 내용이 너무 많아 나중에 따로 정리해서 포스트를 쓸 예정이다.

Do you want something exciting?

© 2022. YSH All rights reserved.