The Go Programming Language 3~6장

Go에 대해 몰랐던 사실들 2

  • Go의 소수점 자료형인 float32와 float64는 두 가지 크기의 복소수(!?) complex64와 complex128을 제공한다.

  • complex 함수는 실수부와 허수부로 복소수를 생성하며, 내장된 real과 imag 함수는 각각 실수와 허수를 추출한다. (별 기능이 다있네;)

  • 문자열은 기본적으로 유니코드 코드 포인트를 UTF-8로 인코딩한 시퀀스로 해석한다.

  • 슬라이스는 “슬라이스의 내부 배열”이라고 알려진 배열의 원소들 일부(또는 전부)에 접근할 수 있는 경량 자료 구조다.

  • slicing이란 Go에서 배열과 slice의 특정 영역을 slice 형태로 추출할 수 있는 기능을 의미한다.

a := []int{1, 200, 3000, 40000}
b := a[0:3] // [1 200 3000] : 0번 인덱스부터 3번 인덱스 전까지 Slicing 합니다.
c := b[1:3] // [200 3000]
d := a[:2] //  [1 200] : a[0:2]와 동일
e := a[2:] // [3000 40000] : a[2:4]와 동일
  • 주의할 점은 slicing은 포인터를 사용하기 때문에 내부적으로 같은 배열을 공유한다.

  • 이게 싫으면 copy 함수를 사용해 복사된 slice를 사용해야한다.

  • 슬라이스에서 원소를 추가하기 위한 함수인 append는 동작원리가 자바의 ArrayList와 상당히 유사한 모습을 보인다.

  • 차이점은 기존 배열에서 새로운 원소를 담기에 충분하다면 원소를 추가하고 슬라이스를 확장 시켜줘야한다. 이는 자바 내부 어레이리스트에 있는 배열과는 사뭇 다른 모습이다.

  • 새로운 원소를 담기에 슬라이스 내부 배열의 크기가 충분히 크지 못하다면 내부 배열의 크기 * 2를 하고 슬라이스를 새롭게 할당한다. 그 후 기존 값들을 copy 함수를 이용해 새롭게 할당한 슬라이스의 내부 배열에 복사시키고, 새로운 원소를 추가한다.

  • Go의 mapHashTable의 참조이다.

  • map의 원소는 변수가 아니므로 주소를 얻을 수 없다. 왜냐하면 map이 커지면 기존 원소에 rehash가 일어나면서 새 저장 공간으로 옮겨질 수 있고, 이로 인해 잠재적으로 주소가 무효화될 가능성이 있기때문이다.

  • 구조체 내부에는 자기 자신과 같은 타입을 가질 수는 없지만 포인터 타입인 자기참조구조체를 가질 수 있다.

  • 이를 통해 링크드 리스트나 트리 같은 데이터를 만드는 것이 가능하다.

  • JSON은 자바스크립트 값(문자열,숫자,불리언,배열,객체)을 유니코드 텍스트로 인코딩한 것이다. (몰랐다…)

  • Go에서는 구조체를 JSON으로 바꾸는 것을 marshalling이라고 부른다. 마샬링은 json.Marshal로 수행한다.

  • 함수의 타입은 함수의 signature라고 한다. 두 함수의 파라미터 목록의 타입이 같고 결과 목록의 타입이 같으면 두 함수는 타입 또는 시그너치 값이 같다고 한다.

  • Go의 함수는 1급 시민이다.

  • 익명함수는 함수 선언 뒤에 이름을 명시하지 않음으로 사용 가능하다.

x := 3
result := func() int{
    x++
    return x*x
}
  • panic() 함수는 현재 함수를 즉시 멈추고 defer 함수를 모두 수행한 뒤 비정상 종료된다. 이는 일반적인 에러 처리보다 훨씬 더 중대하고 심각한 오류임을 의미한다.

  • panic을 다룰 수 있는 방법은 recover이 존재하는데, defer를 포함하는 함수에 panic이 발생하면 recover가 현재의 panic을 상새화고 리턴값으로 타입의 기본 값이 들어가게 된다.

  • Go에서 메소드를 정의할 때 함수와 수신자를 선언하는데, 수신자라는 이름은 초기 객체지향 언어에서 메소드 호출을 “객체에 메시지를 저농한다”라고 하던 전통을 따른 것이라고 한다.

  • Go는 다른 객체지향 언어들과 달리 어떤 타입에도 메소드를 붙일 수 있다. 가령 숫자, 문자열, 슬라이스, 맵, 심지어 함수 등의 간단한 타입에도 부가적인 동작을 정의할 수 있다. (마치 Kotlin의 확장함수 같다.)

  • 메소드가 *T 타입을 필요로 하지만 현재 타입이 T인 경우라도 T.Method()를 호출하면 컴파일러가 묵시적으로 변수를 &T로 변경해준다. (와우!)

  • 내부적으로 익명 필드의 타입은 명명 타입의 포인터가 될 수 있으며, 이때 필드와 메소드는 간접적으로 참조된 객체로 승격된다.

예를들어 아래와 같은 코드를 보자.


type Point struct{ X, Y float64 }

type ColoredPoint struct {
    Point
    Color color.RGBA
}

func (p Point) Distance(q Point) float64 {
    return math.Hypot(q.X-p.X, q.Y-p.Y)
}

이때 ColoredPoint는 Point를 갖고(has-a) 있기 때문에 Point의 메소드인 Distance를 승격한 메소드를 갖게 되고 이는 아래와 같다.

func (p ColoredPoint) Distance(q Point) float64 {
    return p.Point.Distance(q)
}

이러한 래퍼 메소드는 컴파일러가 생성해주기 때문에 우리는 ColoredPoint에서 Point의 메소드를 사용할 수 있다.

이는 마치 OOP에서의 상속과 유사하지만 기본적으로 has-a로 상속을 흉내낼 수 있다.

사실 자바의 디자인 패턴에서도 상속보다는 Composite(has-a)를 더 선호할 정도이니 Go에서 상속이라는 기능을 제공하지 않으면서 내부적으로 상속과 유사한 기능을 제공하는 이유도 납득이 된다.

Reference

The Go Programming Language - 앨런 도노반, 브라이언 커니건 지음, 이승 옮김, 공용준 감수

https://velog.io/@kimmachinegun/Go-Slice-%EC%A7%91%EC%A4%91-%ED%83%90%EA%B5%AC-t2jn1kd1gc



© 2022. by minkuk

Powered by minkuk