Go 프로그래밍 언어 둘러보기에 오신 것을 환영합니다.
이 둘러보기는 세 개의 섹션으로 나뉘어져 있습니다. 각 섹션의 끝에는 여러분의 완벽한 이해를 돕기 위한 연습문제들이 있습니다.
이 둘러보기는 인터랙티브한 방법을 사용합니다. 원격 서버여러분의 컴퓨터에서 이 프로그램을 컴파일하고 실행해보기 위해 지금 바로 RUN 버튼을 클릭해보세요. (또는 Shift-Enter를 눌러보세요) 결과가 소스 코드 아래에 표시됩니다.
이 둘러보기에 있는 예제 프로그램들은 Go의 다양한 측면을 보여줍니다. 여기에 있는 프로그램들은 여러분들이 Go를 실험해보기 위한 시작점이 될 것입니다.
이 프로그램을 수정해서 다시 실행해보세요.
다음으로 이동할 준비가 되었으면, NEXT 버튼을 클릭하거나 PageDown 키를 누르세요.
이 둘러보기는 또한 여러분이 인터넷 접속없이 스탠드얼론(stand-alone) 프로그램으로도 사용할 수 있도록 되어 있습니다.
스탠드얼론 둘러보기는 여러분의 컴퓨터에서 샘플 코드를 빌드하고 실행하므로 좀 더 빠릅니다. 또, 온라인 버전에서는 이용할 수 없는 추가적인 연습문제들을 포함하고 있습니다.
둘러보기를 로컬에서 실행해보려면 먼저 Go를 설치 (마지막 stable 릴리즈는 release.r60.1)하고, gotour를 설치하기 위해 goinstall을 사용합니다:
goinstall go-tour.googlecode.com/hg/gotour
그리고 그 결과로 나온 gotour 파일을 실행합니다.
(역주: 위 goinstall을 실행하고 나면 $GOROOT/bin/gotour 실행파일이 생김)
위처럼 하지 않고 계속 진행하려면, NEXT 버튼을 클릭하거나 PageDown 키를 누르세요.
(INDEX 버튼을 클릭해서 언제든 다시 이 gotour 설치 순서를 보러 되돌아올 수 있습니다.)
모든 Go 프로그램은 패키지들로 구성되어 있습니다.
프로그램들은 main 패키지 안에서 실행을 시작합니다.
이 프로그램은 "fmt"와 "math" 패키지들을 임포트해서 사용합니다.
관례적으로, 패키지 이름은 임포트 경로의 마지막 요소와 같습니다.
(역주: 패키지 경로는 $GOROOT/pkg/{$GOOS_$GOARCH} 가 됩니다. 즉, linux에 386 머신이라면 $GOROOT/pkg/linux_386/ 아래에 패키지 파일들이 있습니다.)
이 코드에서는 임포트할 패키지들을 괄호안에 그룹화했습니다. 이런 것을 "인자화된"(factored) 임포트 문이라고 합니다. 여러분은 물론 각 임포트 문장을 따로 써도 됩니다. 아래처럼요:
import "fmt"
import "math"
하지만 어수선함을 제거하기 위해서 임포트할 패키지를 묶어서 사용하는 것이 일반적입니다.
패키지를 임포트한 후에, 그 패키지가 외부로 드러낸 이름들을 참조할 수 있습니다.
Go에서는 변수나 함수의 첫 글자가 대문자로 시작하면 그 이름이 외부에서 참조할 수 있는 이름이 됩니다.
Pi는 외부로 드러난 이름이지만, pi는 그렇지 않습니다.
이 코드를 실행해보세요. 그리고 math.pi를 math.Pi로 고치고
다시 실행해보세요.
함수는 0개 또는 그 이상의 인자들을 받을 수 있습니다.
이 예제에서는, add 함수는 두 개의 int형 파라미터를 받습니다.
타입이 변수명 다음에 온다는 것에 주의하세요.
(왜 타입들을 이렇게 선언하는지 더 자세한 내용을 보려면, 이 블로그 포스트를 참고하세요.)
두 개 이상의 함수 파라미터가 같은 타입을 가지고 있을 때, 마지막 타입은 놔두고 나머지 타입들은 생략할 수 있습니다.
이 예제에서, 아래처럼 되어 있는 것을
x int, y int
아래처럼 줄일 수 있습니다.
x, y int
한 함수는 여러 개의 결과를 리턴할 수 있습니다.
swap 함수는 두 개의 string을 리턴합니다.
함수는 파라미터를 가질 수 있습니다; Go에서는 함수가 리턴할 결과에 이름을 미리 지어줄 수가 있고, 그 걸 변수처럼 사용할 수 있습니다; 이런 것을 리절트 파라미터(result parameters)라고 부릅니다.
만일 이 리절트 파라미터가 이름지어져 있다면, 인자없는 return 문은 그 결과의 현재 값들을 리턴합니다.
역주: x, y라는 이름을 가진 int 타입 두 개를 리턴할 것이라고 하면 그 함수내에서 x, y를 변수처럼 사용하고 리턴할 때는 그냥 return만 해주면 x, y가 리턴됩니다. 물론 그 함수의 결과값을 받을 때는 꼭 x, y로 받지 않아도 됩니다.
var 문은 변수들의 리스트를 선언합니다.
함수 인자 리스트에서처럼 타입은 마지막에 적습니다.
var 선언은 변수 당 하나의 값을 초기값으로 줄 수 있습니다.
만일 초기값이 지정되어 있으면 var의 변수 타입은 생략이 가능합니다; 이 때, 그 변수는 초기값의 타입을 (추론에 의해) 가지게 됩니다.
함수 내에서, var 선언 대신에 축약된 할당문인
:=를 사용할 수 있습니다.
(함수 밖에서, 모든 구조는 키워드로 시작되고 := 구조는
사용할 수 없습니다.)
상수는 변수처럼 선언되지만, const 키워드를 사용해서 선언합니다.
상수는 string, boolean, 숫자 값(numeric values)만 될 수 있습니다.
숫자 상수는 고정밀도(high-precision)의 값입니다.
타입이 없는 상수(untyped constant)는 문맥에 의해 필요한 타입을 가지게 됩니다.
needInt(Big)도 출력해보세요.
Go는 단지 하나의 루프문을 가지고 있는데, 바로 for 루프입니다.
기본적인 for 루프는 C나 Java와 같지만, 괄호 ( )가
없고(이건 옵션도 아닙니다) 중괄호 { }는 필수입니다.
C나 Java에서처럼, 조건의 앞과 뒤를 비워둘 수 있습니다.
세미콜론을 뺄 수도 있습니다:
이 경우 C의 while처럼 Go에서 for를 쓸 수 있습니다.
만일 루프 조건을 생략한다면, 무한루프가 됩니다.
세미콜론들을 완전히 생략하면, 역시 무한루프 표현이 됩니다.
if 문은 C나 Java와 같지만, 괄호 ( )가
없고(이건 옵션도 아닙니다) 중괄호 { }는 필수입니다.
(어디서 많이 들어본 소리 같죠?)
for에서처럼, if 문은 조건 전에 실행할 짧은 문장과
함께 시작할 수 있습니다.
이 문장에서 선언된 변수는 단지 if의 안에서만 사용할 수 있는 범위를
가지게 됩니다.
(마지막 return 문 전에 v를 한 번 사용해보세요.)
if의 짧은 문장 안에서 선언된 변수들은 또한 else 블록
안에서도 사용 가능합니다.
Go의 기본 타입들입니다.
bool string int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr float32 float64 complex64 complex128
struct는 필드들의 모음입니다.
(그리고 type 선언은 여러분이 예상하고 있는 것입니다.)
struct의 필드들은 점(.)을 사용해서 접근합니다.
Go는 포인터는 있지만, 포인터 연산은 없습니다.
struct 필드들은 struct 포인터를 통해 접근할 수 있습니다. 이 포인터를 통한 간접지정은 명백합니다.
struct 리터럴은 struct 내 필드들의 값을 기재해서 새롭게 할당된 struct를 의미합니다.
Name: 문법을 사용해서 필드들 중 일부만 기재할 수 있습니다.
(이렇게 이름 지어진 필드들의 순서는 상관이 없습니다.)
특별한 접두사 & 는 struct 리터럴에 대한 포인터를 만듭니다.
new function
new(T) 라는 표현은 제로화된(zeroed) T 값을
메모리에 할당하고 그것에 대한 포인터를 리턴합니다.
var t *T = new(T)
또는
t := new(T)
map은 키와 값을 연관시킵니다. (역주: Python의 Dictionary와 같은)
map은 사용하기 전에 make로 만들어져야 합니다. (new가 아니라);
nil map은 비어있는 것이고 할당할 수 없습니다.
map 리터럴은 struct 리터럴과 같습니다만, 키가 필요합니다.
만일 최상위 레벨 타입이 타입명이라면, 리터럴의 요소에서 그 부분을 생략할 수 있습니다.
slice는 값들의 배열을 가리키고 또한 길이를 포함하고 있습니다.
[]T 는 T 타입의 원소들을 가지는 slice 입니다.
slice는 같은 array를 가리키는 새로운 slice를 만듬으로써 다시 slice화 될 수 있습니다.
아래 표현은
s[lo:hi]
lo에서 hi-1까지의 요소들 중 일부분으로 slice를 만듭니다. 따라서
s[lo:lo]
이건 비어있는 slice고
s[lo:lo+1]
이건 하나의 요소를 가진 slice가 됩니다.
slice는 make 함수로 만듭니다. 이는 제로화된(zeroed) array를
메모리에 할당하고 그 array를 참조하는 slice를 리턴합니다:
a := make([]int, 5) // len(a)=5
slice는 length와 capacity를 가지고 있습니다.
capacity는 slice가 늘어날 수 있는 최대 length 입니다.
capacity를 지정하기 위해서는 make 시 3 번째 인자를 넘겨주면 됩니다:
b := make([]int, 0, 5)
// len(b)=0, cap(b)=5
slice는 re-slicing 될 수 있습니다 (그 slice의 capacity 한도까지)
b = b[:cap(b)] // len(b)=5, cap(b)=5 b = b[1:] // len(b)=4, cap(b)=4
slice의 zero value는 nil 입니다.
nil slice는 0의 length, capacity를 가지게 됩니다.
좀 더 자세한 내용은 "Go Slices: usage and internals" 이 포스트를 보세요.
함수 역시 값입니다.
함수는 완전한 클로저(closures) 입니다.
adder 함수는 클로저를 리턴합니다.
각각의 클로저는 자신의 sum 변수에 바인딩 됩니다.
for 루프에서 range 는
slice나 map에서 값을 하나씩 꺼내오면서 반복적으로 루프돌 수 있게 합니다.
_ 에 사용하지 않을 key나 value를 대입할 수 있습니다.
만약에 이 예제에서 value가 아닌 slice의 index만 얻고자 한다면,
“, value” 부분을 제거하면 됩니다.
여러분은 아마 switch가 어떤 것인지는 알 것입니다.
좀 다른 점은 case 에서 fallthrough 문을 끝에 써주지 않는다면
그 case는 자동으로 break 됩니다.
switch case는 위에서부터 아래로 case를 확인하는데, case 조건이 맞으면 멈추게 됩니다.
(예로,
switch i {
case 0:
case f():
}
만일 i == 0 이면, f 함수를 호출하지 않습니다.)
조건이 없는 switch는 switch true 와 같습니다.
함수와 루프를 가지고 놀아볼 간단한 방법으로, 뉴턴의 메서드를 이용한 제곱근을 구현해 보세요.
이 경우에, 뉴턴의 메서드는 시작점 z를 고른 다음 그것을 반복함으로써
Sqrt(x) 근사값을 구하는 것입니다.
처음에는 단지 저 공식을 10번 반복하도록 루프를 만드세요. 그리고 x를 1, 2, 3 등 다양한 값을 줬을 때 나오는 z 값이 얼마나 정답에 가까운지 보세요.
다음으로, 루프를 돌 때 바로 직전에 구한 z 값이 더이상 변하지 않고 고정될 때 루프를 멈추도록 수정해보세요. 루프를 더 많이 혹은 적게 돌았는지 보세요. (또는 아주 작은 값만 변하게 될 때) math.Sqrt와 얼마나 가깝게 답이 나왔나요?
힌트: 부동소수점을 선언하고 값을 초기화하기 위해서는 부동소수점 문법을 쓰든지 아니면 형변환을 사용하세요.
z := float64(0) z := 0.0
WordCount를 구현해봅시다. 이 함수는 string s안에
각 단어(“word”)의 수를 map으로 리턴해야 합니다.
wc.Test 함수는 인자로 제공된 함수에 대해 테스트 수트를 실행하고
성공인지 실패인지를 출력합니다.
strings.Fields 여기에서 도움이 될만한 것을 찾을 수 있을 것입니다.
Pic을 구현해봅시다. Pic 함수는 dy 크기의 slice를 리턴해야 합니다.
dy의 각 원소는 8비트 부호없는 정수의 dx slice 입니다.
이 프로그램을 실행했을 때, 흑백의 값들로 정수를 해석한 여러분의 그림을 표시하게 될 겁니다.
이미지는 여러분이 선택하는 것입니다.
흥미로운 함수들은 x^y, (x+y)/2, x*y 를 포함합니다.
([][]uint8 내에서 각 []uint8를 할당하기 위해 루프를 사용해야 합니다.)
함수로 재미있는 것을 좀 해봅시다.
함수(클로저)를 리턴하는 피보나치(fibonacci) 함수를 구현해보세요.
리턴된 그 함수는 연속된 피보나치 수를 리턴하는 함수입니다.
complex64와 complex128 타입을 통한 Go의 복소수에 대한
지원을 살펴봅시다.
세제곱을 위해 뉴턴의 메서드는 반복하는 것이 됩니다.
2의 세제곱근을 찾아서, 알고리즘이 제대로 동작하는지 확인해보세요. cmath.Pow 함수가 있습니다.
Go에는 클래스가 없습니다. 하지만, struct 타입에 메서드를 정의할 수 있습니다.
메서드 리시버는 func 키워드와 메서드명 사이에 적습니다.
사실, 단지 struct 뿐만 아니라 여러분이 만든 패키지 안에 직접 정의한 어떤 타입에라도 메서드를 정의할 수 있습니다.
하지만, 다른 패키지로부터의 타입이나 기본 타입에는 메서드를 정의할 수 없습니다.
메서드들은 명명된 타입(named type)이나 명명된 타입에 대한 포인터에 연관되어질 수 있습니다.
우리는 방금 두 개의 Abs 메서드들을 보았습니다.
하나는 *Vertex 포인터 타입에 있는 것, 또 하나는
MyFloat 타입에 있는 것이죠.
포인터 리시버를 사용하는 이유는 두 가지가 있습니다. 첫째는, 각 메서드 호출때마다 리시버의 값이 복사되는 것을 피하기 위해서 입니다. (만일 그 값의 타입이 아주 큰 struct라면 더 효과적) 두번째는, 그럼으로써 메서드는 그 리시버가 가리키고 있는 값을 변경할 수 있게 됩니다.
리시버로 *Vertex 대신에 Vertex를 사용하도록
Abs와 Scale 메서드 선언을 고쳐보세요.
p가 Vertex 일 때, Scale 메서드는
아무런 영향을 안미칩니다. Scale은 p를 변형시킵니다.
p가 값(포인터가 아닌)일 때, 이 메서드는 Vertex의
복사된 값을 가지게 되고 원래의 값을 변형시킬 수 없습니다.
Abs 역시 마찬가지입니다. 단지 p를 읽어들일 뿐입니다.
원래의 값(포인터를 통해)을 읽어들이든지 복사본을 읽어들이든지는 문제가 되지 않습니다.
interface 타입은 메서드들의 집합에 의해 정의됩니다.
interface 타입의 값은 그 interface의 메서드들을 구현한 어떠한 값이라도 담을 수 있습니다.
한 타입은 메서드를 구현하기만 함으로써 어떤 인터페이스를 구현했다고 할 수 있습니다.
명시적으로 인터페이스를 구현한다는 선언은 없습니다.
이런 암묵적인 인터페이스는 인터페이스가 정의되어 있는 패키지와 구현된 패키지를 분리할 수 있게 해줍니다: 아무 곳에도 다른 곳에 의존된 곳은 없습니다.
이런 방식은 개발자가 꼼꼼한 인터페이스 정의를 하도록 합니다. 왜냐하면 개발자가 모든 인터페이스 구현된 것을 찾을 필요도 없고, 인터페이스를 구현할 때 인터페이스 이름을 표시할 필요도 없기 때문입니다.
Package io는 Reader와 Writer
를 정하고 있습니다; 여러분이 할 필요가 없는.
에러는 그 자신을 설명할 수 있는 그 무엇 입니다:
package os
type Error interface {
String() string
}
Package http는 http.Handler를
구현한 어떤 타입을 사용해서 HTTP 요청 처리하는 방법을 제공합니다.
package http
type Handler interface {
ServeHTTP(w ResponseWriter,
r *Request)
}
이 예제에서, MyHandler 타입은 http.Handler를 구현합니다.
인사말을 보려면 http://localhost:4000/ 여기를 방문해보세요. 주의: 이 예제는 웹 기반의 둘러보기에서는 실행되지 않습니다. 웹 서버 작성을 직접 해보기 위해 Go 설치 이 문서가 필요할 겁니다.
Package image는 Image
인터페이스를 정의하고 있습니다:
package image
type Image interface {
ColorModel() ColorModel
Bounds() Rectangle
At(x, y int) Color
}
(자세한 것은 이 문서를 보세요.)
Color와 ColorModel 역시 인터페이스입니다. 하지만,
우리는 미리 정의된 구현 image.RGBAColor와 image.RGBAColorModel를
사용해서 이 부분을 무시할 것입니다.
앞선 연습문제에서 여러분이 작성했던 Sqrt 함수를 복사해와서
이 함수가 os.Error 값을 리턴하도록 수정해보세요.
Sqrt는 음수가 주어졌을 때, 제곱근 구하는 것은 복소수를 지원하지 않으므로
nil이 아닌 값을 리턴해야 합니다.
새로운 타입을 만드세요.
type ErrNegativeSqrt float64
그리고 이 것을 하나의 아래 메서드를 줘서 os.Error로 만드세요.
func (e ErrNegativeSqrt) String() string
ErrNegativeSqrt(-2).String() 이렇게 하면
"cannot Sqrt negative number: -2" 이런 메시지가 리턴되도록 String() 메서드를 만드세요.
주의: String 메서드 안에서 fmt.Print(e)를 호출하는 것은
프로그램을 무한루프에 빠지게 할 겁니다.
이 것을 피하기 위해 여러분은 첫번째로 e를 형변환 할 수 있습니다.
fmt.Print(float64(e)). 왜 그럴까요?
Sqrt 함수에 음수가 주어졌을 때, ErrNegativeSqrt 값을
리턴하도록 수정하세요.
아래 타입들을 구현해보세요. 그리고 그 타입에 ServeHTTP 메서드를 정의해보세요. 여러분의 웹 서버로 들어오는 특정 경로를 처리할 수 있도록 등록해보세요.
type String string
type Struct struct {
Greeting string
Punct string
Who string
}
예로, 아래와 같은 핸들러를 등록할 수 있도록 해야 합니다.
http.Handle("/string", String("I'm a frayed knot."))
http.Handle("/struct", &Struct{"Hello", ":", "Gophers!"})
앞서 여러분이 만든 그림 제너레이터를 기억하시나요?
새로운 걸 하나 만들어 봅시다. 하지만 이번에는 데이터의 slice 대신에
image.Image의 구현을 리턴하도록 할 것입니다.
여러분만의 Image 타입을 정의하고,
the necessary methods
를 구현한 다음, pic.ShowImage을 호출하세요.
Bounds는 image.Rect(0, 0, w, h) 처럼
image.Rectangle를 리턴해야 합니다.
ColorModel은 image.RGBAColorModel을 리턴해야 합니다.
At 하나의 컬러를 리턴해야 합니다.
마지막 그림 제너레이터의 v 값은 image.RGBAColor{v, v, 255, 255} 의 것과
일치합니다.
흔한 패턴 중 하나는 io.Reader 입니다.
이건 어떤 경우에는 스트림을 바꾸기도 하는 또다른 io.Reader를 감싸고 있습니다.
예로,
gzip.NewReader는
io.Reader(gzip된 데이터의 스트림)를 인자로 받고, *gzip.Decompressor를
리턴합니다. *gzip.Decompressor은 또한 io.Reader(압축 해제된 데이터의 스트림)을
구현하고 있습니다.
io.Reader를 구현한 rot13Reader를 만들어보세요.
rot13Reader은 ROT13가
적용되어 스트림을 변경하는 io.Reader로부터 읽어들여야 합니다.
ROT13은 모든 알파벳 문자를 13자리씩 치환해서 암호화하는 방법입니다.
rot13Reader 타입은 소스에 나와 있고, 이걸 Read 메서드를 구현한
io.Reader로 만들어보세요.
goroutine은 Go 런타임에서 관리되는 경량의 쓰레드입니다.
go f(x, y, z)
위 문장은 함수 f를 새로운 goroutine으로 실행합니다.
f(x, y, z)
f, x, y, z의 evaluation은
현재의 goroutine 안에서 일어나고, f의 실행은 새로운 goroutine 안에서 일어납니다.
goroutine들은 같은 어드레스 공간에서 실행되기 때문에 공유 메모리는 동기화 되어야 합니다.
sync 패키지는
다른 원시 타입들보다는 많이 쓰이지 않겠지만 유용한 방법을 제공합니다.
(다음 슬라이드를 보세요.)
채널(channel)은 채널 오퍼레이터인 <- 를 통해 값을 보내고 받을 수 있는 타입화된 도관과 같은 것입니다.
ch <- v // v를 channel ch로 보냄. v := <-ch // ch로부터 받아서 그 값을 v로 할당.
(데이터는 "화살표"의 방향으로 흐릅니다.)
map과 slice처럼 channel도 사용하기 전에 생성되어 있어야 합니다.
ch := make(chan int)
기본적으로 보내고 받은 것은 다른 한 쪽이 준비가 되기 전까지는 블럭됩니다. 그래서 명시적인 lock이나 조건 변수 없이도 goroutine이 동기화되도록 해줍니다.
channel은 buffered 될 수 있습니다. buffered channel을 초기화를 위해 make의
두 번째 인자로 버퍼 크기를 제공하면 됩니다.
ch := make(chan int, 100)
buffered channel로 데이터를 보낼 때는 단지 그 버퍼가 꽉 찼을 때만 블럭됩니다. 받을 때는 버퍼가 비어있을 때 블럭됩니다.
버퍼가 넘치도록 예제를 수정해보고 무슨 일이 일어나는지 보세요.
보내는 쪽에서는 더 이상 보낼 값이 없다는 걸 가리키기 위해 channel을
close 할 수 있습니다.
받는 쪽에서는 channel로부터 값을 받는 표현에 두 번째 파라미터를 줌으로써
그 channel이 닫혔는지 확인해볼 수 있습니다.
v, ok := <-ch
만약 더 이상 받을 값이 없고 channel이 닫혀있다면 ok는 false가 됩니다.
for i := range c 이렇게 하면 channel이 닫힐 때까지 루프를 돌며 반복적으로
그 channel로부터 값을 받아오게 됩니다.
주의: 단지 보내는 쪽(sender)에서만 channel을 close 할 수 있습니다. 절대 받는 쪽(receiver)에서는 close 할 수 없습니다. 닫혀 있는 channel로 데이터를 보내는 것은 panic을 일으킬 것입니다.
주의 하나 더: channel은 파일과 같지 않습니다; 보통은 channel을 파일 close 처럼 close 할 필요가 없습니다. channel을 close 하는 것은 receiver에서 더 이상 받을 값이 없다고 알게 할 때만 필요합니다.
select 문은 복수 개의 통신 작업에서 goroutine이 기다도록 합니다.
select의 case들 중 하나가 실행할 수 있을 때까지 select는 블럭합니다.
그리고 case에 조건이 맞았을 때 case 안을 실행합니다. case가 복수 개가 만족되어 있다면
무작위로 하나를 선택해서 실행합니다.
select 안에서 만족하는 case가 없다면 default가 실행됩니다.
블럭되는 일 없이 보내기, 받기를 하려면 default를 사용하세요.
select {
case i := <-c:
// use i
default:
// receiving from c would block
}
주의: 이 예제는 웹 기반의 둘러보기에서는 실행되지 않습니다. 왜냐하면 샌드박스 환경에서는 time의 개념이 없기 때문입니다. 이 예제를 실행해보기 위해 Go 설치 문서가 필요할 것입니다.
이진 트리에서는 같은 순서를 가진 값들을 가지고도 잎(노드, leaves)에
저장하는 방식에 따라 많은 이진 트리 종류들이 있습니다.
예로, 아래에 1, 1, 2, 3, 5, 8, 13 순서를 저장하는 두 종류의 이진 트리가 있습니다.
두 개의 이진 트리가 같은 순서로 저장을 했는지 체크하는 함수는 대부분의 프로그래밍 언어에서는 아주 복잡한 구현입니다. 우리는 간단한 해결책을 만들어보기 위해 Go의 병행성(concurrency)과 channel을 사용할 것입니다.
이 예제는 tree 패키지를 사용하는데, 여기에는 아래와 같은 타입을 정의하고 있습니다.
type Tree struct {
Left *Tree
Value int
Right *Tree
}
1. Walk 함수를 구현해보세요.
2. Walk 함수를 테스트해보세요.
tree.New(k) 함수는 값 k, 2k, 3k, ...,
10k를 무작위의 구조화된 이진 트리로 만듭니다.
새 채널 ch를 만들고 Walk를 시작해보세요:
go Walk(tree.New(1), ch)
이 채널로부터 값을 읽고 10 개의 값을 출력해보세요. 결과는 숫자 1, 2, 3, ..., 10 이 되어야 합니다.
3. t1과 t2가 같은 값들을 저장하고 있는지 판단하기 위해
Walk를 사용해서 Same 함수를 구현해보세요.
4. Same 함수를 테스트해보세요.
Same(tree.New(1), tree.New(1)) 은 true를 리턴해야 합니다.
Same(tree.New(1), tree.New(2)) 은 false를 리턴해야 합니다.
이번 예제에서는 웹 크롤러를 병렬화하기 위한 Go의 concurrency 기능을 사용할 것입니다.
같은 URL을 두 번 가져오지 않도록 병렬적으로 URL 들을 가져오는
Crawl 함수를 수정해보세요.
여러분은 Go 설치나 Go App Engine SDK를 다운로드 받는 것으로부터 시작할 수 있습니다.
일단 Go를 여러분의 컴퓨터에 설치하거나 Go App Engine SDK를 다운로드 받았다면, Go Documentation은 좋은 시작점입니다. 여기에는 레퍼런스들, 튜토리얼, 비디오, 그리고 많은 것들이 포함되어 있습니다.
만일 표준 라이브러리에 대한 도움이 필요하면, package reference를 보세요. Go 언어 자체에 대한 도움을 위해서는, Language Spec이 꽤 읽을만 할 겁니다.
웹 애플리케이션 작성하는 것에 관심이 있다면, Wiki Codelab을 보세요.
Go의 concurrency 모델을 좀 더 살펴보고 싶다면, Share Memory by Communicating를 보세요.
First Class Functions in Go는 Go의 함수 타입에 대한 흥미로운 관점을 줄 것입니다.
Go Blog에는 Go에 대한 유용한 글이 많이 있습니다.
좀 더 알고 싶으시면 golang.org를 방문해보세요.
Go 언어 한국 커뮤니티도 방문해보세요.
번역: 김종민 (Google Plus | Twitter): 잘못된 부분이 있으면 언제든 편하게 알려주세요.