Golang Syntax _6
21. 메소드
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를 호출하지만, 위 코드 처럼 직접 에러를 정의할 수 있다.
- + 이해가 힘들어 추가적인 내용 첨부 [예제로 배우는 Go 프로그래밍]
23. 연습문제
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)) }