###A Tour of Go
#Methods
関数
引数 | func ScaleFunc(v Vertex, i int) | func ScaleFunc(v *Vertex, i int) |
---|---|---|
変数(n) | ◯ | × |
ポインタ(*n) | ◯ | × |
アドレス(&p) | × | ◯ |
###メソッド
| レシーバ| func (v Vertex)) | func (v *Vertex) |
|:-:|:-:|:-:|:-:|
| 変数(n) | ○| ◯ | ◯ |
| アドレス(&p) | ◯ | ◯ |
struct と type について
[Goを学びたての人が誤解しがちなtypeと構造体について](https://qiita.com/tenntenn/items/45c568d43e950292bc31 #)
type Vertex struct {
X, Y float64
}
type Name struct {
first, last string
}
func(n Name) Greet() string {
//変数.値でメソッド内で構造体の値を使うことができる
return "I'm " + n.first + " " + n.last
}
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())
n := Name{"first", "last"}
fmt.Println(n.Greed())
}
> 5
> I'm first last
#通常の関数としてもかける
type Name struct {
first, last string
}
func Greet(n Name) string {
return "I'm " + n.first + " " + n.last
}
func main() {
n := Name{"first", "last"}
fmt.Println(Greet(n))
}
> I'm first last
*任意の型にメソッドを宣言できる
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】
構造体も引数にされた関数内での値はコピーされたものなので、関数内の変更は関数外の元の構造体に反映されない。ポインタを引数に指定すれば、関数内の変更も元の構造体に反映される
[https://www.yoheim.net/blog.php?q=20170902](https://www.yoheim.net/blog.php?q=20170902 #)
[ポインタについて学ぶ(基本的なところ)](https://www.yoheim.net/blog.php?q=20170901 #)
関数の引数に値を渡した場合には、関数が実行される際に値がコピーされ、参照先のアドレスが変わります。
そのため、以下のように関数内で値を変更したとしても、呼び出し元の変数には値が反映されません。
####ポインタレシーバを使う理由
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.X)
v.Scale(10)
fmt.Println(v.X)
fmt.Println(v.Abs())
}
> 50
*ポインタを使わないと関数内の変更が反映されない
func (v Vertex) Scale(f float64) {
// そもそもここのvは元の構造体と違う別物、値はコピーされる
// コピーした値を変更し使用しているため、元の構造体の値は変更されない
// この関数内でのみ、変更された値を使うことができる
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(10)
//関数外なので、XとYの値は最初に宣言した3,4のままである
fmt.Println(v.Abs())
}
> 5
#ポインタレシーバ【2】
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}
// &vはvのアドレスであるため、エラーなる
// Scale(v, 10)ならエラーにならないが、vの値の変更は関数内のみ適用される
Scale(&v, 10)
fmt.Println(Abs(v))
// 例えば、 n := &として引数に*nとするとエラーにならないが、
//しかし同様にポインタレシーバでないので、Vertexの値は変わらない
Scale(*n, 10)
fmt.Println(Abs(v))
}
// Scaleメソッドはポインタレシーバではない
// つまりScaleメソッドは、ポインタを引数に取らないためエラーになる
> cannot use &v (type *Vertex) as type Vertex in argument to Scale
//Scale関数内では値は変わるが、関数外では元のVertexの値は変わらない
>5
- ポインタレシーバであった場合は、レシーバ(呼び出し元)が変数でもポインタでもok(変数の場合は自動で"&変数"として解釈してくれる)
type Vertex struct {
X, Y float64
}
//*Vertex型(Vertexへのポインタである *Vertex型)
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を"&v"として自動で解釈してくれている)
v.Scale(2)
fmt.Println(v)
ScaleFunc(&v, 10)
fmt.Println(v)
// もちろん、こちらはアドレスを渡しているので、ok
p := &Vertex{4, 3}
p.Scale(3)
fmt.Println(v)
ScaleFunc(p, 8)
fmt.Println(v)
fmt.Println(v, p)
}
#ポインタレシーバ【3】
・復習
"&変数"でアドレスを取得
"*変数"でアドレスを元に参照先の値を取得
//関数1
// 引数に変数を指定しているのに、ポインタをいれるとエラー
func Scale(v Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v:= Vertex{4, 3}
fmt.Println(Scale(&v))
}
//関数2
//関数の引数にポインタを指定したら、関数の引数は変数でもポインタでもOK
func Scale(v *Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
n = Vertex{5, 4}
fmt.Println(Scale(n))
v:= &Vertex{4, 3}
fmt.Println(Scale(v))
}
//メソッド
//メソッドを設定時、レシーバに変数を指定したら、メソッド実行時のレシーバは変数はもちろん、ポインタ変数でもOK
//・・・レシーバ(メソッドの呼び出し元、今回はVertex)
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}
//レシーバ(呼び出し元)が変数vなのでOK
fmt.Println(v.Abs())
fmt.Println(AbsFunc(v))
p := &Vertex{4, 3}
// レシーバがポインタ変数(p=&Vertex)でもOK
fmt.Println(p.Abs())
fmt.Println(AbsFunc(*p))
}