안녕하세요. 개발자 모도리입니다.
The Go Programming Language 라는 책으로 Go를 공부하고 있으며, 해당 책의 내용을 요약 정리해서 올리려고 합니다. 저는 번역본을 구매해서 공부하고 있습니다.
게시물에 예제코드 라고 나오는 것들은 https://github.com/modolee/tgpl.git 에서 다운 받으실 수 있습니다.
지난 게시물
- [Go] Mac에서 Atom으로 Go 개발 환경 구축하기
- [The Go Programming Language] 1장 튜토리얼 - 1.1 Hello, World
- [The Go Programming Language] 2장 프로그램 구조 - 2.1 이름
- [The Go Programming Language] 2장 프로그램 구조 - 2.2 선언
- [The Go Programming Language] 2장 프로그램 구조 - 2.3 변수
- [The Go Programming Language] 2장 프로그램 구조 - 2.4 할당
- [The Go Programming Language] 2장 프로그램 구조 - 2.5 타입 선언
2장 프로그램 구조
2.6 패키지와 파일
패키지
- Go의 패키지는 다른 언어의 라이브러리나 모듈과 마찬가지로 모듈화, 캡슐화, 분할 컴파일 및 재사용 등을 지원합니다.
- 패키지의 소스코드는 하나 이상의 .go 로 끝나는 파일 내에 있으며, 일반적으로 임포트 경로의 마지막 이름과 같은 디렉토리 안에 있습니다.
예시
tgpl/ch1/helloworld
패키지 파일들은$GOPATH/src/tgpl/ch1/hellworld
디렉토리에 저장됩니다.
- 패키지는 또한 패키지 외부에서 보이거나 익스포트되는 이름을 제어해 정보를 숨길 수 있게 합니다.
- Go에서는 식별자를 대문자로 시작하는 것으로 익스포트되는지 여부가 결정됩니다.
패키지 작성 예제
2.5에서 작성했던 패키지를 변형해서 tgpl/ch2/tempconv라는 패키지를 만들어 봅니다.
패키지에서 개별 파일의 선언에 접근하는 방법을 보여주기 위해 패키지 자체는 두 개의 파일에 저장돼 있습니다.
실제로는 이런 작성 패키지는 파일 하나면 충분합니다.
타입, 상수, 메소드 선언들을 tempconv.go에 넣습니다.
//tempconv 패키지는 섭씨와 화씨 변화를 수행합니다.
package tempconv
import "fmt"
type Celsius float64
type Fahrenheit float64
const (
AbsoluteZeroC Celsius = -273.15
FreezingC Celsius = 0
BoilingC Celsius = 100
)
func (c Celsius) String() string { return fmt.Sprintf("%gºC", c) }
func (f Fahrenheit) String() string { return fmt.Sprintf("%gºF", c) }
예제코드 [ch2/tempconv/tempconv.go]
- 변환 함수들은 conv.go에 넣습니다.
package tempconv
// CToF는 섭씨온도를 화씨온도로 변환합니다.
func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }
// FToC는 화씨온도를 섭씨온도로 변환합니다.
func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }
예제코드 [ch2/tempconv/conv.go]
- 각 파일은 패키지명을 정의하는 package 선언으로 시작합니다.
- 패키지가 임포트되면 패키지 멤버는 tempconv.CToF 등으로 참조됩니다.
- 패키지 수준의 이름은 모든 소스가 하나의 파일에 있을 때와 마찬가지로 패키지 내의 다른 모든 파일에서 볼 수 있습니다.
- 패키지 수준의 const 이름들이 대문자로 시작하므로 이들도 tempconv.AbsoulteZeroC와 같은 적합한 이름으로 접근할 수 있습니다.
fmt.Printf("Brrrr! %v\n", tempconv.AbsoulteZeroC) // "Brrrr! -273.15ºC"
2.6.1 임포트
- Go 프로그램 안의 모든 패키지는 임포트 경로라는 고유한 문자열로 식별됩니다.
- Go 도구를 사용 할 때 임포트 경로는 패키지를 구성하는 하나 이상의 Go 소스 파일을 포함한 디렉토리를 의미합니다.
사용자 패키지 임포트 사용 예제
//Cf는 숫자 인수를 섭씨와 화씨로 변환합니다.
package main
import (
"fmt"
"os"
"strconv"
"tgpl/ch2/tempconv"
)
func main() {
for _, arg := range os.Args[1:] {
t, err := strconv.ParseFloat(arg, 64)
if err != nil {
fmt.Fprintf(os.Stderr, "cf: %v\n", err)
os.Exit(1)
}
f := tempconv.Fahrenheit(t)
c := tempconv.Celsius(t)
fmt.Printf("%s = %s, %s = %s\n", f, tempconv.FToC(f), c, tempconv.CToF(c))
}
}
예제코드 [tgpl/ch2/cf/main.go]
실행결과
$ go build tgpl/ch2/cf
$ ./cf 32
32ºF = 0ºC, 32ºC = 89.6ºF
$ ./cf 212
212ºF = 100ºC, 212ºC = 413.6ºF
$ ./cf -40
-40ºF = -40ºC, -40ºC = -40ºF
2.6.2 패키지 초기화
- 패키지 초기화는 시작 시 패키지 수준 변수를 선언된 순서대로 초기화화며, 의존성이 있을 때는 의존하는 변수부터 초기화합니다.
var a = b + c // a가 3으로 세 번째로 초기화 됨
var b = f() // b는 f를 호출함으로써 두 번째로 초기화 됨
var c = 1 // c가 먼저 1로 초기화 됨
func f() int { return c + 1 }
- init 함수를 사용하면 초기화를 더 간단하게 할 수 있습니다.
func init() { /* ... */ }
- init 함수는 호출하거나 참조할 수 없다는 것 외에는 일반 함수와 같습니다.
- 프로그램이 시작할 때는 각 파일 내의 init 함수들이 선언된 순서대로 자동으로 실행됩니다.
- 한 패키지는 프로그램 안에서 의존하는 패키지들이 초기화된 후 임포트 순서에 따라 초기화 되며, 따라서
q
를 임포트하는p
패키지에서는p
의 초기화 과정이 시작되기 전에q
의 초기화가 완료된다는 것을 확신할 수 있습니다.
예제 : PopCount
- uint64 안의 값이 1로 지정된 비트 개수를 반환하며, 이 값을 인구수라고 합니다.
- 8비트 값을 미리 계산해 PopCount 함수에서 64번의 조회 대신 8개 테이블만 조회해 그 합을 반환하게 합니다.
package popcount
// pc[i]은 i의 인구수입니다.
var pc [256]byte
func init() {
for i := range pc {
pc[i] = pc[i/2] + byte(i&1)
}
}
// PopCount는 x의 인구수(1로 지정된 비트 수)를 반환합니다.
func PopCount(x uint64) int {
return int(pc[byte(x>>(0*8))] +
pc[byte(x>>(1*8))] +
pc[byte(x>>(2*8))] +
pc[byte(x>>(3*8))] +
pc[byte(x>>(4*8))] +
pc[byte(x>>(5*8))] +
pc[byte(x>>(6*8))] +
pc[byte(x>>(7*8))])
}
예제코드 [tgpl/ch2/popcount/popcount.go]
package main
import (
"fmt"
"os"
"strconv"
"tgpl/ch2/popcount"
)
func main() {
for _, arg := range os.Args[1:] {
pc, err := strconv.ParseUint(arg, 10, 10)
if err != nil {
fmt.Fprintf(os.Stderr, "popcount: %v\n", err)
os.Exit(1)
}
fmt.Println(popcount.PopCount(pc))
}
}
예제코드 [tgpl/ch2/popcount_main.go]
- 256을 2진수로 표현하면 11111111 이고, 이것을 8bit 단위로 잘라서 카운팅할 수 있는 테이블을 init 함수에서 미리 만들어 놓았습니다. 그래서 빠르게(?) 1의 개수를 구할 수 있습니다.
실행결과
$ go run ch2/popcount_main.go 255
8