package main
import "fmt"
func fibonacci(c, quit chan int) {
var x, y int = 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
Go의 Select문은 고루틴이 다수의 통신 동작으로부터 수행 준비를 대기하게 해줌.
Select는 Case구문으로 받는 통신동작들 중 하나가 수행될 수 있을때 까지 대기함.
다수의 채널이 동시에 준비되면 그 중 하나를 무작위로 선택함.
32. 셀렉트의 디폴트 케이스
package main
import (
"fmt"
"time"
)
func main() {
tick := time.Tick(1e6)
boom := time.After(5e8)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
return
default:
fmt.Println(" .")
time.Sleep(5e7)
}
}
}
package main
import (
"code.google.com/p/go-tour/tree"
"fmt"
)
// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
_walk(t, ch)
close(ch)
}
func _walk(t *tree.Tree, ch chan int) {
if t != nil {
_walk(t.Left, ch)
ch <- t.Value
_walk(t.Right, ch)
}
}
// Same determines whether the trees
// t1 and t2 contain the same values.
func Same(t1, t2 *tree.Tree) bool {
ch1 := make(chan int)
ch2 := make(chan int)
go Walk(t1, ch1)
go Walk(t2, ch2)
for i := range ch1 {
if i != <- ch2 {
return false
}
}
return true
}
func main() {
//tree.New(2)
ch := make(chan int)
go Walk(tree.New(1), ch)
for v := range ch {
fmt.Print(v)
}
fmt.Println(Same(tree.New(1), tree.New(1)))
fmt.Println(Same(tree.New(1), tree.New(2)))
}
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(1 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
/**** Output ****
hello
world
world
hello
world
hello
hello
world
hello
****************/
Goroutines은 런타임중에 관리되는 경략 쓰레드이다.
go f(a, b, c)와같이 사용되며, 새로운 고루틴을 실행한다.
고루틴은 동일한 주소공간에서 실행되므로, 공유되는 자원의 접근은 반드시 동기화되야한다.
29. 채널
채널은 채널 연산자 <-를 이용해 값을 주고 받을 수 있는, 타입이 존재하는 파이프이다.
package main
import "fmt"
func sum(a []int, c chan int) {
sum := 0
for _, v := range a {
sum += v
}
c <- sum // send sum to c
}
func main() {
a := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(a[:len(a)/2], c)
go sum(a[len(a)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y) // -5 17 12
}
ch <- v : v를 ch로 보낸다. v := <-ch : ch로부터 값을 받아서 v로 넘긴다.
맵이나 슬라이스처럼, 채널은 사용되기 전에 생성 되어야한다. ch := make(chan int)
송/수신은 상대편이 준비될때까지 대기한다.
채널은 버퍼링될 수 있다. make에서 두번째 인자로 버퍼의 용량을 지정해 줌으로써 해당 버퍼만큼 버퍼링되는 채널을 생성할 수 있다. ch := make(chan int, 100)
package main
import "fmt"
func main() {
c := make(chan int, 2)
c <- 1
c <- 2
fmt.Println(<-c)
fmt.Println(<-c)
}
만약 버퍼를 1을 잡게 된다면 에러가 발생하게 된다.
30. Range와 Close
데이터 송신측은 더 이상 보낼 값이 없음을 알리기 위해 채널을 close할 수 있다.
수신자는 아래와 같은 수신 코드의 두번째 인자를 줌으로써 채널이 닫혓는지 테스트 할 수 있다. v, ok := <- ch와 같이 사용하며, 만약 채널이 닫겨있다면 ok엔 false가 들어가게 된다.
또한 for i := range ch와 같은 반복문은 채널이 닫힐 때 까지 계속해서 값을 받는다.
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
주의할 점은 채널은 송신측만 닫을수 있으며, 수신측에선 닫을 수 없다.
채널은 파일과 다르기에 항상 닫을 필요가 없으며,
채널을 닫는 동작은 오로지 수신측에게 더이상 보낼값이 없다고 말해야 할 때만 행해지면 된다.
package image
type Image interface {
ColorModel() color.Model
Bounds() Rectangle
At(x, y int) color.Color
}
27. 연습문제
이전의 연습에서 당신이 작성한 그림 생성기를 기억하십니까? 다른 생성기를 만들어봅시다.
하지만 이번에는 데이터의 슬라이스 대신에 image.Image 의 구현체를 반환할 것입니다.
당신 자신의 Image 타입을 정의하시고, 필수 함수들 을 구현하신 다음, pic.ShowImage 를 호출하십시오. Bounds 는 image.Rect(0, 0, w, h) 와 같은 image.Rectangle 을 반환해야 합니다. ColorModel 은 color.RGBAModel 을 반환해야 합니다. At 은 하나의 컬러를 반환해야 합니다; 지난 그림 생성기에서 값 v 는 color.RGBA{v, v, 255, 255} 와 같습니다.
package main
import (
"code.google.com/p/go-tour/pic"
"image"
"image/color"
)
type Image struct{
w, h int
colorA, colorB uint8
}
func (i *Image) ColorModel() color.Model{
return color.RGBAModel
}
func (i *Image) Bounds() image.Rectangle {
return image.Rect(0,0,i.w,i.h)
}
func (i *Image) At(x, y int) color.Color{
return color.RGBA{uint8(x), uint8(y), i.colorB, i.colorA}
}
func main() {
m := &Image{123,123,131,141}
pic.ShowImage(m)
}
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := &Vertex{3, 4}
fmt.Println(v.Abs())
}
Go에는 클래스가 없지만 메소드를 구조체(struct)에 붙일 수 있다.
메소드 리시버(method receiver) 는 func 키워드와 메소드 이름 사이에 인자로 들어간다. func 구조체이름(혹은포인터) 메소드_리시버_이름 반환타입이고 위 코드를 통해 예시를 들면, func (v *Vertex) Abs() float64: 구조체 Vertex의 Abs()란 이름으로 호출 할 수 있다.
Go의 메소드는 구조체 뿐만아니라 모든 타입(type)에 붙일 수 있다.
하지만, 다른 패키지에 있는 타입이나 기본 타입들에 메소드를 붙이는 것은 불가능하다.
package main
import (
"fmt"
"math"
)
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
func main() {
f := MyFloat(-math.Sqrt2)
fmt.Println(f.Abs())
}
포인터 리시버는 메소드가 호출될때마다 값이 복사되는 것을막고, 메소드에서 리시버 포인터가 가르키는 값을 수정하기 위함이다.
(함수에서 포인터를 전달하는것과 값을 전달하는 것에 차이를 생각하면 이해가 쉽다..)
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := &Vertex{3, 4}
v.Scale(5)
fmt.Println(v, v.Abs()) // &{15 20} 25
}
22. 인터페이스
package main
import (
"fmt"
"math"
)
type Abser interface {
Abs() float64
}
func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}
a = f // a MyFloat implements Abser
a = &v // a *Vertex implements Abser
//a = v // a Vertex, does NOT
// implement Abser
fmt.Println(a.Abs()) // 5
}
/****** MyFloat ******/
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
/****** Vertex ******/
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
인터페이스는 메소드의 집합으로 정의된다.
인터페이스 안에 정의되어있는 모든 메소드가 구현되어있는 타입의 값은 모두 인터페이스 타입의 값이 될수 있다.
인터페이스의 메소드가 이미 정의 되어있는 패키지 혹은 타입에 대해서 따로 명시적으로 표현할 필요가 없다.
패키지 io에 Reader와 Writer가 정의되어 있기 때문에 따로 정의할 필요가없다.
package main
import (
"fmt"
"os"
)
type Reader interface {
Read(b []byte) (n int, err error)
}
type Writer interface {
Write(b []byte) (n int, err error)
}
type ReadWriter interface {
Reader
Writer
}
func main() {
var w ReadWriter
//var w Writer
// os.Stdout implements Writer
w = os.Stdout
fmt.Fprintf(w, "hello, writer\n")
}
22. 에러
package main
import (
"fmt"
"time"
)
type MyError struct {
When time.Time
What string
}
func (e *MyError) Error() string {
return fmt.Sprintf("at %v, %s",
e.When, e.What)
}
func run() error {
return &MyError{
time.Now(),
"it didn't work",
}
}
func main() {
if err := run(); err != nil {
fmt.Println(err) // at 2009-11-10 23:00:00 +0000 UTC m=+0.000000001, it didn't work
}
}
Go에선 문자열을 반환하는 하나의 메소드 Error()로 구성된 내장 인터페이스 타입 error를 사용한다. type error interface { Error() string }
기본적으로 모든 에러는 error interface를 호출하지만, 위 코드 처럼 직접 에러를 정의할 수 있다.
Sqrt 함수는 복소수를 지원하지 않기 때문에, 음수가 주어지면 nil 이 아닌 에러 값을 반환해야 합니다.
새로운 타입을 만드십시오.
type ErrNegativeSqrt float64
and make it an error by giving it a 그리고 아래 메소드를 구현함으로써 그 타입이 error 가 되게 하십시오.
func (e ErrNegativeSqrt) Error() string
이는 ErrNegativeSqrt(-2).Error() 가 "cannot Sqrt negative number: -2" 를 반환하는 그러한 메소드입니다.
package main
import (
"fmt"
"math"
)
type ErrNegativeSqrt float64
func (e ErrNegativeSqrt) Error() string {
if e < 0 {
return fmt.Sprintf("cannot Sqrt negative number: %0.1f", e)
}
return fmt.Sprintf("OK")
}
func Sqrt(f float64) (float64, error) {
if f > 0 {
var a, z, c float64 = 0, 1, 0
for a = 1; a < 10; a++ {
if math.Abs(z - c) > 1e-10 {
c = z
z = z - (z * z - f)/(2*z)
} else {
break
}
}
return z, ErrNegativeSqrt(f)
} else {
return f, ErrNegativeSqrt(f)
}
}
func main() {
fmt.Println(Sqrt(2))
fmt.Println(Sqrt(-2))
}
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("When's Saturday?")
today := time.Now().Weekday()
switch time.Saturday {
case today + 0:
fmt.Println("Today.")
case today + 1:
fmt.Println("Tomorrow.")
case today + 2:
fmt.Println("In two days.")
default:
fmt.Println("Too far away.")
}
}
Go의 Switch는 break문을 통해 빠져나오지 않고 조건이 일치하는 case문을 마치면 switch문을 종료하게된다.
위의 코드를 보면 만약 time.Saturday가 today + 0와 일치하면 today + 1과 today + 2, default문은 실행하지 않는다.
Go의 switch에선 조건을 생략할 수 있는데 이를 이용해 if-then-else문을 깔끔하게 작성할 수 있다.
switch옆에 조건이 없는 대신 case문에 조건문이 들어가게되며, 제일 위쪽부터 true인 조건의 case문을 실행한다.
package main
import (
"fmt"
"time"
)
func main() {
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
}
20. 연습문제
complex64 타입과 complex128 타입을 통해서 Go 언어의 복소수 지원 기능을 알아봅니다.
세제곱근을 얻기 위해서는, 뉴턴의 방법 (Newton’s method)을 적용하여 다음을 반복 수행합니다:
z = z - (z * z * z - x) / (3 * z * z)
package main
import "fmt"
func Cbrt(x complex128) complex128 {
var z complex128 = 1
for a := 1; a < 10; a++ {
z = z - (z * z * z - x) / (3 * z * z)
}
return z
}
func main() {
fmt.Println(Cbrt(3)) // (1.4422495703074083+0i)
}
package main
import "fmt"
type Vertex struct {
Lat, Long float64
}
var m = map[string]Vertex{
"Bell Labs": Vertex{
40.68433, -74.39967,
},
"Google": {
37.42202, -122.08408,
},
}
func main() {
fmt.Println(t, m) // map[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]
}
Go에서 맵은 값에 key를 지정한다.
맵은 반드시 사용하기 전에 make를 명시해아한다.
make를 수행하지 않은 nil 에는 값을 할당할 수 없다.
만약 상위의 타입이 타입명이라면 리터럴에서 타입명을 생략해도 된다.
즉 "Bell Labs": {40.68433, -74.39967} 와 "Bell Labs": Vertex{40.68433, -74.39967}는 같은 뜻이다.
맵 다루기 >
맵의 요소를 생성/ 수정 : m[key] = data
요소값 가져오기 : val = m[key]
키 존재 여부 확인 : val, has_key = m[key] >> 만약 m[key]가 존재하지 않는다면 has_key엔 false가, val엔 zero value이 할당된다.
16. 연습문제
WordCount 함수를 구현합니다. 이 함수는 s라는 문자열 내에서 각각의 “단어”의 등장 횟수를 나타내는 맵을 반환해야 합니다. wc.Test 함수는 주어진 함수를 이용하여 테스트를 실행한 뒤에 그 성공 여부를 출력해 줍니다.
package main
import (
"code.google.com/p/go-tour/wc"
"strings"
)
func WordCount(s string) map[string]int {
m := make(map[string]int)
for _, key := range strings.Fields(s) {
_, ok := m[key]
if ok {
m[key]++
} else {
m[key] = 1
}
}
return m
}
func main() {
wc.Test(WordCount)
}
17. 함수 값과 클로저
package main
import "fmt"
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
neg(-2*i),
)
}
}
Go에서 함수를 value(값)이다. 값이기 때문에 변수에 저장해 사용할 수 있다.
함수는 클로저(full closures)이다. 위 예제에서 보면 각각의 pos와 neg의 sum 변수가 존재하는 걸 볼 수있다.
18. 연습문제
fibonacci 함수를 구현합니다. 이 함수는 이어지는 피보나치 수를 반환하는 함수 (클로져)를 반환해야 합니다.
package main
import "fmt"
// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
x1 := 1
x2 := 2
return func() int {
x3 := x1+x2
x1, x2 = x2, x3
return x3
}
}
func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}
rune [ int32의 alias이다. ] : 대개 유니코드 코드 포인트 값을 표현하는데 사용된다.
float32, float64
complex64, complex128
+ Go에선 Type casting을 위해선 uint8(10)과 같이 castring 할 수 있다.
9. 구조체 (struct)
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
v.X = 4
fmt.Println(v.X)
}
type 구조체이름 struct { ... } 으로 정의하고, 구조체이름( ... )으로 선언한다.
구조체 필드에 속한 데이터는 dot(.)으로 접근할 수 있다.
구조체 리터럴(Struct Literals)은 필드와 값을 나열해 구조체를 새로 할당하는 방법이다.
순서의 상관없이 {name: value}구문을 통해 할당 할 수 있다.
package main
import "fmt"
type Vertex struct {
X, Y int
}
var (
p = Vertex{1, 2} // has type Vertex
r = Vertex{X: 1} // Y:0 is implicit
s = Vertex{} // X:0 and Y:0
)
func main() {
fmt.Println(p, r, s) // {1 2} {1 0} {0 0}
}
10. 포인터
Go에선 포인터는 & 접두어로 사용되며, C처럼 포인터 연산은 지원하지 않는다.
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
p := Vertex{1, 2}
q := &p
q.X = 1e9
fmt.Println(p) // {1000000000 2}
}
C에서의 동작과 유사하게 돌아간다. ( 포인터를 통해 접근시 가르키고 있는 데이터도 변경이 이루어짐 )
포인터 연산이 안되니 오히려 C++의 참조자와 비슷한것아닌지…(주관적인생각)
11. new 함수
new(T)는 모든 필드가 0(Zero value)이 할당된 T 타입의 포인터를 반환한다.
(Zero value는 숫자 타입에서는 0, 참조 타입에선 nil을 뜻한다.)
package main
import "fmt"
type Vertex struct {
X, Y int
}
func main() {
v := new(Vertex)
// or var v *Vertex = new(Vertex)
fmt.Println(v)
v.X, v.Y = 11, 9
fmt.Println(v) // &{0 0} &{11 9}
}
12. 슬라이스 (Slices)
슬라이스란 Go에서의 배열의 값을 가르킨다. 그리고 배열의 길이(length)를 가지고 있다.
슬라이스에 저장된 데이터를 가져올때 [0보다 큰값:슬라이스의 길이보다 작은 값]을 통해서 부분적으로 잘라서 가져올 수 있다.
[lo:hi]일 경우 슬라이스의 lo번째 데이터에서 hi-1 번째 데이터 까지 가져온다. 전체를 가져오고 싶은 경우, [:]을 통해서 모든 값을 가져 올 수 있다.
슬라이스는 make(타입, 길이, 용량)함수를 통해서 만들 수 있다. cap()함수는 슬라이스의 용량(capacity)를 가져온다.
package main
import "fmt"
func main() {
a := make([]int, 5)
printSlice("a", a)
b := make([]int, 0, 5)
printSlice("b", b)
c := b[:2]
printSlice("c", c)
d := c[2:5]
printSlice("d", d)
fmt.Println(b[:4])
}
func printSlice(s string, x []int) {
fmt.Printf("%s len=%d cap=%d %v\n",
s, len(x), cap(x), x)
}
/** Output **
a len=5 cap=5 [0 0 0 0 0]
b len=0 cap=5 []
c len=2 cap=5 [0 0]
d len=3 cap=3 [0 0 0]
[0 0 0 0]
*/
빈 슬라이스는 슬라이스의 zero value가 nil 이다.
nil 슬라이스는 길이와 용량이 이다.
package main
import "fmt"
func main() {
var z []int
fmt.Println(z, len(z), cap(z))
if z == nil {
b := [...]string{"Penn", "Teller"}
fmt.Println("nil!", cap(b))
}
}
/** Output **
[] 0 0
nil! 2
*/
13. 레인지 Range
for 반복문에서 range를 사용하면 슬라이스나 맵을 순회(iterates)할 수있다.
package main
import "fmt"
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
}
/** Output **
2**0 = 1
2**1 = 2
2**2 = 4
2**3 = 8
2**4 = 16
2**5 = 32
2**6 = 64
2**7 = 128
*/
for index, value := range pow {...}에서 index와 value는 슬라이스의 index와 value를 의미한다.
만약 index나 value를 무시하려면 _를 통해서 무시하고 진행 할 수 있다. for _, v := range pow {...} or for i := range pow {...}
14. 슬라이스 연습
Pic이라는 함수를 구현합니다. 이 함수는 dy개 만큼의 길이를 가지는 슬라이스를 리턴해야 하는데, 각각의 요소들은 또한 dx 개의 8비트 부호없는 8비트 정수 타입을 가지는 슬라이스입니다. 프로그램을 실행하면 이 정수값들을 흑백 (사실은 파란색)을 나타내는 값으로 해석하여 그림을 보여줄 것입니다.
package main
import "code.google.com/p/go-tour/pic"
func Pic(dx, dy int) [][]uint8 {
y := make([][]uint8, dy, dy)
for i := 0; i < int(dy); i++{
y[i] = make([]uint8, dx, dx)
}
for i, pe := range y {
for x := 0; x < dx; x++{
pe[x] = uint8(x) ^ uint8(i)
}
}
return y
}
func main() {
pic.Show(Pic)
}
7-1 함수와 루프의 사용법을 익히는 간단한 연습으로, 제곱근 함수를 뉴턴의 방법(Newton’s method)을 이용하여 구현합니다.
여기서 뉴턴의 방법이란 초기값 z를 선택한 후에 다음의 공식을 이용하여 반복적으로 Sqrt(x) 함수의 근사값을 찾아가는 방법을 말합니다: z = z - (z * z - x) / (2 * z)
처음에는 계산을 10번만 반복하여 여러분이 작성한 함수가 다양한 값들 (1, 2, 3, …)에 대하여 얼마나 정확한 값을 찾아내는지 확인합니다.
7-2 루프의 조건을 수정하여 값이 더이상 바뀌지 않을 때 (혹은 아주 작은 차이가 발생할 때) 루프를 종료하도록 합니다. 이렇게 하면 반복문의 실행 횟수가 어떻게 달라지는지 확인합니다. 결과값이 math.Sqrt함수의 값과 얼마나 비슷한가요?
package main
import (
"fmt"
"math"
)
func Sqrt(x float64) float64{
var a, z, c float64 = 0, 1, 0
for a = 1; a < 10; a++ {
if math.Abs(z - c) > 1e-10 {
c = z
z = z - (z * z - x)/(2*z)
} else {
break
}
}
return z
}
func main() {
fmt.Println(Sqrt(2))
}