始めに
この記事は私がA Tour of Goをやっていて詰まったところをメモしたものです。読みづらい部分も多いかもしれませんが少しでも役に立てばと思います。
また間違いなどあればコメントで優しく教えてくださると幸いです。
また並行処理の記事は別途投稿するつもりです。
ポインタ
ポインタの型は*Tだけど、変数に*をつけると値が取り出される。
メモリアドレスを使いたかったら&をつける
package main
import "fmt"
func main() {
i:=1
var a *int
a = &i //iのアドレスを代入
fmt.Println(*a) //1(iの値)
fmt.Println(a) //0xc000010070(iのアドレス) &iそのまま
fmt.Println(&a) //0xc00004a040(aのアドレス)
}
ポインタを通して値の書き換えが可能。仕組みはシャローコピーの記事を参考。
勿論変数の値をポインタを通して書き換えても、メモリのアドレスは変わらない。
package main
import "fmt"
func main() {
i:=1
var a *int
a = &i
fmt.Println(a) //0xc000098040
*a = 10 // ポインタaを通してiへ値を代入する
fmt.Println(i) //10
fmt.Println(&i) //0xc000098040(値を変えてもアドレスは不変)
}
スライス(容量)
これは次の一文をちゃんと読んでいれば全く躓かないのですが、僕はここを読み飛ばしてしまいかなり混乱していたので説明します。
スライスの容量は、スライスの最初の要素から数えて、元となる配列の要素数です。
package main
import "fmt"
func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)
// Slice the slice to give it zero length.
s = s[:0]
printSlice(s)
// Extend its length.
s = s[:4]
printSlice(s)
// Drop its first two values.
s = s[2:]
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
定義はちゃんと読みましょう(自戒)
またa[1:4]は次の式は a の要素の内 1 から 3 を含むスライスを作ります。
これは最初の要素は含むが、最後の要素は除いたものとなっているので注意
容量の増え方
スライスに新しい要素を追加するにはappendを使いますが、容量を超えた際の増え方が特徴的だったので、メモします。
具体的には容量を超えるたびに、容量が次のように2倍になっていきます。
var s []int //len=0 cap=0 []
s = append(s, 0) //len=1 cap=1 [0]
s = append(s, 1) //len=2 cap=2 [0 1]
s = append(s, 2) //len=3 cap=4 [0 1 2]
s = append(s, 3) //len=4 cap=4 [0 1 2 3]
s = append(s, 4) //len=5 cap=8 [0 1 2 3 4]
s = append(s, 5) //len=6 cap=8 [0 1 2 3 4 5]
s = append(s, 6) //len=7 cap=8 [0 1 2 3 4 5 6]
s = append(s, 0) //len=8 cap=8 [0 1 2 3 4 5 6 7]
s = append(s, 0) //len=9 cap=16 [0 1 2 3 4 5 6 7 8]
実際にlen(s)やcap(s)で長さと容量を確かめられるので、一度プリント文で出力してみるとより理解できると思います。
メソッド
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
書き方的にはAbs関数はVertexのレシーバを持つという意味合いが強いが
(同じパッケージ内では)
逆に言うとVertex型はAbsメソッドを持つといえる。
このようにメソッドを使えば型に付随する機能として関数をもたせることができ、オブジェクト指向的に書くことが可能となる。
ポインタレシーバ
v *Vertexにするとポインタレシーバになり、レシーバが指す変数を変更できる。
またポインタレシーバであればメソッドの呼び出しのたびに変数をコピーせずに済む。
毎回コピーするのは構造体が大きければ負荷がかかる。
インターフェイス
個人的に一番理解しづらかったです。ただメソッドの部分で太字として書いた事を理解してからは割とすんなり理解できました。
GoのインターフェイスはJavaでのimplementsのように明示的には宣言しません。
代わりに全てのメソッドのシグネチャを一致させる事によって実装します。
シグネチャとはこの記事ではメソッド名、引数、戻り値を指します。
全てのと表記したのは、メソッドは1つ以上含むことができるからです。
type Abser interface {
Abs() float64
}
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
上のコードを例にすると、Abserがインターフェイスで、メソッド名のAbsと引数なし、戻り値がfloat64とインターフェイスと一致しているので、インターフェイスを実装しています。
注意して欲しいのが、このときインターフェイスを実装しているのは*Vertex型 です。
先程シグネチャにレシーバを含めなかったのは レシーバの型がインターフェイスを実装する型となるためです。
(また今回は*Vertex型にインターフェイスを実装しているので、Vertex型の変数ではAbsを呼び出すことは当然できません。)
error
インターフェイスのよくある例としてerror型が挙げられます
errorは以下のようになっています。
type error interface {
Error() string
}
新しく構造や型を定義して、インターフェイスを実装して使います。
type NewError struct {
Message string
}
func (e *NewError) Error() {
return fmt.Sprintf("Error: %s",e.Message)
}
func run() error {
return &MyError{
time.Now(),
"it didn't work",
}
}
func do() error {
return &NewError{
"it didn't work",
}
}
func main(){
if err := do(); err != nil {
fmt.Println(err)
}
エラーハンドリングは、errがnilかどうかの分岐で行います。
先頭にifをつけて;で文を終了すれば簡潔に記述できます。
戻り値が2つ以上あっても同様です。
func test(){
if i,err := do(); err != nil {
fmt.Println(err)
}
}
出典