Hello, 안녕

Go 프로그래밍 언어 투어에 오신 것을 환영합니다.

이 투어는 3개의 섹션으로 되어 있고, 각 섹션의 마지막 부분에는 좀더 완벽한 이해를 돕기 위해 연습문제가 준비되어 있습니다.

지금 Run 버튼을 클릭해보거나 키보드에서 Shift-Enter 키를 눌러보세요. 옆의 Go 소스가 컴파일되고 실행될 겁니다. 실행 결과는 코드 밑에 표시됩니다.

이 투어에 있는 많은 예제 프로그램들은 Go가 다른 언어들과 어떤 차이점이 있는지 보여줄 것이고, 여러분이 Go를 배우는데 출발점이 될 것입니다.

소스를 수정하고 다시 한번 실행해보세요.

이제 다음으로 넘어갈 준비가 되었으면, Next 버튼을 클릭하거나 PageDown 키를 누르세요.

package main

import "fmt"

func main() {
    fmt.Println("Hello, 안녕")
}

Go local

이 투어는 다른 나라 언어로도 이용할 수 있습니다:

계속 하시려면 Next 버튼을 클릭하거나 PageDown 키를 누르세요.

Go offline

Go Tour는 인터넷 연결없이 stand-alone 프로그램으로도 사용할 수 있습니다.

stand-alone으로 Go Tour를 이용하면 예제 코드들이 로컬 머신에서 빌드되고 실행되기 때문에 보다 빠릅니다. 그리고 웹버전에는 가능하지 않은 추가적인 연습문제들을 포함하고 있습니다.

이 투어를 로컬에서 실행하기 위해 먼저 Go를 설치 하고, gotour를 설치하기 위해 go get을 명령을 이용하면 됩니다.

go get code.google.com/p/go-tour/gotour

그리고 위 결과로 생긴 gotour 를 실행하세요.

오프라인 모드를 사용하지 않고 계속 진행하려면, "next" 버튼이나 PageDown 키를 누르세요.

(언제든 "index" 버튼을 누르면 원하는 특별한 챕터로 이동할 수 있습니다.)

패키지(Packages)

모든 Go 프로그램은 패키지로 구성되어 있습니다.

프로그램은 main 패키지에서부터 실행을 시작합니다.

이 프로그램은 `"fmt"`와 "math" 패키지를 import 해서 사용하고 있습니다.

패키지 이름은 디렉토리 경로의 마지막 이름을 사용하는 것이 규칙입니다.

예를 들어 "path/filepath" 를 사용한다면 패키지명은 filepath 입니다.

package main

import (
    "fmt"
    "math"
)

func main() {
    fmt.Println("Happy", math.Pi, "Day")
}

임포트 (Imports)

이 코드에서는 여러개의 "package" 를 소괄호로 감싸서 import를 표현합니다. 아래와 같이 import 문장을 여러번 사용할 수 도 있습니다.

import "fmt"
import "math"
package main

import (
    "fmt"
    "math"
)

func main() {
    fmt.Printf("Now you have %g problems.",
        math.Nextafter(2, 3))
}

익스포트 (Exported names)

패키지를 Import 하면 패키지가 외부로 export 한 것들(메서드나 변수, 상수 등)에 접근할 수 있습니다.

Go에서는 첫 문자가 대문자로 시작하면 그 패키지를 사용하는 곳에서 접근할 수 있는 exported name이 됩니다.

예를 들어 FooFOO 는 외부에서 참조할 수 있지만 foo 는 참조 할 수 없습니다.

예제를 실행해보세요. 에러가 발생한다면 math.pimath.Pi 로 변경 해보세요.

package main

import (
    "fmt"
    "math"
)

func main() {
    fmt.Println(math.pi)
}

함수 (1)

함수는 매개변수(인자)를 가질 수 있습니다.

예를 들어 add 라는 함수는 두개의 int 타입 매개변수를 받습니다.

C, C++, Java 언어와 다르게 매개변수의 타입은 변수명 뒤에 명시합니다.

(타입을 왜 변수명 뒤에 명시하는지에 대한 자세한 내용은 Go's declaration syntax를 참고하시기 바랍니다. 간단히 설명하면 코드를 왼쪽에서 오른쪽으로 읽을 때 자연스럽게 읽기 위해서 입니다.)

package main

import "fmt"

func add(x int, y int) int {
    return x + y
}

func main() {
    fmt.Println(add(42, 13))
}

함수 (2)

두 개 이상의 매개변수가 같은 타입(type)일 때, 같은 타입을 취하는 마지막 매개변수에만 타입을 명시하고 나머지는 생략할 수 있습니다.

예를 들어

x int, y int

과 같은 문장은 아래와 같이 표현할 수 있습니다.

x, y int
package main

import "fmt"

func add(x, y int) int {
    return x + y
}

func main() {
    fmt.Println(add(42, 13))
}

여러 개의 결과 (Multiple results)

하나의 함수는 여러 개의 결과를 반환할 수 있습니다.

예제 코드에서의 함수는 두개의 문자열을 반환합니다.

package main

import "fmt"

func swap(x, y string) (string, string) {
    return y, x
}

func main() {
    a, b := swap("hello", "world")
    fmt.Println(a, b)
}

이름이 정해진 결과 (Named results)

함수는 매개변수를 취합니다. Go에서 함수는 여러 개의 결과를 반환할 수 있습니다. 반환 값에 이름을 부여하면 변수처럼 사용할 수도 있습니다.

결과에 이름을 붙히면, 반환 값을 지정하지 않은 return 문장으로 결과의 현재 값을 알아서 반환합니다.

package main

import "fmt"

func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return
}

func main() {
    fmt.Println(split(17))
}

변수 (Variables)

변수를 선언을 위해 var 을 사용합니다.

함수의 매개변수처럼 타입은 문장 끝에 명시합니다.

package main

import "fmt"

var x, y, z int
var c, python, java bool

func main() {
    fmt.Println(x, y, z, c, python, java)
}

변수의 초기화

변수 선언과 함께 변수 각각을 초기화를 할 수 있습니다.

초기화를 하는 경우 타입(type)을 생략할 수 있습니다. 변수는 초기화 하고자 하는 값에 따라 타입이 결정됩니다.

package main

import "fmt"

var x, y, z int = 1, 2, 3
var c, python, java = true, false, "no!"

func main() {
    fmt.Println(x, y, z, c, python, java)
}

변수의 짧은 선언

함수 내에서 := 을 사용하면 var 과 명시적인 타입(e.g. int, bool) 을 생략할 수 있습니다.

(그러나 함수 밖에서는 := 선언을 사용할 수 없습니다.)

package main

import "fmt"

func main() {
    var x, y, z int = 1, 2, 3
    c, python, java := true, false, "no!"

    fmt.Println(x, y, z, c, python, java)
}

상수 (Constants)

상수는 const 키워드와 함께 변수처럼 선언합니다.

상수는 문자(character), 문자열(string), 부울(boolean), 숫자 타입 중의 하나가 될 수 있습니다.

package main

import "fmt"

const Pi = 3.14

func main() {
    const World = "안녕"
    fmt.Println("Hello", World)
    fmt.Println("Happy", Pi, "Day")

    const Truth = true
    fmt.Println("Go rules?", Truth)
}

숫자형 상수 (Numeric Constants)

숫자형 상수는 정밀한 값(values) 을 표현할 수 있습니다.

타입을 지정하지 않은 상수는 문맥(context)에 따라 타입을 가지게 됩니다.

코드를 통해 needInt(Big) 는 어떤 결과를 출력할지 실험해보세요.

package main

import "fmt"

const (
    Big   = 1 << 100
    Small = Big >> 99
)

func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
    return x * 0.1
}

func main() {
    fmt.Println(needInt(Small))
    fmt.Println(needFloat(Small))
    fmt.Println(needFloat(Big))
}

For (반복문 for)

Go 언어는 반복문이 for 밖에 없습니다.

기본적인 for 반복문은 C와 Java 언어와 거의 유사합니다. 다른점은 소괄호 ( )가 필요하지 않다는 것입니다.

하지만 실행문을 위한 중괄호 { } 는 필요합니다.

package main

import "fmt"

func main() {
    sum := 0
    for i := 0; i < 10; i++ {
        sum += i
    }
    fmt.Println(sum)
}

For (2)

C와 Java에서 처럼 전.후 처리를 제외하고 조건문만 표현할 수도 있습니다.

package main

import "fmt"

func main() {
    sum := 1
    for sum < 1000 {
        sum += sum
    }
    fmt.Println(sum)
}

Go에서 "While" 사용하기

이전의 예제에서 처럼 조건문만 표시하면 C언어에서 while 을 사용하듯 for 를 사용할 수 있습니다.

package main

import "fmt"

func main() {
    sum := 1
    for sum < 1000 {
        sum += sum
    }
    fmt.Println(sum)
}

무한 루프

for에서 조건문을 생략하면 무한 루프를 간단하게 표현할 수 있습니다.

package main

func main() {
    for {
    }
}

조건문 (If)

if 문은 C와 Java와 비슷합니다. 조건 표현을 위해 ( ) 는 사용하지 않습니다. 하지만 실행문을 위한 { } 는 반드시 작성해야합니다.

(For문과 비슷하죠?)

package main

import (
    "fmt"
    "math"
)

func sqrt(x float64) string {
    if x < 0 {
        return sqrt(-x) + "i"
    }
    return fmt.Sprint(math.Sqrt(x))
}

func main() {
    fmt.Println(sqrt(2), sqrt(-4))
}

If와 짧은 명령 사용하기

for 처럼 if 에서도 조건문 앞에 짧은 문장을 실행할 수 있습니다.

예제에서는 조건을 비교하기 전에 `v := math.Pow(x,n)` 을 실행했습니다.

짧은 실행문을 통해 선언된 변수는 if 안쪽 범위(scope) 에서 만 사용할 수 있습니다.

(예제 코드의 pow 함수에서 return 전에 v 를 사용해보세요.)

package main

import (
    "fmt"
    "math"
)

func pow(x, n, lim float64) float64 {
    if v := math.Pow(x, n); v < lim {
        return v
    }
    return lim
}

func main() {
    fmt.Println(
        pow(3, 2, 10),
        pow(3, 3, 20),
    )
}

If와 else

if 에서 짧은 명령문을 통해 선언된 변수는 else 블럭 안에서도 사용할 수 있습니다.

package main

import (
    "fmt"
    "math"
)

func pow(x, n, lim float64) float64 {
    if v := math.Pow(x, n); v < lim {
        return v
    } else {
        fmt.Printf("%g >= %g\n", v, lim)
    }
    // can't use v here, though
    return lim
}

func main() {
    fmt.Println(
        pow(3, 2, 10),
        pow(3, 3, 20),
    )
}

연습: 루프와 함수

함수와 루프의 사용법을 익히는 간단한 연습으로, 제곱근 함수를 뉴턴의 방법(Newton's method)을 이용하여 구현합니다.

여기서 뉴턴의 방법이란 초기값 z를 선택한 후에 다음의 공식을 이용하여 반복적으로 Sqrt(x) 함수의 근사값을 찾아가는 방법을 말합니다:

z = z - (z * z - x) / (2 * z)

처음에는 계산을 10번만 반복하여 여러분이 작성한 함수가 다양한 값들 (1, 2, 3, ...)에 대하여 얼마나 정확한 값을 찾아내는지 확인합니다.

그 다음에는, 루프의 조건을 수정하여 값이 더이상 바뀌지 않을 때 (혹은 아주 작은 차이가 발생할 때) 루프를 종료하도록 합니다. 이렇게 하면 반복문의 실행 횟수가 어떻게 달라지는지 확인합니다. 결과값이 math.Sqrt 함수의 값과 얼마나 비슷한가요?

힌트: 실수(floating point)값을 선언하고 초기화 하려면, 실수값을 표현하는 문법을 사용하거나 변환 함수를 사용합니다:

z := float64(1)
z := 1.0
package main

import (
    "fmt"
)

func Sqrt(x float64) float64 {
}

func main() {
    fmt.Println(Sqrt(2))
}

기본 자료형

Go의 기본 자료형은 아래와 같습니다.

bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // uint8의 다른 이름(alias)

rune // int32의 다른 이름(alias)
     // 유니코드 코드 포인트 값을 표현합니다. 

float32 float64

complex64 complex128
package main

import (
    "fmt"
    "math/cmplx"
)

var (
    ToBe   bool       = false
    MaxInt uint64     = 1<<64 - 1
    z      complex128 = cmplx.Sqrt(-5 + 12i)
)

func main() {
    const f = "%T(%v)\n"
    fmt.Printf(f, ToBe, ToBe)
    fmt.Printf(f, MaxInt, MaxInt)
    fmt.Printf(f, z, z)
}

구조체 (Structs)

struct 는 필드(데이터)들의 조합입니다.

(그리고 type 선언으로 struct의 이름을 지정할 수 있습니다.)

package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

func main() {
    fmt.Println(Vertex{1, 2})
}

구조체 필드

구조체에 속한 필드(데이터)는 dot(.) 으로 접근합니다.

package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    v.X = 4
    fmt.Println(v.X)
}

포인터 (Pointers)

Go에는 포인터가 있지만 포인터 연산은 불가능합니다.

구조체 변수는 구조체 포인터를 이용해서 접근 할 수 있습니다.

포인터를 이용하는 간접적인 접근은 실제 구조체에도 영향을 미칩니다.

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)
}

구조체 리터럴 (Struct Literals)

구조체 리터럴은 필드와 값을 나열해서 구조체를 새로 할당하는 방법입니다.

원하는 필드를 `{Name: value}` 구문을 통해 할당할 수 있습니다. (필드의 순서는 상관 없습니다.)

특별한 접두어 & 를 사용하면 구조체 리터럴에 대한 포인터를 생성할 수 있습니다.

package main

import "fmt"

type Vertex struct {
    X, Y int
}

var (
    p = Vertex{1, 2}  // has type Vertex
    q = &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, q, r, s)
}

new 함수

new(T) 는 모든 필드가 0(zero value) 이 할당된 T 타입의 포인터를 반환합니다.

( zero value 는 숫자 타입에서는 0 , 참조 타입에서는 nil 을 뜻합니다 )

var t *T = new(T)

또는

t := new(T)

위의 변수 t는 T 에서 반환된 포인터를 가집니다.

package main

import "fmt"

type Vertex struct {
    X, Y int
}

func main() {
    v := new(Vertex)
    fmt.Println(v)
    v.X, v.Y = 11, 9
    fmt.Println(v)
}

슬라이스 (Slices)

슬라이스는 배열의 값을 가리킵니다(point). 그리고 배열의 길이를 가지고 있습니다.

[]T 는 타입 T 를 가지는 요소의 슬라이스(slice) 입니다.

package main

import "fmt"

func main() {
    p := []int{2, 3, 5, 7, 11, 13}
    fmt.Println("p ==", p)

    for i := 0; i < len(p); i++ {
        fmt.Printf("p[%d] == %d\n",
            i, p[i])
    }
}

슬라이스 자르기 (Slicing slices)

슬라이스는 재분할 할 수도 있고, 같은 배열을 가리키는(point) 새로운 슬라이스를 만들 수 도 있습니다.

예제로 살펴보면

s[lo:hi]

위의 표현은 lo 에서 hi-1 의 요소(element)를 포함하는 슬라이스입니다. 따라서

s[lo:lo]

는 빈(empty) 슬라이스 이고

s[lo:lo+1]

는 하나의 요소를 가집니다.

package main

import "fmt"

func main() {
    p := []int{2, 3, 5, 7, 11, 13}
    fmt.Println("p ==", p)
    fmt.Println("p[1:4] ==", p[1:4])

    // missing low index implies 0
    fmt.Println("p[:3] ==", p[:3])

    // missing high index implies len(s)
    fmt.Println("p[4:] ==", p[4:])
}

슬라이스 만들기

슬라이스는 make 함수로 만들 수 있습니다. 이렇게 생성된 슬라이스는 0을 할당한 배열을 생성하고, 그것을 참조(refer)합니다.

a := make([]int, 5)  // len(a)=5

make 함수의 세번째 매개변수로 용량(capacity)를 제한할 수 있습니다.

b := make([]int, 0, 5) // len(b)=0, cap(b)=5

b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:]      // len(b)=4, cap(b)=4
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)
}

func printSlice(s string, x []int) {
    fmt.Printf("%s len=%d cap=%d %v\n",
        s, len(x), cap(x), x)
}

빈 슬라이스

슬라이스의 zero value는 nil 입니다.

nil 슬라이스는 길이와 최대 크기가 0입니다.

(슬라이스에 대해 더 알고 싶다면 다음 글을 읽어보세요. Slices: usage and internals)

package main

import "fmt"

func main() {
    var z []int
    fmt.Println(z, len(z), cap(z))
    if z == nil {
        fmt.Println("nil!")
    }
}

레인지 (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)
    }
}

레인지 (2)

_ 를 이용해서 인덱스(index)나 값(value)를 무시할 수 있습니다.

만약 인덱스만 필요하다면 “ `, value` ” 부분을 다 제거하면 됩니다.

for i, value := range pow {
	pow[i] = 1 << uint(i)
}

에서

for i := range pow {
	pow[i] = 1 << uint(i)
}

처럼 사용할 수 있습니다.

package main

import "fmt"

func main() {
    pow := make([]int, 10)
    for i := range pow {
        pow[i] = 1 << uint(i)
    }
    for _, value := range pow {
        fmt.Printf("%d\n", value)
    }
}

연습: 슬라이스

Pic이라는 함수를 구현합니다. 이 함수는 dy개 만큼의 길이를 가지는 슬라이스를 리턴해야 하는데, 각각의 요소들은 또한 dx 개의 8비트 부호없는 8비트 정수 타입을 가지는 슬라이스입니다. 프로그램을 실행하면 이 정수값들을 흑백 (사실은 파란색)을 나타내는 값으로 해석하여 그림을 보여줄 것입니다.

그림은 여러분이 원하는 것으로 선택할 수 있습니다. (이용할 수 있는) 흥미로운 함수로는 x^y, (x+y)/2, x*y 등이 있습니다.

(여러분은 [][]uint8 슬라이스 내에서 사용할 각각의 []uint8 슬라이스를 할당하기 위해 루프를 활용해야 할 것입니다.)

(타입 간의 변환을 위해서는 uint8(intValue)을 사용합니다.)

package main

import "code.google.com/p/go-tour/pic"

func Pic(dx, dy int) [][]uint8 {
}

func main() {
    pic.Show(Pic)
}

맵 (Maps)

맵은 값에 키를 지정합니다.

맵은 반드시 사용하기 전에 make 를 명시해야합니다. (주의: new 가 아닙니다)

make 를 수행하지 않은 nil 에는 값을 할당할 수 없습니다.

package main

import "fmt"

type Vertex struct {
    Lat, Long float64
}

var m map[string]Vertex

func main() {
    m = make(map[string]Vertex)
    m["Bell Labs"] = Vertex{
        40.68433, -74.39967,
    }
    fmt.Println(m["Bell Labs"])
}

맵 리터럴 (Map literals)

맵 리터럴은 구조체 리터럴과 비슷하지만 key 를 반드시 지정해야 합니다.

package main

import "fmt"

type Vertex struct {
    Lat, Long float64
}

var m = map[string]Vertex{
    "Bell Labs": Vertex{
        40.68433, -74.39967,
    },
    "Google": Vertex{
        37.42202, -122.08408,
    },
}

func main() {
    fmt.Println(m)
}

맵 리터럴 (2)

만약 가장 상위의 타입이 타입명이라면 리터럴에서 타입명을 생략해도 됩니다.

"Bell Labs": {40.68433, -74.39967}

또는

"Bell Labs": Vertex{40.68433, -74.39967}

는 같은 표현입니다.

package main

import "fmt"

type Vertex struct {
    Lat, Long float64
}

var m = map[string]Vertex{
    "Bell Labs": {40.68433, -74.39967},
    "Google":    {37.42202, -122.08408},
}

func main() {
    fmt.Println(m)
}

맵 다루기 (Mutating Maps)

m 의 요소를 삽입하거나 수정하기:

m[key] = elem

요소 값 가져오기:

elem = m[key]

요소 지우기:

delete(m, key)

키의 존재 여부 확인하기:

elem, ok = m[key]

위의 ok 의 값은 mkey 가 존재한다면 true 존재하지 않으면 false , elem 은 타입에 따라 0(zero value) 가 됩니다.

이처럼 map 을 읽을 때, 존재하지 않는 key 의 반환 값은 타입에 맞는 zero value 입니다.

package main

import "fmt"

func main() {
    m := make(map[string]int)

    m["Answer"] = 42
    fmt.Println("The value:", m["Answer"])

    m["Answer"] = 48
    fmt.Println("The value:", m["Answer"])

    delete(m, "Answer")
    fmt.Println("The value:", m["Answer"])

    v, ok := m["Answer"]
    fmt.Println("The value:", v, "Present?", ok)
}

연습: 맵

WordCount 함수를 구현합니다. 이 함수는 s라는 문자열 내에서 각각의 "단어"의 등장 횟수를 나타내는 맵을 반환해야 합니다. wc.Test 함수는 주어진 함수를 이용하여 테스트를 실행한 뒤에 그 성공 여부를 출력해 줍니다.

아마도 다음 링크 (strings.Fields)의 내용이 도움이 될 것입니다.

package main

import (
    "code.google.com/p/go-tour/wc"
)

func WordCount(s string) map[string]int {
    return map[string]int{"x": 1}
}

func main() {
    wc.Test(WordCount)
}

함수 값 (Function values)

함수도 값 입니다.

(번역자 : 맨 아래 hypot(3,4) 의 hypot 함수를 Println함수의 인자값 처럼 사용 하고 있습니다.)

package main

import (
    "fmt"
    "math"
)

func main() {
    hypot := func(x, y float64) float64 {
        return math.Sqrt(x*x + y*y)
    }

    fmt.Println(hypot(3, 4))
}

함수 클로져 (Function closures)

그리고 함수는 클로져(full closures) 입니다.

코드에서 adder 함수는 클로져(closure)를 반환합니다.

각각의 클로져는 자신만의 sum 변수를 가집니다.

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),
        )
    }
}

연습: 피보나치 클로져

함수를 가지고 놀아봅시다.

fibonacci 함수를 구현합니다. 이 함수는 이어지는 피보나치 수를 반환하는 함수 (클로져)를 반환해야 합니다.

package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
}

func main() {
    f := fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Println(f())
    }
}

스위치 (Switch)

다른 일반적인 언어를 아는 분이라면 switch 에 대해서 잘 알 것입니다.

다른 언어와 다른점은 case의 코드 실행을 마치면 알아서 break를 한다는 점입니다.

( fallthrough 로 끝나는 case는 스스로 break를 하지 않습니다 )

package main

import (
    "fmt"
    "runtime"
)

func main() {
    fmt.Print("Go runs on ")
    switch os := runtime.GOOS; os {
    case "darwin":
        fmt.Println("OS X.")
    case "linux":
        fmt.Println("Linux.")
    default:
        // freebsd, openbsd,
        // plan9, windows...
        fmt.Printf("%s.", os)
    }
}

스위치 동작 순서

스위치의 각 조건은 위에서 아래로 평가합니다. 만약 조건이 참인 case를 찾으면 평가를 마칩니다.

(예를 들어

switch i {
case 0:
case f():
}

에서 i==0 이라면 f() 는 실행하지 않습니다)

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.")
    }
}

조건을 생략한 스위치

스위치에서 조건을 생략하면 " switch true " 와 같습니다.

만약 긴 if-then-else 를 작성해야 할 때, 이 구조를 사용하면 코드를 깔끔하게 작성할 수 있습니다.

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.")
    }
}

심화 연습: 복소수 세제곱근

complex64 타입과 complex128 타입을 통해서 Go 언어의 복소수 지원 기능을 알아봅니다. 세제곱근을 얻기 위해서는, 뉴턴의 방법 (Newton's method)을 적용하여 다음을 반복 수행합니다:

z = z - (z * z * z - x) / (3 * z * z)

알고리즘이 잘 동작하는지 확인하기 위해 2의 세제곱근을 구해봅시다. math/cmplx 패키지에는 Pow 함수가 있습니다.

package main

import "fmt"

func Cbrt(x complex128) complex128 {
}

func main() {
    fmt.Println(Cbrt(2))
}

메소드와 인터페이스

메소드 (Methods)

고에는 클래스가 없습니다. 하지만 메소드를 구조체(struct)에 붙일 수 있습니다.

메소드 리시버(method receiver)func 키워드와 메소드의 이름 사이에 인자로 들어갑니다.

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())
}

메소드 (2)

사실 메소드는 구조체(struct) 뿐 아니라 아무 타입(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())
}

포인터 리시버를 가지는 메소드

메소드는 이름이 있는 타입 또는 이름이 있는 타입의 포인터와 연결할 수 있습니다.

방금 두 개의 Abs 메소드를 보았는데, 하나는 *Vertex 라는 포인터 타입의 메소드고, 다른 하나는 MyFloat 값 타입의 메소드 입니다.

포인터 리시버를 사용하는 이유는 두 가지 입니다. 첫째, 메소드가 호출될 때 마다 값이 복사되는 것(큰 구조체 타입인 경우 값이 복사되는 것은 비효율적이죠)을 방지하기 위함 입니다. 다른 이유는 메소드에서 리시버 포인터가 가르키는 값을 수정하기 위함 입니다.

*Vertex 타입의 리시버 대신 Vertex 를 사용하도록 메소드 AbsScale 의 선언부분을 바꿔 보세요.

vVertex 타입으로 받으면 Scale 메소드가 더 이상 동작하지 않습니다. Scalev 를 바꾸는데, v 가 (포인터가 아닌) 값 타입이기 때문에 Vertex 타입인 복사본에 작업을 하기 때문에 원래의 값은 바뀌지 않습니다.

Abs 의 경우는 다릅니다. 여기서는 v 를 읽기만 하기 때문에, (포인터가 가르키는) 원래의 값이건 복사본이건 상관이 없게 됩니다.

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())
}

인터페이스 (Interface)

인터페이스는 메소드의 집합으로 정의됩니다.

그 메소드들의 구현되어 있는 타입의 값은 모두 인터페이스 타입의 값이 될 수 있습니다.

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())
}

type MyFloat float64

func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

type Vertex struct {
    X, Y float64
}

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

인터페이스 암시적으로 충족됩니다

타입이 인터페이스의 메소드들을 구현하면 인터페이스를 구현한 게 됩니다.

이를 위해 명시적으로 선언할 게 없습니다.

암시적 인터페이스는 인터페이스를 정의한 패키지로 부터 구현 패키지를 분리(decouple)해 줍니다. 다른 의존성 또한 없음은 물론입니다.

이 특징은 상세하게 인터페이스를 정의하게 독려합니다. 모든 구현을 찾아 새 인터페이스 이름으로 태그할 필요가 없기 때문입니다.

패키지 ioReaderWriter 가 정의되어 있습니다. 따로 정의할 필요가 없습니다.

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 Writer

    // os.Stdout implements Writer
    w = os.Stdout

    fmt.Fprintf(w, "hello, writer\n")
}

에러(error)

에러 문장(string)으로 자신을 표현할 수 있는 것은 모두 에러입니다. 이 아이디어는 문자열(string)을 반환하는 하나의 메소드 Error 로 구성된 내장 인터페이스 타입 error 에서 나왔습니다.

type error interface {
	Error() string
}

fmt 패키지의 다양한 출력 루틴들은 error 의 출력을 요청받았을 때 자동으로 이 메소드를 호출합니다.

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)
    }
}

연습: 에러

당신의 Sqrt 함수를 이전 연습에서 복사하고 error 값을 반환하도록 수정하십시오.

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" 를 반환하는 그러한 메소드입니다.

Note: Error 메소드 내에서 fmt.Print(e) 를 호출하면 이 프로그램을 무한루프에 빠질 것입니다. e 를 바꿈으로써 이 문제를 피할 수 있습니다. 왜 그럴까요?

음수가 주어졌을 때 ErrNegativeSqrt 값을 반환하도록 당신의 Sqrt 함수를 바꾸십시오.

package main

import (
    "fmt"
)

func Sqrt(f float64) (float64, error) {
    return 0, nil
}

func main() {
    fmt.Println(Sqrt(2))
    fmt.Println(Sqrt(-2))
}

웹 서버

Package httphttp.Handler 를 구현한 어떠 값을 사용하여 HTTP 요청(requests)을 제공합니다.

package http

type Handler interface {
	ServeHTTP(w ResponseWriter, r *Request)
}

이 예제에서, Hello 라는 타입은 http.Handler 를 구현합니다.

이 코드를 로컬에서 실행하고, http://localhost:4000/ 에 접속해보세요.

package main

import (
    "fmt"
    "net/http"
)

type Hello struct{}

func (h Hello) ServeHTTP(
    w http.ResponseWriter,
    r *http.Request) {
    fmt.Fprint(w, "Hello!")
}

func main() {
    var h Hello
    http.ListenAndServe("localhost:4000", h)
}

연습: HTTP 핸들러

아래 나오는 타입을 구현하고 그 타입의 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!"})
package main

import (
    "net/http"
)

func main() {
    // your http.Handle calls here
    http.ListenAndServe("localhost:4000", nil)
}

이미지

Package imageImage 인터페이스를 정의합니다.

package image

type Image interface {
	ColorModel() color.Model
	Bounds() Rectangle
	At(x, y int) color.Color
}

(모든 세부사항에 대한 것은 이 문서 를 참고하십시오.)

또한, color.Colorcolor.Model 는 인터페이스이지만, 미리 정의된 구현체인 color.RGBAcolor.RGBAModel 을 사용함으로써 그 인터페이스를 무시할 수 있습니다.

package main

import (
    "fmt"
    "image"
)

func main() {
    m := image.NewRGBA(image.Rect(0, 0, 100, 100))
    fmt.Println(m.Bounds())
    fmt.Println(m.At(0, 0).RGBA())
}

연습: 이미지

이전의 연습에서 당신이 작성한 그림 생성기를 기억하십니까? 다른 생성기를 만들어봅시다. 하지만 이번에는 데이터의 슬라이스 대신에 image.Image 의 구현체를 반환할 것입니다.

당신 자신의 Image 타입을 정의하시고, 필수 함수들 을 구현하신 다음, pic.ShowImage 를 호출하십시오.

Boundsimage.Rect(0, 0, w, h) 와 같은 image.Rectangle 을 반환해야 합니다.

ColorModelcolor.RGBAModel 을 반환해야 합니다.

At 은 하나의 컬러를 반환해야 합니다; 지난 그림 생성기에서 값 vcolor.RGBA{v, v, 255, 255} 와 같습니다.

package main

import (
    "code.google.com/p/go-tour/pic"
    "image"
)

type Image struct{}

func main() {
    m := Image{}
    pic.ShowImage(m)
}

연습: Rot13 Reader

어떤 식으로든 스트림을 수정하여 다른 io.Reader 를 감싸는 io.Reader 는 흔한 패턴입니다.

예컨대, gzip.NewReader 함수는 io.Reader (gzip으로 압축된 데이터의 스트림) 를 가지고, io.Reader (압축 해제된 데이터의 스트림) 를 구현한 `*gzip.Reader`를 반환합니다.

ROT13 치환 암호화를 모든 알파벳 문자에 적용함으로써 스트림을 수정하며 io.Reader 를 구현하고 io.Reader 로 부터 읽는 rot13Reader 를 구현하십시오.

rot13Reader 타입은 당신을 위해 제공됩니다. 이 타입의 Read 함수를 구현함으로써 io.Reader 을 만들어 보십시오.

package main

import (
    "io"
    "os"
    "strings"
)

type rot13Reader struct {
    r io.Reader
}

func main() {
    s := strings.NewReader(
        "Lbh penpxrq gur pbqr!")
    r := rot13Reader{s}
    io.Copy(os.Stdout, &r)
}

동시성

고루틴(Goroutines)

_고루틴_은 Go 런타임에 의해 관리되는 경량 쓰레드입니다.

go f(x, y, z)

위의 코드는 새로운 고루틴을 시작시킵니다.

f(x, y, z)

현재의 고루틴에서 f , x , y , z 가 평가(evaluation)되고, 새로운 고루틴에서 f 가 수행(execution)됩니다.

고루틴은 동일한 주소 공간에서 실행되므로, 공유되는 자원으로의 접근은 반드시 동기화 되어야 합니다. [[http://golang.org/pkg/sync/][sync]] 패키지가 이를 위해 유용한 기본 기능을 제공합니다. Go 에서는 그외에도 다양한 기본 기능을 제공하니 크게 필요치 않을 테지만요. (다음 슬라이드를 보세요.)

package main

import (
    "fmt"
    "time"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    go say("world")
    say("hello")
}

채널(Channels)

채널은 채널 연산자 <- 를 이용해 값을 주고 받을 수 있는, 타입이 존재하는 파이프입니다.

ch <- v    // v 를 ch로 보냅니다.
v := <-ch  // ch로부터 값을 받아서
           // v 로 넘깁니다.

(데이터가 화살표 방향에 따라 흐릅니다.)

맵이나 슬라이스처럼, 채널은 사용되기 전에 생성되어야 합니다:

ch := make(chan int)

기본적으로, 송/수신은 상대편이 준비될 때까지 블록됩니다. 이런 특성이 고루틴이 명시적인 락이나 조건 없이도 동기화 될 수 있도록 돕습니다.

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)
}

버퍼링되는 채널

채널은 _버퍼링_될 수 있습니다. 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)
}

Range와 Close

데이터 송신측은 더이상 보낼 값이 없다는 것을 알리기 위해 채널을 close 할 수 있습니다. 수신측은 다음과 같이 수신 코드에 두번째 인자를 줌으로써 채널이 닫혔는지 테스트 할 수 있습니다.

v, ok := <-ch

채널이 이미 닫혔고 더이상 받을 값이 없다면 okfalse 가 됩니다.

for i := range c 반복문은 채널이 닫힐 때까지 계속해서 값을 받습니다.

주의: 송신측만 채널을 닫을 수 있습니다. 수신측에선 불가능합니다. 이미 닫힌 채널에 데이터를 보내면 패닉이 일어납니다.

또하나의 주의: 채널은 파일과 다릅니다; 항상 닫을 필요는 없습니다. 채널을 닫는 행위는 오로지 수신측에게 더이상 보낼 값이 없다고 말해야 할때만 행해지면 됩니다. range 루프를 종료시켜야 할 때처럼요.

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)
    }
}

셀렉트(Select)

select 구문은 고루틴이 다수의 통신 동작으로부터 수행 준비를 기다릴 수 있게 합니다.

selectcase 구문으로 받는 통신 동작들 중 하나가 수행될 수 있을 때까지 수행을 블록합니다. 다수의 채널이 동시에 준비되면 그 중 하나를 무작위로 선택합니다.

package main

import "fmt"

func fibonacci(c, quit chan int) {
    x, y := 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)
}

셀렉트의 디폴트(default) 케이스

selectdefault 케이스는 현재 수행 준비가 완료된 케이스가 없을 때 수행됩니다.

블로킹 없이(비동기적인) 송/수신을 하고자 할 때 default 케이스를 사용하세요.

select {
case i := <-c:
	// i를 사용
default:
	// c로부터의 수신은 블록된 상태
}
package main

import (
    "fmt"
    "time"
)

func main() {
    tick := time.Tick(1e8)
    boom := time.After(5e8)
    for {
        select {
        case <-tick:
            fmt.Println("tick.")
        case <-boom:
            fmt.Println("BOOM!")
            return
        default:
            fmt.Println("    .")
            time.Sleep(5e7)
        }
    }
}

연습: 동등한 이진 트리

노드(leaf)들에 있는 값들의 정렬 순열는 같지만 생김새가 다른 이진트리가 있을 수 있습니다. 예를들어, 다음 그림의 두 이진 트리를 정렬 순열는 1, 1, 2, 3, 5, 8, 13 으로 같습니다.

대부분의 프로그래밍 언어에서 두 이진 트리가 같은 순열인지를 검사하는 함수의 구현은 복잡합니다. 이제 고의 동시성과 채널을 사용한 단순한 방법으로 해결해 봅시다.

이 예제는 다음의 Tree 구조체가 정의된 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. Walk 함수를 사용해 두 트리 t1t2 이 값은 값들을 가지고 있는지 비교하는 Same 함수를 구현해 보세요.

4. Same 함수를 테스트 해 보세요.

`Same(tree.New(1),`tree.New(1))`의 수행결과는 true, `Same(tree.New(1),`tree.New(2))`의 수행 결과는 false 이어야 합니다.

package main

import "code.google.com/p/go-tour/tree"

// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int)

// Same determines whether the trees
// t1 and t2 contain the same values.
func Same(t1, t2 *tree.Tree) bool

func main() {
}

연습: 웹 크롤러

이 연습에서는 고의 동시성 기능을 사용해 웹 크롤러를 병렬화 해 볼 것입니다.

Crawl 함수를 고쳐서, 같은 URL을 두번 가져오는 중복을 피하면서 URL들을 병렬로 패치하게 고쳐보세요.

package main

import (
    "fmt"
)

type Fetcher interface {
    // Fetch returns the body of URL and
    // a slice of URLs found on that page.
    Fetch(url string) (body string, urls []string, err error)
}

// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher) {
    // TODO: Fetch URLs in parallel.
    // TODO: Don't fetch the same URL twice.
    // This implementation doesn't do either:
    if depth <= 0 {
        return
    }
    body, urls, err := fetcher.Fetch(url)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("found: %s %q\n", url, body)
    for _, u := range urls {
        Crawl(u, depth-1, fetcher)
    }
    return
}

func main() {
    Crawl("http://golang.org/", 4, fetcher)
}

// fakeFetcher is Fetcher that returns canned results.
type fakeFetcher map[string]*fakeResult

type fakeResult struct {
    body string
    urls []string
}

func (f *fakeFetcher) Fetch(url string) (string, []string, error) {
    if res, ok := (*f)[url]; ok {
        return res.body, res.urls, nil
    }
    return "", nil, fmt.Errorf("not found: %s", url)
}

// fetcher is a populated fakeFetcher.
var fetcher = &fakeFetcher{
    "http://golang.org/": &fakeResult{
        "The Go Programming Language",
        []string{
            "http://golang.org/pkg/",
            "http://golang.org/cmd/",
        },
    },
    "http://golang.org/pkg/": &fakeResult{
        "Packages",
        []string{
            "http://golang.org/",
            "http://golang.org/cmd/",
            "http://golang.org/pkg/fmt/",
            "http://golang.org/pkg/os/",
        },
    },
    "http://golang.org/pkg/fmt/": &fakeResult{
        "Package fmt",
        []string{
            "http://golang.org/",
            "http://golang.org/pkg/",
        },
    },
    "http://golang.org/pkg/os/": &fakeResult{
        "Package os",
        []string{
            "http://golang.org/",
            "http://golang.org/pkg/",
        },
    },
}

더 살펴볼 곳 들...

우선 Go 문서 사이트에서 시작하는 것이 좋습니다. 여기에서 레퍼런스, 튜토리얼, 비디오 등의 자료를 볼 수 있습니다.

고 코드를 구성하기와 고로 작업하는 방법을 배우려면, 이 스크린캐스트를 보거나, 고 코드 작성 방법를 읽어 보세요.

표준 라이브러리에 대한 도움이 필요하면, 패키지 레퍼런스를 살펴보세요. 고 언어 자체에 대해서는 언어 스펙이 도움이 되며, 아마 꽤 쉽게 스펙문서를 읽을 수 있음에 놀라게 될 것입니다.

더 나아가 고의 동시성(concurrency) 모델을 살펴보려면 코드워크, 통신으로 메모리 공유하기를 보세요.

코드워크, First Class Functions in Go에서는 고의 함수 타입에 관련된 흥미로운 관점을 제공합니다.

공식 블로그, Go 에는 유익한 기사들이 많이 있습니다.

공식 사이트 golang.org를 방문해 더 살펴보세요.

번역: 한국 Go 언어 커뮤니티(GDG Korea Golang)