Go言語でGO(勉強記録2)

テクノロジー

Go言語でGOシリーズの2回目です。


基本文法の続き

RANGE反復

for文とrangeを組み合わせると、いわゆるforeachができるようです。

forの継続条件部分で、2つ変数を定義して、右辺でrange [配列]とする感じ。

定義する変数の1つめは配列番号、2つめは値が入ります。

func main() {
	array := [...]string{"犬", "猫", "モルモット"}
	for i, v := range array {
		fmt.Println(i, v)
	}
}

ちなみにrangeの左辺の変数を1つにすると、配列番号だけ取れます。
値ちゃうんかい!!

func main() {
	array := [...]string{"犬", "猫", "モルモット"}
	for a := range array {
		fmt.Println(a)
	}
}

値だけ取りたいときは、配列番号が入る変数名を _ にしましょう。
Pythonと同じですね。

package main

import "fmt"

func main() {
	array := [...]string{"犬", "猫", "モルモット"}
	for _, a := range array {
		fmt.Println(a)
	}
}

IF文の簡易ステートメント

if文の条件部の前に、処理を書くことができます。

func main() {
	if yama := "山"; yama == "山" {
		fmt.Println("yama is", yama) // yama is 山
	}
}

スコープはifのブロック内に限ります。

func main() {
	yama := "川"
	if yama := "山"; yama == "山" {
		fmt.Println("yama is", yama) // yama is 山
	}
	fmt.Println("yama is", yama) // yama is 川
}

ifのブロック内に限るとはいえ、外部の変数を上書きしたら反映されますけどね。
(IF文の簡易ステートメントの :== に変えた)

func main() {
	yama := "川"
	if yama = "山"; yama == "山" {
		fmt.Println("yama is", yama) // yama is 山
	}
	fmt.Println("yama is", yama) // yama is 山
}

マップ

連想配列。よく使いそう。

よく使いそうだからか、作り方がたくさんあるみたいで、サイトによって書き方が違うのが非常に厄介です。
でも、使うのは多分2番目かな。

1.長いやつ

たぶん一番長いけどシンプルなやつ。

var 変数名 map[キーの型]値の型 = map[キーの型]値の型{}

func main() {
	var pets map[string]string = map[string]string{}
	fmt.Println(pets) // map[]
	pets["dog"] = "犬"
	fmt.Println(pets) // map[dog:犬]
}

2.短いやつ

1の書き方ができるなら、 := を使う書き方もできるはず。
できます。

変数名 := map[キーの型]値の型{}

func main() {
	pets := map[string]string{}
	fmt.Println(pets) // map[]
	pets["dog"] = "犬"
	fmt.Println(pets) // map[dog:犬]
}

3.makeを使って短縮

スライスのところでもチラッと出てきたmakeを使うやつ。

makeの構文は make(作る構造体, 要素数) だったので、これをスライスに応用すると、こうなる。

func main() {
	pets := make(map[string]string)
	fmt.Println(pets) // map[]
	pets["dog"] = "犬"
	fmt.Println(pets) // map[dog:犬]
}

,要素数 の部分は、あってもなくてもいいみたい。
たぶん事前に要素数を予約しておけば、スムーズにアイテムを追加できるんだと思います。

4.最初から初期値を放り込む

1と2に出てきた意味深な{}、この中に値を入れれば初期値になるのはご承知の通り。

func main() {
	pets := map[string]string{"dog": "犬", "cat": "猫"}
	fmt.Println(pets) // map[cat:猫 dog:犬]
	pets["dog"] = "オオカミ"
	fmt.Println(pets) // map[cat:猫 dog:オオカミ]
}

その他

存在しない要素を参照しようとすると、空文字が帰ってきます。
ランタイムエラーにはなりません。

func main() {
	pets := map[string]string{"dog": "犬", "cat": "猫"}
	fmt.Println(pets["dog"])  // 犬
	fmt.Println(pets["bird"]) // (空文字)
}

構造体

いわゆるクラスの概念っぽい何か、らしい。
Goにはオブジェクト指向言語でいうクラスは存在しないとのこと。

構造体の定義

type 構造体名 struct{…} の形。

package main

type pet struct {
	english  string
	japanese string
	cry      string
}

func main() {
}

初期化

オブジェクトを生成する、に相当するやつ

1.varで変数を定義してからフィールドに入れる

シンプルやね

package main

import "fmt"

type pet struct {
	english  string
	japanese string
	cry      string
}

func main() {
	var dog pet
	dog.english = "Dog"
	dog.japanese = "犬"
	dog.cry = "(∪^ω^)わんわんお!"
	fmt.Println(dog.english, dog.japanese, dog.cry)
	// Dog 犬 (∪^ω^)わんわんお!
}

しかしこのやり方だと、ポインタが生成されない?様子。

構造体を作る専用関数(コンストラクタ関数)を仕立てるときに、よく使うっぽい。

コンストラクタ関数は戻り値として、生成した構造体のポインタを返してやるものっぽい。
(2番目の生成方法だとポインタが戻ってくる)

package main

import "fmt"

type pet struct {
	english  string
	japanese string
	cry      string
}

func newPet(english string, japanese string, cry string) *pet {
	// var pet pet
	// pet.english = english
	// pet.japanese = japanese
	// pet.cry = cry
	// // ここまでを1行でまとめると、↓になる
	pet := pet{english: english, japanese: japanese, cry: cry}
	return &pet
}

func main() {
	dog := newPet("Dog", "犬", "わんわんお")
	fmt.Println(dog) // &{Dog 犬 わんわんお}
}

newPetという関数で、petを作って、そのポインタを返すようにしてあげた。

2.newを使う

Javaっぽい。

package main

import "fmt"

type pet struct {
	english  string
	japanese string
	cry      string
}

func main() {
	cat := new(pet)
	cat.english = "cat"
	cat.japanese = "猫"
	cat.cry = "にゃーん"
	fmt.Println(cat) // &{cat 猫 にゃーん}}

	// newだと1行で初期化できないが、↓だと1行で初期化できる
	human := &pet{"human", "ヒト", "にゃーん(社会性フィルター)"}
	fmt.Println(human) // &{human ヒト にゃーん(社会性フィルター)}
}

メソッド

クラスじゃないけど、メソッドが定義できる。

ただ構造体の中にfuncを書くのではなく、「レシーバ引数」というのに小細工をする形になります。

package main

import "fmt"

type pet struct {
	english  string
	japanese string
	cry      string
}

//     ↓ここがレシーバ
func (p pet) crying() string {
	p.cry = "ワン!"
	return p.cry
}

func main() {
	human := &pet{"human", "ヒト", "にゃーん(社会性フィルター)"}
	fmt.Println(human)          // &{human ヒト にゃーん(社会性フィルター)}
	fmt.Println(human.crying()) // ワン!
	fmt.Println(human)          // &{human ヒト にゃーん(社会性フィルター)}
}

上のように書くと、crying()へは構造体を値渡しするようです。
(humanのPrintln結果が変わってない)

参照渡しするには、レシーバ部分の型名の頭に*をつけます。

package main

import "fmt"

type pet struct {
	english  string
	japanese string
	cry      string
}

//      ↓ここに*をつける
func (p *pet) crying() string {
	p.cry = "ワン!"
	return p.cry
}

func main() {
	human := &pet{"human", "ヒト", "にゃーん(社会性フィルター)"}
	fmt.Println(human)          // &{human ヒト にゃーん(社会性フィルター)}
	fmt.Println(human.crying()) // ワン!
	fmt.Println(human)          // &{human ヒト ワン!}
}

ちなみに、上の例ではhumanはポインタが格納されている(いわゆる*T型)のですが、レシーバが値レシーバ(*なし)だと、勝手に値のコピーを取って渡す挙動をするようです。
型は暗黙変換してくれないけれど、なぜかレシーバは暗黙変換するみたい。

ポインタレシーバと値レシーバ、どっちがいいんだってのは、ここを読みましょう。

Javaやってた身としては、ポインタ渡しのほうがスッキリするような…いやそうでもないのか…?

埋め込み

Goにはクラスがないので当然だけれど、継承という概念もない。

構造体には継承という概念がない。
親構造体とか子構造体なんてものは、ない。

似たようなことはできる。それが「埋め込み」。

package main

import "fmt"

type human struct {
	name string
}

type worker struct {
	human
	job string
}

func (h *human) say() string {
	return "My name is " + h.name
}

func main() {
	you := &human{"Tanaka"}
	me := &worker{}
	me.name = "Sato"
	me.job = "SE"

	fmt.Println(you.say()) // My name is Tanaka
	fmt.Println(me.say())  // My name is Sato
}

見栄え上はhumanの子構造体がworkerだけれど、そんな概念はない(is-aの関係でしかない)。

でも、まあ、humanをレシーバとする関数をworkerが呼んでも上手くいくみたいなので、それっぽい使い方はできそう。