함수값 (Function values)
import (
'fmt'
'math'
)
func main() {
hypot := function(x, y, float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(3,4))
}
// 5
함수 클로저 (Function closures)
함수는 클로져(full closures) 입니다.
코드에서 adder
함수는 클로져(closure)를 반환합니다.
각각의 클로져는 자신만의 sum
변수를 가집니다.
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),
)
}
}
/*
0 0
1 -2
3 -6
6 -12
10 -20
15 -30
*/
스위치 (Switch)
다른 일반적인 언어를 아는 분이라면 switch
에 대해서 잘 알 것입니다.
다른 언어와 다른점은 case의 코드 실행을 마치면 알아서 break를 한다는 점입니다.
( fallthrough
로 끝나는 case는 스스로 break를 하지 않습니다 )
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를 찾으면 평가를 마칩니다.
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 를 작성해야 할 때, 이 구조를 사용하면 코드를 깔끔하게 작성할 수 있습니다.
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.")
}
}
메소드 (Methods)
Go에는 클래스가 없습니다. 하지만 메소드에 구조체(struct)를 붙일 수 있습니다.
메소드 리시버(method receiver) 는 func
키워드와 메소드의 이름 사이에 인자로 들어갑니다.
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())
}
// 5
메소드 (2)
사실 메소드는 구조체(struct) 뿐 아니라 아무 타입(type)에나 붙일 수 있습니다.
다른 패키지에 있는 타입이나 기본 타입들에 메소드를 붙이는 것은 불가능합니다.
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())
}
// 1.4142135623730951
포인터 리시버를 가지는 메소드
메소드는 이름이 있는 타입 또는 이름이 있는 타입의 포인터와 연결할 수 있습니다.
방금 두 개의 Abs
메소드를 보았는데, 하나는 *Vertex
라는 포인터 타입의 메소드고, 다른 하나는 MyFloat
값 타입의 메소드 입니다.
포인터 리시버를 사용하는 이유는 두 가지 입니다. 첫째, 메소드가 호출될 때 마다 값이 복사되는 것(큰 구조체 타입인 경우 값이 복사되는 것은 비효율적이죠)을 방지하기 위함 입니다. 다른 이유는 메소드에서 리시버 포인터가 가르키는 값을 수정하기 위함 입니다.
*Vertex
타입의 리시버 대신 Vertex
를 사용하도록 메소드 Abs
와 Scale
의 선언부분을 바꿔 보세요.
v
를 Vertex
타입으로 받으면 Scale
메소드가 더 이상 동작하지 않습니다. Scale
은 v
를 바꾸는데, v
가 (포인터가 아닌) 값 타입이기 때문에 Vertex
타입인 복사본에 작업을 하기 때문에 원래의 값은 바뀌지 않습니다.
Abs
의 경우는 다릅니다. 여기서는 v
를 읽기만 하기 때문에, (포인터가 가르키는) 원래의 값이건 복사본이건 상관이 없게 됩니다.
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
인터페이스 ( Interface )
인터페이스는 메소드의 집합으로 정의됩니다.
그 메소드들의 구현되어 있는 타입의 값은 모두 인터페이스 타입의 값이 될 수 있습니다.
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)해 줍니다. 다른 의존성 또한 없음은 물론입니다.
이 특징은 상세하게 인터페이스를 정의하게 독려합니다. 모든 구현을 찾아 새 인터페이스 이름으로 태그할 필요가 없기 때문입니다.
패키지 io에 Reader
와 Writer
가 정의되어 있습니다. 따로 정의할 필요가 없습니다.
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
의 출력을 요청받았을 때 자동으로 이 메소드를 호출합니다.
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