The Go Programming Language 3~6장
in Programming Language on Go
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의
map
은 HashTable의 참조이다.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