LoginSignup
2
0

More than 1 year has passed since last update.

【Go】学習メモ① ~Structs, ポインタ, Slice, Map, Interface~

Posted at

はじめに

調べれば星の数ほどでてくるGoの基礎をあえて自分用にまとめました。
現業のフロントエンドだけではなくバックエンドにも磨きをかけたいというのがGoの学習目的です。
基礎学習後は個人サービスつくって実践していきたいなと思っています。

Structs(構造体)

Goにはオブジェクト指向言語におけるClassがなく、その代わりにStructs(構造体)というものが存在します。
Structs型の変数を定義すると、Structsに定義されている各フィールドに必要なメモリ領域が確保され、それぞれのフィールドは型に合わせた初期値をとります。

例えば、以下のようにpersonというStructsをつくり、初期値を設定せずにそのまま出力してみます。

main.go
package main

import "fmt"

type person struct {
    firstName string
    lastName string
}

func main() {
    mrSasuke := person{}
    mrSasuke.print()
}

func (p person) print() {
    fmt.Printf("%+v", p)
}

すると、以下のように各フィールドの初期値が空文字""として設定されます。

{firstName: lastName: }

また、Structsを以下のようにネストして定義することもできます。

main.go
type contactInfo struct {
    email string
    zipCode int
}

type person struct {
    firstName string
    lastName string
    contactInfo contactInfo
}

ポインタ

ポインタはメモリのアドレス値などの情報が格納された変数のことをいいます。
C言語をやっていた方は必ず挫折したであろうアイツのことです。

なんでそのアイツがここででてくるの?という疑問が湧くと思うので、firstNameを更新するメソッドupdateName()の実装を通して謎を紐解いていきます。

p personをレシーバとしたupdateName()メソッドを追加し、firstName:ケインになるかどうか試してみます。

main.go
func main() {
    mrSasuke := person {
        firstName: "勝巳",
        lastName: "山田",
        contactInfo: contactInfo{
            email: "yamada@gmail.com",
            zipCode: 94000,
        },
    }

    mrSasuke.updateName("ケイン")
    mrSasuke.print()
}

func (p person) updateName(newFirstName string) {
    p.firstName = newFirstName
}

func (p person) print() {
    fmt.Printf("%+v", p)
}

出力結果をみると、firstName:勝巳のままで値が更新されていません。何故だ...

{firstName:勝巳 lastName:山田 contactInfo:{email:yamada@gmail.com zipCode:94000}}

これはupdateName()を実行するときに、レシーバのStructsであるpersonが別のメモリのアドレスにコピーされるためです。
このように、引数に変数のコピーを値として渡すことを値渡しと呼びます。

値を更新するためには同じメモリのアドレスに格納されている値を書き換えればよく、このときに使うのがポインタです。
updateName()を以下のように書き換えて、改めて更新処理を行ってみます。

func main() {
...
    mrSasukePointer := &mrSasuke
    mrSasukePointer.updateName("ケイン")
    mrSasuke.print()
}

func (pointerToPerson *person) updateName(newFirstName string) {
    (*pointerToPerson).firstName = newFirstName
}

すると書き換わりました。やった!

{firstName:ケイン lastName:山田 contactInfo:{email:yamada@gmail.com zipCode:94000}}

*int*personのように型の先頭に*がついたものをポインタ型と呼び、メモリ上のアドレスを記憶する変数の型となります。
また、*pointerToPersonのように引数の先頭に*がつくとアドレスの値を指し、&mrSasukeのように先頭に&がつくとメモリのアドレスを指します。

また以下の部分をmrSasuke.updateName("ケイン")と省略することもできます。
このように、レシーバの型と違ってもGoだと通るようです。

    mrSasukePointer := &mrSasuke
    mrSasukePointer.updateName("ケイン")

ArrayとSlice

GoにおけるArrayは実体的な値として扱います。
Array変数を別の変数に代入するときは値渡し(変数のコピーを渡す)となり、それぞれのArrayがもつ値は別のメモリアドレス上に存在することになります。
つまり、一方の値を変えても、もう一方の値には影響がありません。
また、Arrayは固定長(リサイズできない)のデータ構造であり、型情報はサイズまで含めて扱われます。

一方、Sliceは可変であり、Array(基底配列)への参照(ポインタ)をもつデータ構造です。
Arrayへの参照、要素の長さ(length)、要素の最大長(capacity)の3つからSliceは構成されます。
image.png

Sliceの変数を別の変数に代入するとSliceのコピーが渡されますが、参照先のArrayは変わりません。
そのため、一方の値を変えたらもう一方の値も同様に書き換わります。

Map(連想配列)

Mapはキーで要素を指定するデータ構造で、JavaScriptでいうオブジェクトにあたります。

Structsとの違いとしては以下のことが挙げられます。

  • すべてのキー、値が同じ型をもつ
  • 参照型(参照渡しのため元の変数に影響がある)

以下のサンプルは、色をキー、色コードを値にもったMapを作成してprintMap()で出力したものです。

main.go
func main() {
    colors := map[string]string{
        "red": "#ff0000",
        "green": "#4bf745",
        "white": "#ffffff",
    }
    printMap(colors)
}

func printMap(c map[string]string) {
    for color, hex := range c {
        fmt.Println("Hex code for", color, "is", hex)
    }
}
Hex code for green is #4bf745
Hex code for white is #ffffff
Hex code for red is #ff0000

Interface

Interfaceとはメソッドの型だけを定義した型のことです。
Goでは、Interfaceの中にあるメソッド名と同一のメソッドが全て実装されているStructsに自動的に実装されます。

以下コードを例にすると、botというInterfaceでgetGreeting()の型を定義することで、StructsであるenglishBotspanishBotには自動的にgetGreeting()が実装されます。

main.go
type bot interface {
    getGreeting() string
}

type englishBot struct{}
type spanishBot struct{}

func main() {
    eb := englishBot{}
    sb := spanishBot{}

    printGreeting(eb)
    printGreeting(sb)
}

func printGreeting(b bot) {
    fmt.Println(b.getGreeting())
}

func (englishBot) getGreeting() string {
    return "Hi There"
}

func (spanishBot) getGreeting() string {
    return "Hola"
}

そのため、例えばfunc (spanishBot) getGreeting() stringが記述されていないと、printGreeting(sb)sbの部分で「printGreetingの引数でbotの値としてsbは使用できません」といったエラーが発生します。

var sb spanishBot
cannot use sb (variable of type spanishBot) as bot value in argument to printGreeting: missing method

参考資料

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0