#はじめに
調べれば星の数ほどでてくるGoの基礎をあえて自分用にまとめました。
現業のフロントエンドだけではなくバックエンドにも磨きをかけたいというのがGoの学習目的です。
基礎学習後は個人サービスつくって実践していきたいなと思っています。
#Structs(構造体)
Goにはオブジェクト指向言語におけるClassがなく、その代わりにStructs(構造体)というものが存在します。
Structs型の変数を定義すると、Structsに定義されている各フィールドに必要なメモリ領域が確保され、それぞれのフィールドは型に合わせた初期値をとります。
例えば、以下のようにperson
というStructsをつくり、初期値を設定せずにそのまま出力してみます。
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を以下のようにネストして定義することもできます。
type contactInfo struct {
email string
zipCode int
}
type person struct {
firstName string
lastName string
contactInfo contactInfo
}
#ポインタ
ポインタはメモリのアドレス値などの情報が格納された変数のことをいいます。
C言語をやっていた方は必ず挫折したであろうアイツのことです。
なんでそのアイツがここででてくるの?という疑問が湧くと思うので、firstName
を更新するメソッドupdateName()
の実装を通して謎を紐解いていきます。
p person
をレシーバとしたupdateName()
メソッドを追加し、firstName:ケイン
になるかどうか試してみます。
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は構成されます。
Sliceの変数を別の変数に代入するとSliceのコピーが渡されますが、参照先のArrayは変わりません。
そのため、一方の値を変えたらもう一方の値も同様に書き換わります。
#Map(連想配列)
Mapはキーで要素を指定するデータ構造で、JavaScriptでいうオブジェクトにあたります。
Structsとの違いとしては以下のことが挙げられます。
- すべてのキー、値が同じ型をもつ
- 参照型(参照渡しのため元の変数に影響がある)
以下のサンプルは、色をキー、色コードを値にもったMapを作成してprintMap()
で出力したものです。
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であるenglishBot
とspanishBot
には自動的にgetGreeting()
が実装されます。
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
#参考資料