개발

[golang] A tour of Go exercise

melonbbang-ruffy 2024. 11. 19. 12:03

Stringer

type Stringer interface {
	String() string
}

특정 type의 기본 문자열 출력을 String() 메소드를 통해 구현 가능한 인터페이스

주어진 예시 코드를 보면

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func (p Person) String() string {
	return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
	a := Person{"Arthur Dent", 42}
	z := Person{"Zaphod Beeblebrox", 9001}
	fmt.Println(a, z)
}

String() 메소드는 리시버로 구조체 Person을 받고 있다. (오로지 구조체 Person에 속한 인스턴스만 호출 가능한 메소드라는 의미)

fmt.Print 식으로 문자열로 출력할 때, "%v (%v years)" 이런 식으로 출력하겠다는 의미

 

Stringer는 fmt 패키지에 의해 정의된 인터페이스로, fmt 패키지를 비롯한 여러 다양한 패키지에서 값을 출력(print)하기 위해 사용한다.

자기 자신을 문자열로 설명하는 타입으로 그냥 기본 출력을 문자열로 나타내겠다는 의미

// TODO: Add a "String() string" method to IPAddr.

위의 "a.b.c.d" 형식으로 출력해주는 String() 메소드를 구현하라는 의미

아래는 정답 

더보기
package main

import "fmt"

type IPAddr [4]byte

func (addr IPAddr) String() string {
	return fmt.Sprintf("%d.%d.%d.%d", addr[0], addr[1], addr[2], addr[3]) // 출력이 아니라 리턴을 해야하므로 Sprintf 사용
}

func main() {
	hosts := map[string]IPAddr{
		"loopback":  {127, 0, 0, 1},
		"googleDNS": {8, 8, 8, 8},
	}
	for name, ip := range hosts {
		fmt.Printf("%v: %v\n", name, ip)
	}
}

String() 메소드를 통해 문자열 출력 형식을 변경할 수 있음을 보여주는 예제

 

errors

Stringer와 비슷하게 내장 인터페이스로, go에서 오류 상태를 표현하는 인터페이스이다.

type error interface {
	Error() string
}

fmt 패키지 또한 값을 출력할때 Stringer 인터페이스와 마찬가지로 error 인터페이스를 함께 동반한다.

i, err := strconv.Atoi("42")
if err != nil {
    fmt.Printf("couldn't convert number: %v\n", err)
    return
}
fmt.Println("Converted integer:", i)
// 오류 없이 잘 되면 err는 nil 값을 갖고, 오류가 발생할 경우 err는 nil이 아니다
// 이를 if 분기문을 통해 에러처리함

문제로 ㄱㄱ

쉽게 말해 float64 형식의 값을 반환하는 타입을 만들고, 그 타입에 error 메소드도 만들어 인스턴스 생성 시 오류가 발생하면 만든 error 메소드를 이용해서 미리 만든 형식의 에러를 뱉어내는 구조의 코드를 짜라는 의미

type 선언 -> 해당 type을 리비서로 받는 메소드 작성(Error()) -> Sqrt 함수에서 분기문으로 에러 처리(음수이면 ErrNegative(x)를 가져와서 오류 출력)

아래는 정답

더보기
package main

import (
	"fmt"
)

func Sqrt(x float64) (float64, error) {
	if x < 0 {
		return 0, ErrNegativeSqrt(x)
	}
	return 0, nil
}

// go에서 순서는 중요치 x
type ErrNegativeSqrt float64 // 새로운 type 생성

func (e ErrNegativeSqrt) Error() string {
	return fmt.Sprintf("cannot Sqrt negative number: %f", float64(e))
}

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

return fmt.Sprintf("cannot Sqrt negative number: %f", float64(e))에서 %f말고 %g로 바꿀 경우, -2로 출력이 된다.

%f는 실수(소수점)만을 표현하지만 %g는 소수점 여부에 따라 정수 / 실수 자동적으로 바꿔서 표현하기 때문

readers

※ io 패키지 
golang에서 입출력을 위한 인터페이스와 함수 제공

type Reader interface {
    Read(p []byte) (n int, err error)
}

Read 메소드는 최대 p 바이트를 읽어서 p에 저장 후, 읽은 바이트의 수인 n과 error를 반환한다.

실습 예제

'A'를 무한히 구현하는 메소드 작성

 

아래는 정답

더보기
package main

import "golang.org/x/tour/reader"

type MyReader struct{}

func (r MyReader) Read(p []byte) (int, error) {
	for := range p {
		p[i] = 'A'
	}
	return len(p), nil // 읽은 바이트 수, nil 반환
}

func main() {
	reader.Validate(MyReader{}) // 사용자 정의 타입인 MyReader의 빈 구조체 인스턴스 생성
	// Validate 함수를 이용해 검증
}

// type MyReader struct{}는 필드 없는 빈 구조체이기 때문에 중괄호 안에 특별히 초기화할 값이 x

실습 예제 - rot13Reader

해당 실습은 io.Reader의 데이터를 읽고, rot13 변환을 적용하여 변환이 적용된 문자열을 반환하여 출력하는 예제이다.

 

아래는 정답

더보기
type rot13Reader struct {
	r io.Reader
} // rot13Reader 구조체 정의

func (rot13 *rot13Reader) Read(p p[]byte) (int, error) {
	n, err := rot13.r.Read(p)
	if err != nil {
		return n, err
	}
	// rot13 변환
	for i := 0; i < n; i++ {
		if (p[i] >= 'A' && p[i] <= 'Z') || (p[i] >= 'a' && p[i] <= 'z') {
			if (p[i] >= 'A' && p[i] <= 'Z') {
				p[i] = 'A' + (p[i] - 'A' + 13) % 26
			}
			else if (p[i] >= 'a' && p[i] <= 'z') {
				p[i] = 'a' + (p[i] - 'a' + 13) % 26
			}
		}
	}
	return n, err // if문에 안걸렸다면 err = nil일 것
}
  • rot13Reader 구조체 선언 -> io.Reader 타입을 필드(변수)로 가짐
  • Read 메소드에서 p바이트의 슬라이스를 읽어온 후 각 문자를 rot13 방식으로 변환
    (대문자 / 소문자 경우의 수를 나누어서 각각 변환함)
  • rot13 변환 -> 13글자씩 이동한 후 순환(%26, 알파벳은 총 26개 있으므로)
  • io.Copy 함수는 포인터 r을 통해 데이터를 읽어와서 stdout으로 출력

&는 변수 앞에 붙어서 포인터 변수임을 선언, *를 통해 포인터를 통한 메모리에 접근 가능, 메모리의 값을 변경 가능

 

전체 코드를 작성해보자.

package main

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

type rot13Reader struct {
	r io.Reader
} // rot13Reader 구조체 정의

func (rot13 *rot13Reader) Read(p []byte) (int, error) {
	n, err := rot13.r.Read(p)
	if err != nil {
		return n, err
	}
	// rot13 변환
	for i := 0; i < n; i++ {
		if (p[i] >= 'A' && p[i] <= 'Z') || (p[i] >= 'a' && p[i] <= 'z') {
			if (p[i] >= 'A' && p[i] <= 'Z') {
				p[i] = 'A' + (p[i] - 'A' + 13) % 26
			} else if (p[i] >= 'a' && p[i] <= 'z') {
				p[i] = 'a' + (p[i] - 'a' + 13) % 26
			}
		}
	}
	return n, err // if문에 안걸렸다면 err = nil일 것
}

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

go playground에 돌려보면

 

Images

Image 인터페이스는 이미지의 기본적인 속성과 픽셀 데이터를 읽어오는 방법을 정의한다.

package image

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

 

※ ColorModel() color.Model
이미지의 색상 모델을 반환하는 메소드 
색상 모델(color.Model)은 이미지의 픽셀이 어떤 색상 공간을 사용하는지 나타냄(예: RGB, Grayscale 등)
※ Bounds() Rectangle
이미지의 경계(Rectangle)를 반환하는 메소드
반환된 Rectangle은 이미지의 너비와 높이를 결정하며, 이미지의 좌표계를 정의함
※ At(x, y int) color.Color
(x, y) 좌표의 픽셀 색상 값을 반환하는 메소드 
반환된 값은 color.Color 인터페이스를 구현하는 타입으로, 색상 정보를 포함한 값을 반환함

++) ColorModel, Bounds, At 메소드?
ColorModel() 메소드는 이미지의 색상 표현 방식을 반환하며, 이는 color.RGBAModel, color.GrayModel 등으로 구현될 수 있다.
Bounds() 메소드는 이미지의 크기를 반환하는데, Rectangle 타입은 Min과 Max 필드를 통해 이미지의 경계를 나타내는 타입이다.
At(x, y int) 메소드는 이미지의 (x, y) 좌표에 해당하는 픽셀의 색상 값을 반환한다. 해당 메소드를 통해 이미지를 읽을 때 특정 좌표의 색상 정보를 가져올 수 있다.

 

실습 예제

데이터 슬라이스를 이용하는 게 아닌, Image 인터페이스를 구현하는 사용자 정의 타입을 만들고, 그 타입을 통해 이미지를 생성하는 문제

만약 Image 인터페이스를 구현하는 나만의 Image 타입을 정의하고, 필요한 메소드를 구현한 후에 pic.ShowImage를 호출하면 우리가 원하는 대로 출력이 될 것이다.

해당 Image 인터페이스를 구현하기 위해? -> 인터페이스에 정의된 메소드들을 필수적으로 구현해야함

ColorModel : color.RGBA Model을 반환
Bounds : 이미지의 크기를 반환하는 image.Rectangle 반환
At : 특정 좌표의 픽셀의 색상을 반환하는 color.Color 반환

아래는 정답

더보기
// Image 타입정의
type Image struct {
	width, height int
}

// ColorModel 메소드 구현
func (img Image) ColorModel() color.Model {
	return color.RGBAModel
}

// Bounds 메서드 구현
func (img Image) Bounds() image.Rectangle {
    return image.Rect(0, 0, img.width, img.height)
}

// At 메서드 구현
func (img Image) At(x, y int) color.Color {
    v := uint8((x + y) % 256) // 임의의 픽셀 값 생성
    return color.RGBA{v, v, 255, 255}
}
  • type Image struct: 사용자 정의 Image 타입 생성
  • ColorModel():
    • color.RGBAModel을 반환하여 이미지가 RGBA 색상 모델을 사용함을 명시
  • Bounds():
    • 이미지의 경계를 image.Rect(0, 0, img.width, img.height)로 반환 ->  이미지의 좌표계를 정의하여 (0, 0)부터 (width, height)까지의 사각형을 나타냄
  • At(x, y int):
    • (x, y) 좌표의 픽셀 값 반환
    • 예제에서는 v := uint8((x + y) % 256)을 사용해 단순한 패턴의 픽셀 값을 생성함.
    • 이 값을 color.RGBA{v, v, 255, 255} 형태로 반환해 픽셀 색상 설정

pic.ShowImage(m)이 호출되어 256x256 크기의 이미지를 생성하고 표시하게 된다.

해당 이미지는 At() 메서드에서 정의한 대로 각 픽셀의 색상이 (x, y) 좌표에 따라 변경된다.

 

+) 전체 코드

여기서는 Image의 크기를 10x10으로 설정함

package main

import (
    "image"
    "image/color"
    "golang.org/x/tour/pic"

)


// Image 타입정의
type Image struct {
	width, height int
}

// ColorModel 메소드 구현
func (img Image) ColorModel() color.Model {
	return color.RGBAModel
}

// Bounds 메서드 구현
func (img Image) Bounds() image.Rectangle {
    return image.Rect(0, 0, img.width, img.height)
}

// At 메서드 구현
func (img Image) At(x, y int) color.Color {
    v := uint8((x + y) % 256) // 임의의 픽셀 값 생성
    return color.RGBA{v, v, 255, 255}
}

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