A Tour of Go やってみた Part4
前回
「A Tour of Go」の"Basics: More types: structs, slices, and maps." までを終わらせたので、今回は "Methods and interfaces" をやる
1. Methods
Goにはクラスはないが、型に関数を定義できる。これを「メソッド」と呼ぶ。例えば以下。
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
上記の(v Vertex)
の部分が「レシーバ」になる。上記の例だと構造体Vertex
にAbs()
というメソッドを生やして、Abs()
は v
を受け取って、処理結果を返すことができる。
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())
}
5
struct+メソッドでオブジェクトっぽい操作が可能になるということ。
2. Methods are functions
メソッドは、レシーバーを引数にしただけの、ただの関数なので、普通の関数として書き換えることもできる。
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func Abs(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(Abs(v))
}
5
3. Methods continued
メソッドはstructだけでなく、自分で定義した型ならどんな型にも定義できる。以下は自分で作成したfloat64の新しい型(エイリアス)に対して、abs()
メソッドを定義している。
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())
}
1.4142135623730951
ただし、レシーバー型は同じパッケージで定義された型でなければならない、つまり、他パッケージの型、組み込みのintやstringなどに対してはメソッド追加できない。
4. Pointer receivers
メソッドのレシーバーを*T
みたいなポインタ型にできる。これをポインタレシーバと呼ぶ。例えばこういう感じ。
type Vertex struct {
X, Y float64
}
// Scale はポインタレシーバー *Vertex を受け取って、
// レシーバー先の値を直接変更する
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
メソッドは、メソッド内で自分自身、つまりレシーバーのフィールドを書き換えたい場合のほうが多い。値レシーバだとコピーされてしまうため、メソッド内で変更しても変更されない。なので、ポインタレシーバのほうが使い方として一般的。
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 (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs())
v.Scale(10)
fmt.Println(v.Abs())
}
5
50
Scale
を値レシーバに変えて再度実行してみるとこうなる。
func (v Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
5
5
5. Pointers are functions
Abs
とScale
メソッドを関数として書き直す。
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func Abs(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func Scale(v *Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
Scale(&v, 10)
fmt.Println(Abs(v))
}
5
Scale を値で受け取るように書き換える。
func Scale(v Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
./5.go:23:8: cannot use &v (value of type *Vertex) as Vertex value in argument to Scale
それはそうだよね。呼び出し元も値を渡すように書き換える。
func main() {
v := Vertex{3, 4}
Scale(v, 10) # 値で渡す
fmt.Println(Abs(v))
}
5
Scaleは何も返していないのでそれも当然。以下のように修正する。
func Scale(v Vertex, f float64) Vertex {
v.X = v.X * f
v.Y = v.Y * f
return v
}
func main() {
v := Vertex{3, 4}
v2 := Scale(v, 10)
fmt.Println(Abs(v2))
}
50
6. Methods and pointer indirection
1つ前で分かる通り、関数の呼び出しは引数の型に呼び出し方も合わせる必要があるが、メソッドがポインタレシーバである場合、呼び出すときのレシーバは変数でもポインタでもどちらでもいける。
package main
import "fmt"
type Vertex struct {
X, Y float64
}
// ポインタレシーバを持つメソッド
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
// ポインタ引数を持つ関数
func ScaleFunc(v *Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(2) // 裏では `(&v).Scale(5) `が呼び出される
fmt.Println(v)
ScaleFunc(&v, 10)
fmt.Println(v)
p := &Vertex{4, 3}
p.Scale(3)
fmt.Println(p)
ScaleFunc(p, 8)
fmt.Println(p)
}
{6 8}
{60 80}
&{12 9}
&{96 72}
7. Methods and pointer indirection (2)
同様に、メソッドが値レシーバである場合でも、呼び出すときのレシーバは変数でもポインタでもどちらでもいける。
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 AbsFunc(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs())
fmt.Println(AbsFunc(v))
p := &Vertex{4, 3}
fmt.Println(p.Abs()) // (*p).Abs() として解釈される
fmt.Println(AbsFunc(*p))
}
5
5
5
5
つまりメソッドなら、値レシーバの場合でもポイントレシーバの場合でも、呼び出し側はあまり気にしなくて良い。コピーを変えるか、本体を変えるかだけを考えてメソッド定義すれば良い。
8. Choosing a value or pointer receiver
値レシーバを使うべきか、ポインタレシーバを使うべきか?ポインタレシーバを使う理由は以下の2つ。
- レシーバー先を直接変更したい場合
- 例: オブジェクト内部のフィールドを更新したい場合など
- コピーコストを避けたい場合
- 例: 大きな struct(たとえば数百バイト以上)の場合
- メソッドを呼ぶたびに「コピー」が走ると重い
- ポインタで渡すほうが効率が良い
- 例: 大きな struct(たとえば数百バイト以上)の場合
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}
fmt.Printf("Scale実行前: %+v, Abs: %v\n", v, v.Abs())
v.Scale(5)
fmt.Printf("Scale実行後: %+v, Abs: %v\n", v, v.Abs())
}
Scale実行前: {X:3 Y:4}, Abs: 5
Scale実行後: {X:15 Y:20}, Abs: 25
どちらを使うか?のベストプラクティスとしては以下らしい
- 変更が必要か?大きさが大きいか?で判断
- 変更が必要: ポインタレシーバを選択
- 読み取りのみ & 型が小さい: 値レシーバでもOK、ただし他と混ぜないほうがベター
- 一貫性を重視
- 同じ型のメソッドは「全部ポインタ」「全部値」のどちらかに統一するのがベター
- 可読性を重視
- 一貫性があるとコードを読む際に、型のメソッドを理解しやすい
9. Interfaces
interface型はメソッドのシグネチャ(名前+引数+戻り値)の集合を定義する。
・・・の前に、インタフェースとはなんぞや?ChatGPTによると以下。
プログラミングにおける「インタフェース(interface)」とは、大まかに言うと「機能や振る舞いの約束事」を定めたものです。クラスや構造体がどんなメソッド(操作)を持つべきかを宣言し、それを実装する側(具象クラスや具象型)にその実装を任せる仕組みです。以下、ポイントを押さえて説明します。
「何ができるか」を示す契約書
- インタフェースは「こういう操作ができるよ」というメソッドのシグネチャ(名前・引数・戻り値)を並べたリストです。
- 具体的にどう動くか(中身)は書かず、あくまで「~できる」という約束だけを定めます。
実装は別の場所で行う
- インタフェースを実装するクラス/型は、その約束に則って中身を定義します。
- たとえば「Drawable」というインタフェースなら
draw()
メソッドを持つことだけを決め、実装クラスでは「円を描画する」「四角を描画する」といった具体的な描画処理を書くイメージです。利点:依存関係の切り離し(疎結合)
- 呼び出し側は「Drawableなら draw() が呼べる」だけを知っていればよく、内部の実装を気にしません。
- これにより、異なる実装を簡単に入れ替えられ、テストや拡張がしやすくなります。
プログラミングにおける「インタフェース(interface)」とは、大まかに言うと「機能や振る舞いの約束事」を定めたものです。クラスや構造体がどんなメソッド(操作)を持つべきかを宣言し、それを実装する側(具象クラスや具象型)にその実装を任せる仕組みです。以下、ポイントを押さえて説明します。言語ごとの書き方の例
- Java
// インタフェースの定義 public interface Shape { double area(); } // 実装クラス public class Circle implements Shape { private double radius; public Circle(double r) { radius = r; } public double area() { return Math.PI * radius * radius; } }
- Go
// インタフェースの定義 type Reader interface { Read(p []byte) (n int, err error) } // 標準パッケージの os.File などが自動的にこの Reader を実装する
身近なアナロジー:電気のコンセント
- コンセント(インタフェース)は「プラグを差し込む」という動作だけを提供します。
- テレビも、掃除機も同じ形のプラグを使いますが、中で電気をどう使うかは機器ごとに異なります。
- プログラミングの世界では、コンセントに相当するのがインタフェース、電気機器が実装クラスにあたります。
まとめると、インタフェースは「これだけの機能を提供しますよ」という仕様書のようなもの。実際の動作は後からそれに従って自由に書くことで、コードの再利用性や保守性が高まります。
例
type Abser interface {
Abs() float64
}
Abser
型にはAbs() float64
というメソッドを持っているならどんな型でもいれることができる、というインタフェースになる。
こういうコードがあるとして。
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)
fmt.Println(v.Abs())
}
そのままやれば普通に動く。
{3 4}
5
ここにインタフェースを追加
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
// インターフェース
type Abser interface {
Abs() float64
}
// Pointer レシーバーに Abs 定義
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
var a Abser
v := Vertex{3, 4}
a = v // コンパイルエラー
fmt.Println(a)
}
./9.go:24:9: cannot use v (variable of struct type Vertex) as Abser value in assignment: Vertex does not implement Abser (method Abs has pointer receiver)
-
v := Vertex{3, 4}
は値型のVertex
- インタフェースに登録されているのはポインタ型の
*Vertex
のポインタレシーバを持つAbs()
メソッド -
a
にはインタフェースで制約がかかっているので、代入できない
ということらしい、ここ難しい・・・ちょっと現時点ではちゃんと理解ができていないかもだけど、とりあえず進める。
なので、以下のようにすれば良い。
func main() {
var a Abser
v := Vertex{3, 4}
a = &v // ポインタで渡すことで、`*Vertex`は`Abs()`を持つため、エラーにならない
fmt.Println(a)
}
&{3 4}
A Tour of Goのサンプルコードも。これもエラーになる。
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 // MyFloat は Abser を実装している
a = &v // *Vertex は Abser を実装している
// 以下の場合は、v は Vertex であり、 *Vertexではないため、Abser を実装していない
a = v
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)
}
10. Interfaces are implemented implicitly
Java、C#、TypeScriptなどでインタフェースを定義した場合、クラスへの実装にはimplements
が必要になるらしい。
// インターフェース定義
public interface Abser {
double abs();
}
// 実装するときは `implements` が必須!
public class Vertex implements Abser {
double x, y;
public Vertex(double x, double y) { this.x = x; this.y = y; }
@Override
public double abs() {
return Math.sqrt(x*x + y*y);
}
}
// インターフェース定義
public interface IAbser {
double Abs();
}
// 実装クラスではコロンで宣言!
public class Vertex : IAbser {
public double X, Y;
public double Abs() {
return Math.Sqrt(X*X + Y*Y);
}
}
// インターフェース定義
interface Abser {
abs(): number;
}
// 実装クラスでは `implements` を使うよ
class Vertex implements Abser {
constructor(public x: number, public y: number) {}
abs(): number {
return Math.hypot(this.x, this.y);
}
}
goの場合、これは暗黙的に行われる、つまり implements
を宣言する必要はない
package main
import "fmt"
type I interface {
M()
}
type T struct {
S string
}
// このメソッドは、T が I を実装していることを意味するが、
// 明示的に宣言する必要はない。
func (t T) M() {
fmt.Println(t.S)
}
func main() {
var i I = T{"こんにちは!"}
i.M()
}
こんにちは!
インタフェースが求めるメソッドをその型が持っていれば、実装していると判断される。これにより、インタフェースの定義は実装と分離する。つまり、パッケージが別であっても良いということになる。
逆に言うと、実装が突然出てくることもある。
11. Interface values
インタフェース型の変数は、値と型のタプルとなる。
package main
import (
"fmt"
"math"
)
type I interface {
M()
}
type T struct {
S string
}
// T にポインタレシーバーで M() を定義
func (t *T) M() {
fmt.Println(t.S)
}
type F float64
// F に値レシーバーで M() を定義
func (f F) M() {
fmt.Println(f)
}
func main() {
var i I
i = &T{"こんにちは!"}
describe(i)
i.M()
i = F(math.Pi)
describe(i)
i.M()
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
(&{こんにちは!}, *main.T)
こんにちは!
(3.141592653589793, main.F)
3.141592653589793
インタフェースの値のメソッドを呼ぶと、この型を見て、その型に実装されているメソッドを呼ぶ。
package main
import (
"fmt"
"math"
)
// インターフェースでメソッドのシグネチャを定義
type Abser interface {
Abs() float64
}
// Vertex にポインタレシーバーでメソッドを定義
type Vertex struct{ X, Y float64 }
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
// MyFloat に値レシーバーでメソッドを定義
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
func main() {
var a Abser
a = &Vertex{3, 4}
// a の中身は ( &Vertex{3,4}, *Vertex )
fmt.Println(a.Abs()) // → Vertex の Abs が呼ばれる
a = MyFloat(-2.5)
// a の中身は ( MyFloat(-2.5), MyFloat )
fmt.Println(a.Abs()) // → MyFloat の Abs が呼ばれる
}
5
2.5
12. Interface values with nil underlying values
Go では、インターフェース変数に nil が入っていても、そのままメソッドを呼び出すと、レシーバーが nil の状態でメソッドが実行される(他の言語ではnullポインタ例外が起きるようになっているものが多い。)よって、メソッド側でこれをチェックする実装にするのが一般的。
package main
import "fmt"
type I interface {
M()
}
type T struct {
S string
}
func (t *T) M() {
// t が nil かどうかをチェック
if t == nil {
fmt.Println("<nil>")
return
}
fmt.Println(t.S)
}
func main() {
var i I
var t *T
i = t
i.M()
i = &T{"hello"}
i.M()
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
<nil>
hello
ただし、インターフェース変数自体は非 nilであり、これは値が nil であっても、型がセットされているため。
13. Nil interface values
インタフェース値がnilというのは、値も型もnilな状態を指す
値も型もないと、その型のどのメソッドを呼べばいいのかわからないため、エラーになる。
package main
import "fmt"
type I interface {
M()
}
func main() {
var i I
describe(i)
i.M()
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
(<nil>, <nil>)
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x1042b5cf8]
goroutine 1 [running]:
main.main()
/Users/kun432/work/a-tour-of-go/2_methods_and_interfaces/13.go:12 +0x28
exit status 2
14. The empty interface
空のインタフェースを定義することができる
var i interface{}
空のインタフェースはどんな型でもいれることができる、というのは全ての型は「少なくとも0個のメソッドを実装している」と言えるため。
package main
import "fmt"
func main() {
var i interface{}
describe(i)
i = 42
describe(i)
i = "hello"
describe(i)
}
func describe(i interface{}) {
fmt.Printf("(%v, %T)\n", i, i)
}
(<nil>, <nil>)
(42, int)
(hello, string)
以下のようなユースケースで使用する
- 未知の型を受け取りたい場合
package main
import "fmt"
func describe(i interface{}) {
fmt.Printf("value=%v, type=%T\n", i, i)
}
func main() {
describe(42)
describe("Hello")
describe([]int{1, 2, 3})
}
value=42, type=int
value=Hello, type=string
value=[1 2 3], type=[]int
- 汎用的なデータ構造
package main
import (
"fmt"
)
func main() {
// いろんな型の値を入れられるマップ
data := map[string]interface{}{
"name": "Alice", // string
"age": 30, // int
"active": true, // bool
"scores": []int{85, 90, 78}, // []int
}
for k, v := range data {
fmt.Printf("%s: %v (type: %T)\n", k, v, v)
}
}
name: Alice (type: string)
age: 30 (type: int)
active: true (type: bool)
scores: [85 90 78] (type: []int)
インタフェース、そもそもが理解できていないので、ここはあとでもう一度調べる必要がある
この辺から自分の理解力を超えてきている感があるなぁ・・・
15. Type assersions
interface{}
型を使うとなんでもいれることができる。ここから中味=具体的な型を取り出すのが型アサーション。
package main
import "fmt"
func main() {
var i interface{} = "hello"
s := i.(string) // 型アサーション
fmt.Println(s)
}
i
は interface{}
型の変数として宣言しつつ、string
型の値hello
を初期値として宣言。型アサーションでi
をstring
型として値を取り出しs
に代入している。
実行するとこうなる
hello
ただし以下のように異なる型で取り出そうとするとPanicになる
var i interface{} = "hello"
s := i.(float64)
panic: interface conversion: interface {} is string, not float64
この場合は返り値を2つ受け取るようにする。i
がstring
型の値を保持していれば、2つ目の返り値にtrue
が返され、1つ目の返り値に値が返る。
var i interface{} = "hello"
s, ok := i.(string)
fmt.Println(s, ok)
hello true
指定した型の値を持たない場合はfalse
が返り、値はゼロ値になる。
var i interface{} = "hello"
s, ok := i.(float64)
fmt.Println(s, ok)
0 false
mapと使い方が似ている
16. Type switches
「型switch」を使えば、interface{}
型に入っている値の「型」に応じた分岐処理が書ける。つまり、case
に型を指定できる。型アサーションと同じ書き方をするけども、特別なキーワードtype
が使用して、switch v := i.(type)
というような書き方になる。
package main
import "fmt"
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("%v かける 2 は %v です。\n", v, v*2)
case string:
fmt.Printf("%q は %v バイトです。\n", v, len(v))
default:
fmt.Printf("型 %T は知りません。\n", v)
}
}
func main() {
do(21)
do("こんにちは")
do(true)
}
21 かける 2 は 42 です。
"こんにちは" は 15 バイトです。
型 bool は知りません。
17. Stringers
fmt
パッケージには特別なインタフェースStringer
がある。
type Stringer interface {
String() string
}
これによりString()
メソッドを持ってる型は、自動的にStringer
インタフェースを満たす型かどうか=自分自身を文字列で出力できるか、をチェックすることになる。
package main
import "fmt"
type Person struct {
Name string
Age int
}
// String メソッドを実装することで、Stringer インターフェースを満たす
func (p Person) String() string {
return fmt.Sprintf("%v (%v 歳)", p.Name, p.Age)
}
func main() {
a := Person{"山田太郎", 30}
b := Person{"鈴木花子", 20}
fmt.Println(a) // 自動的に String メソッドが呼び出される
fmt.Println(b)
}
山田太郎 (30 歳)
鈴木花子 (20 歳)
18. Exercise: Stringers
スキップ
19. Errors
Goではエラーが起きたかどうかを特別なerror
型の値で表現する。
error
型はインタフェースとして以下のように定義されている。fmt.Stringer
と似ている。
type error interface {
Error() string
}
つまり、Error()
メソッドを持っていればエラーとして使えることになる。Goの関数はerror
を返すようになっているものが多いみたい。
package main
import (
"fmt"
"strconv"
)
func main() {
i, err := strconv.Atoi("42")
if err != nil {
fmt.Println("エラー:", err)
return
}
fmt.Println("変換後の値:", i)
}
変換後の値: 42
strconv.Atoi("abc")
の場合はこうなる
エラー: strconv.Atoi: parsing "abc": invalid syntax
Goでは例外を使わずにこういう感じでエラーを返して確認するスタイルみたい。
package main
import (
"fmt"
"time"
)
// カスタムなエラー型を定義
type MyError struct {
When time.Time
What string
}
// Error メソッドを実装することで、error インターフェースを満たす
func (e *MyError) Error() string {
return fmt.Sprintf("%v: %s", e.When, e.What)
}
// 意図的にエラーを返す関数
func run() error {
return &MyError{
time.Now(),
"エラーが発生しました",
}
}
func main() {
if err := run(); err != nil {
fmt.Println(err)
}
}
2025-07-05 17:16:01.166233 +0900 JST m=+0.000128834: エラーが発生しました
20. Exercise: Errors
スキップしたけど、
注意: Error メソッドの中で、 fmt.Sprint(e) を呼び出すことは、無限ループのプログラムになることでしょう。 最初に fmt.Sprint(float64(e)) として e を変換しておくことで、これを避けることができます。 なぜでしょうか?
のところ。以下のように書くと無限ループする。
// カスタムエラー型の定義
type ErrNegativeSqrt float64
// errorインタフェースの実装
func (e ErrNegativeSqrt) Error() string {
return fmt.Sprintf("cannot Sqrt negative number: %v", e)
}
fmt.Sprint(e)
とした場合、
-
e
はError()
メソッドを持っているので、error
として扱い、e.Error()
を呼び出す -
Error()
の中でfmt.Sprint(e)
を呼び出す、つまりError()
を呼んで無限ループ
となる。
float64
にキャストすることで、fmt
はError
ではなくStringer
として扱うので、無限ループせずに出力してくれるようになる。
21. Readers
Goには、「ファイル」「ネットワーク」「文字列」「暗号化されたストリーム」など様々なデータの読み出しを共通化するために、io.Readers
インタフェースが定義されており、Read()
メソッドを持つ
type Reader interface {
Read(p []byte) (n int, err error)
}
func (T) Read(b []byte) (n int, err error)
-
b []byte
: データを読み込むためのバッファ -
n int
: 実際に読み込んだバイト数 -
err error
: 最後まで読見終わるとio.EOF
が返る
文字列を、一定のバッファサイズ分、順に読み込んでいくサンプル。日本語UTF-8だとバイト単位の区切りを少し変えないといけない。
package main
import (
"fmt"
"io"
"strings"
)
func main() {
r := strings.NewReader("こんにちは!今日は良いお天気ですね!")
// 9バイトずつ読み込むバッファ
b := make([]byte, 9)
for {
// データをバッファに読み込んで格納
n, err := r.Read(b)
// 読み込んだデータの長さ、エラー、バッファの内容を表示
fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
// 読み込んだデータの内容を表示
fmt.Printf("b[:n] = %q\n", b[:n])
// ファイルの末尾に達した場合、ループを終了
if err == io.EOF {
break
}
}
}
余談。上はたまたまうまくいったけど、日本語の場合はちゃんとやるなら文字単位にしたほうが良さそう。その場合は bufio.Reader
と ReadRune()
を使えばいけるらしい。
package main
import (
"fmt"
"bufio"
"strings"
)
func main() {
r := strings.NewReader("Hello!😊今日は良いお天気ですね!☀️")
s := bufio.NewReader(r)
for {
char, size, err := s.ReadRune()
if err != nil {
break
}
fmt.Printf("%q (%d バイト)\n", char, size)
}
}
'H' (1 バイト)
'e' (1 バイト)
'l' (1 バイト)
'l' (1 バイト)
'o' (1 バイト)
'!' (1 バイト)
'😊' (4 バイト)
'今' (3 バイト)
'日' (3 バイト)
'は' (3 バイト)
'良' (3 バイト)
'い' (3 バイト)
'お' (3 バイト)
'天' (3 バイト)
'気' (3 バイト)
'で' (3 バイト)
'す' (3 バイト)
'ね' (3 バイト)
'!' (3 バイト)
'☀' (3 バイト)
'️' (3 バイト)
22. Exercise: Readers
スキップ
23. Exercise: rot13Reader
スキップ
24. Images
Image
パッケージは以下のimage
インタフェースを定義している
package image
type Image interface {
ColorModel() color.Model
Bounds() Rectangle
At(x, y int) color.Color
}
定義されているメソッド
-
ColorModel()
: 色モデル(RGB or グレースケール)を返す -
Bounds()
: 画像のサイズ(image.Rectangle
型、つまり長方形)を返す -
At(x, y int)
: 指定したピクセルの色を返す
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())
}
(0,0)-(100,100)
0 0 0 0
color.Color と color.Model は共にインタフェースですが、定義済みの color.RGBA と color.RGBAModel を使うことで、このインタフェースを無視できます。 これらのインタフェースは、image/color パッケージで定義されています。
ここはちょっとよくわからないな。一応、color.Color
と color.Model
は以下のようなインタフェースになっているらしい。
type Color interface {
RGBA() (r, g, b, a uint32)
}
type Model interface {
Convert(c Color) Color
}
こういうメソッドが定義されていて、これらを実装すれば「色」として使えるということらしい。まあここは置いておく。
25. Exercise: Images
スキップしよっかなと思ったけど、一応。
回答例
package main
import (
"image"
"image/color"
"golang.org/x/tour/pic"
)
// 自作の画像構造体
type Image struct{}
// 画像サイズを定義
func (img Image) Bounds() image.Rectangle {
return image.Rect(0, 0, 256, 256) // 256x256 の画像
}
// 色モデルを定義
func (img Image) ColorModel() color.Model {
return color.RGBAModel
}
// 各ピクセルの色を定義
func (img Image) At(x, y int) color.Color {
v := uint8((x + y) / 2) // 色の値を適当に計算
return color.RGBA{v, v, 255, 255} // 青っぽいグラデーション
}
func main() {
m := Image{}
pic.ShowImage(m) // 画像を表示
}
ローカルで実行する場合はモジュールを初期化してパッケージを取得する必要がある
go mod init myimage
go get golang.org/x/tour/pic
実行
go run 25.go
IMAGE:iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xAAACaUlEQVR42uzVMRGAAAzAwLSHf8tgAAf95QVkyVNvNRN50FWBl10V6ABa0AFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIB6ADqEAHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdAA6gBZ0ANIBSAcgHYB0ANIBSAcgHYB0ANIBSAcgHYB0ANIBSAcgHYB0ANIBSAcgHYB0ANIBSAcgHYB0ANIBSAcgHYB0ANIBSAcgHYB0ANIBSAcgHYB0ANIBSAcgHYB0ANIBSAcgHYB0ANIBSAcgHYB0ANIB6AAq0AFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgA6gAh2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADyxy8AAP//YSoDD5pLB7MAAAAASUVORK5CYII=
pic.ShowImage()
は本来はブラウザで画像表示するための関数らしく、ローカルだとbase64 PNGで出力される。ブラウザで表示するには、URL欄に
data:image/png;base64,
の後に上記のIMAGE:
以降の文字列を続けて入力すれば良い。
今夏はちょっと難しかった
続き