エンジニアもどきの雑記

メモ帳です。きっと。macアプリ配信windowsでもできるようになるまで絶対にメイン機にしないマン。特筆しない限り環境は【win10home,i5-7200u,8g,64bit,巨乳が好き】

GoLangやるpart8

前回
dddpga1.hateblo.jp

c言語初学生の鼻を叩きおってきたポインタとかそこらへんやる。
前にc#のこんな感じの記事を書いた時に少しだけ詳しくやったけど、
俺も正直まだよくわかってないと思う。

ポインタとは、メモリ上の型と値の場所。
ってとりあえず覚えてる。かも。


実際、他の言語の他の機能でもそうだけど、
これって何のためにあるの? ってのを理解しないと話にならない。
俺はいまだにjsのcloneを勢いで書いている(冗談だよ! マジだった時もあるけど

で、goのポインタは何に使われるかっていうと
ほぼcの互換だと思う。
フレームワークとか触るとわりと頻繁にでてはくるんだけど、
結局はそういうとこにたどり着いてる気がする。

main.go

package main

import "fmt"

var i int

func main() {
        p := &i
        i = 5

        fmt.Println(*p)

        *p = 10
        fmt.Println(i)
}


&*についてまず理解しないといけない。
まず*だが、このアスタリスクはポインタ型の宣言。型だ。型なんだ。
&の方はアドレス演算子演算子だ。演算子なんだ。

上記コードで見ていく。
var i int
まずはmain packageの変数としてint型のiを定義。

p := &i
は、int型のiを&を使って、ポインタ型を生成し、pに代入している。
どういうことか一旦、pを出力してみる。
pの直後にfmt.Println(p)を入れて実行

以下がでる

0x545e20

これが、iが割り当てられているメモリのアドレス(住所っていうけど位置の方がわかりやすい気がする)になる。

次のi = 5はまぁ今までやった通りで、iに対して5を代入している。
問題の次の行。
fmt.Println(*p)

これが、デリファレンスである。
ポインタ型の宣言が→var 変数名 *int
ポインタ型のデリファレンスが→*変数型

これがまぁ最初はわかりづらいづらいづらい。
変数pはp := &iで変数型が入ってる。なのでpの前にを付けることで
デリファレンスができる。

デリファレンスって何かっていうと、
ポインタ型が保持するメモリ上のアドレス(今回でいう0x545e20)を経由してデータ自体を参照する仕組み。

なので、fmt.Println(
p)の結果はiの中身になる。直前で5を代入しているので、まず5が表示される。

で、次。
*p = 5。ここでたった今5を表示したpに10を代入している。
そしてその次の行でfmt.Println(i)としている。そこで表示されるのは10

先述の通り、
pはデリファレンスでiに入っている値をpのiのアドレス経由で参照している。
そこに10を入れてるので、結果iに10が入っているってだけ。

所謂参照渡しの機能である。・・・よね?

例のごとくphpで言うけど、phpは引数に&を付けるだけで参照渡しを自動で行ってくれる。
それの内部的なものって考えるとすごいわかりやすい気がする。

とりあえずこれだけ。*と&だけわかってれば今はいいや。
配列で使ったりするともっとやっかいなんだけど、各々やろう。


構造体

struct
他でいうenumみたいな。

その前にtypeをやっとく。
これは型のエイリアスを指定できるって機能。

main.go

package main

import "fmt"

type myArr [3]int

var i myArr

func init() {
        i[0] = 1
        i[1] = 2
        i[2] = 3
}

func main() {
        fmt.Println(i)
}


実行

[1, 2, 3]


大事なのは

type myArr [3]int

var i myArr



type 名前 型で型のaliasができる。
なのでvar i myArrvar i [3]intと同義になる
以上。

俺の書いてきたどれでも当てはまるんだけど、もっと細かい仕様やら
条件やら沢山あるんだ。でもそれはここで書ききれない。

じゃあstructやる。typeが必要だったんだ。

定義

type type名 struct {
    //定義
}


実際に動きとかを見てみる。


main.go

package main

import "fmt"

type Point struct {
        X, Y int
}

func main() {
        poi := Point{1, 2}
        fmt.Println(poi.X)
        fmt.Println(poi.Y)

        poi.X = 10
        poi.Y = 20
        fmt.Println(poi.X)
        fmt.Println(poi.Y)

}


実行

1
2
10
20


みただけだとmapとやってることは変わらないかもしれないけど、
これが重要だったりする。特にgoだと。

構造体に構造体を入れこんだりもできるし、言ってしまえば日本語も使える。
わかりやすいリスト程度で最初はいいと思う。使っていく内に重要性がわかると思うし、
今からやるinterface{}でさらにわかると思う。

ゲームとかつくるとすごいわかりやすいんだけど、構造体を用意しておいて引数とかに渡したところで宣言した構造体の値が変化するわけではないので、先ほどの参照渡しでいろいろと値を渡してあげて、ってのが重要になる。作ろう。

さて、作るのには時間がかかるのでとりあえずnew演算子やる

new演算子


動作的には&と変わらないと思う。
指定した型のポインタ型を生成する機能、とのこと。

main.go

package main

import "fmt"

type Point struct {
        X, Y int
}

func main() {
        p := new(Point)

        p.X = 1
        p.Y = 2

        fmt.Println(*p)
}


実行

{1, 2}



メソッド


他でいう関数、とは少し違う。
任意の型に特化した関数を定義するための仕組み、とのこと。

func (レシーバー名 レシーバー型) 関数名 [返り値型] {
    //処理
}


レシーバーってなんぞやっていうと
上記した特化する型とそれの変数名になる。

とりあえずレシーバーの定義があるかないかでしか通常の関数と変わりない。

どういうことができるかってのが以下

main.go

package main

import "fmt"

type Point struct {
        X, Y int
}
type IntArr [3]int
type FloatSli []float64

func (p *Point) Test() {
        fmt.Println(*p)
}

func (i IntArr) Test() {
        fmt.Println(i)
}

func (f FloatSli) Test() {
        fmt.Println(f)
}

func main() {
        p := Point{1, 2}
        p.Test()

        ia := IntArr{1, 2, 3}
        ia.Test()

        fs := FloatSli{1.11, 2.22, 3.33}
        fs.Test()
}


実行

{1 2}
[1 2 3]
[1.11 2.22 3.33]


えぇ・・・同じ名前の関数が三つも・・・
しかもそれぞれ型に属して呼び出されているような・・・
ってなるよね

っていうのがメソッド。
型に対して定義がされて、型から呼び出されているような感覚。
この型の変数だった場合は一致するレシーバーの型を見て、って感じで。


そしてこれに付随するように型のコンストラクタってのがあるからやっておく。
type、構造体、メソッドときて頭がこんがらがる。

main.go

package main

import "fmt"

type Point struct {
        X, Y int
}

func NewPoint(x, y int) *Point {
        p := new(Point)
        p.X = x
        p.Y = y

        return p
}

func main() {
        fmt.Println(NewPoint(11, 22))
}


実行

&{11 22}


Point構造体に対して初期化を行ってる。
これどう便利なのかとかどうとかは下記でわかる気がする。
そもそもmain()内でhoge := Pointみたいなことができない。
以上。

やってみればわかる。書くんだ(説明ができない力不足でごめん


構造体とスライス。

便利。goを書く上でよく使われるパターンらしい(自分は技術力が足りな過ぎてあまり書いてない気がする


main.go

package main

import "fmt"

type Point struct {
        X, Y int
}

func main() {
        pt := make([]Point, 5)

        for _, p := range pt {
                fmt.Println(p.X, p.Y)
        }

}


実行

0 0
0 0
0 0
0 0
0 0


簡単に作れるよって話。



interface



本題。
個人的には学び始めてからこいつがなぜか一番やっかいだった。

interfaceとは、どんな型にも対応した型。
ってのが簡単にいうとなんだけど、当然ただそんな考えで理解はできない。
俺はできなかった。

本ほぼそのままだけど以下

main.go

package main

import "fmt"

type Stringify interface {
        ToString() string
}

type Person struct {
        Name string
        Age  int
}

func (p *Person) ToString() string {
        return fmt.Sprintf("%s(%d)", p.Name, p.Age)
}

type Car struct {
        Number string
        Model  string
}

func (c *Car) ToString() string {
        return fmt.Sprintf("[%s] %s", c.Number, c.Model)
}

func main() {
        vs := []Stringify{
                &Person{Name: "hoge", Age: 12},
                &Car{Number: "1234", Model: "5678"},
        }

        for _, v := range vs {
                fmt.Println(v.ToString())
        }
}


実行

hoge(12)
[1234] 5678


Stringify interfaceを定義。
Person構造体とCar構造体を宣言してある。それぞれがそれぞれの属性を持ってる。
そしてそれぞれにToStringメソッドを作成してやる。

それらをStrigifyで内包してやって、それらを実行してる。

簡単にいうと、別々の型をまとめて総称つけてあげたり、似たような属性を持たせるためのもの。
それぞれを呼べば処理的にはそれで済むが、こういうのも便利機能。


とりあえず、うん。
ここまでで終了。

もっとこれからgoをしっかりと、深く勉強していこう。
いい復習になりました。まだまだ勢いで書いてるとこはあるけど。

じゃあまた別の記事で。