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

テクノロジー

Go言語(通称Golang)をやってみることにしました。
PythonやPHP、JavaScriptなどはわりと頻繁に使っていますが、
Goは"聞いたことがある"止まりでした。

ということで、GW休みも生かして勉強することに。


インストール

Go言語のページからMSIをダウンロード→実行。

最近のGitみたいに色々聞かれることはなく、なんとなくでインストールできました。

Hello Worldに至るまで

ファイル作成→VSCode拡張インストール

とりあえず作業用ディレクトリを作って、その中に「helloworld.go」という名前のファイルを作ってみました

これをVisualStudioCodeで開くと、Goの拡張機能をオススメされたので、従うままに入れてみることに。

その後もなんか「アレコレが足りないよ!入れてね!」という通知が出るので、一旦全部ぶち込む。

シンプルなHello World

package main

import "fmt"

func main() {
	fmt.Println("Hello, World!")
}

たぶん一番シンプルなのがコレ。

Goはすべての関数が何かしらの「パッケージ」に属している、と。

Goのエントリーポイント(最初に実行される関数)は mainパッケージ内のmain関数 とのこと。

んで、その中でfmtパッケージ内のPrintlnを実行している、と。

Printlnの仲間にPrintとPrintfがいるみたい。
それぞれの機能の差はJavaと同じっぽい?ので割愛。

実行

いずれ、VSCodeのF5でも実行できるようにするけど、一旦はアナログにコマンド入力で実行してみましょう。

go run helloworld.go で、即時にコンパイルして実行してくれました。

コンパイルして実行形式にしたいなぁ、ってときは go build helloworld.go

うまくいけば、同一フォルダに「helloworld.exe」が生まれます。

これをコマンドラインから実行したら、ちゃんとHello Worldと言ってくれる。
コンパイルがラクですね。
(コンパイル言語を使うのが久しい人の感想)

基本文法でお遊び

以下、main()の中を色々いじっていく。

変数

Hello Worldを変数化してみよう。

func main() {
	var hw string = "Hello, World!"
	fmt.Println(hw)
}

変数宣言のキーワードはvar
var [変数名] [型] = [初期値]
もちろん、イコールから先、初期値はなくてもよい。

初期値から型が自明なら、型宣言は省略できる。

func main() {
	var hw = "Hello, World!"
	fmt.Println(hw)
}

初期値があれば、varすら省略できる。
ただしその場合、=:=に変える必要がある。イコールの前にコロン。

func main() {
	hw := "Hello, World!"
	fmt.Println(hw)
}

varはブロックでまとめて一気に宣言することもできる。

func main() {
	var (
		h string
		w string
	)
	h = "Hello,"
	w = "World!"
	fmt.Println(h, w)
}

ちなみにfmt.Println()に複数の引数を与えると、半角スペース区切りで出してくれるっぽい。

変数は定義時に予め初期化されているので、何も代入せずにいきなり使うことが可能。
文字列型なら""、数値型なら0、真偽型なら偽が初期値。

定数

キーワードはconst

func main() {
	const japanese string = "こんにちは、世界。"
	const (
		h string = "Hello,"
		w string = "World!"
	)
	fmt.Println(japanese)
	fmt.Println(h, w)
}

定数なので、後から変更しようとするとコンパイルエラーになります。

ちなみに未使用変数があればコンパイルエラーになりますが、未使用定数はエラーになりません。

キーワードiotaを初期値として使えば、連番で定数を作成できます。

0から始まり、constブロックの終わりまで1ずつインクリメントされていきます。

C++のstd::iotaと同じですかね。

func main() {
	const (
		sun = iota
		mon
		tue
		wed
	)
	const (
		thu = iota
		fri
		sat
	)
	fmt.Println(fri)
}

上の例の場合、sunは0、monは1、tueは2、wedは3。
thuは改めて0、friは1、satは2。

ポインタ

C言語で寒気が出たやつ。
猫でも分かる本を読んでいた小学生時代の自分は、猫未満だった。

変数宣言時に型名の頭に*をつけると、ポインタ型の扱いになる。

アドレス番地を取得するときは、変数名の頭に&をつける。
値を取得するときは、変数名の頭に*をつける。

func main() {
	// var1は値
	var var1 int = 10
	// var2はvar1を指し示したポインタ
	var var2 *int = &var1

	fmt.Println(var1)  // 10
	fmt.Println(var2)  // 0xc000014088
	fmt.Println(*var2) // 10

	var1 = 20

	// var1を変えると、var2が示す先の値も変わる
	fmt.Println(var1, *var2) //20 20
}

関数

main()を見れば分かる通り、funcがキーワードで、{}内に処理を書きます。

package main

func main() {
	greeting()
}

func greeting() {
	const ret = "hello"
}

↑実行しても何も起きない。

引数のところは値 型名の順。戻り値の型は)と{の間に書く。

package main

import "fmt"

func main() {
	fmt.Println(greeting("Hello!"))
}

func greeting(ret string) string {
	return ret
}

引数はもちろん、戻り値も複数書ける。複数書く場合は、戻り値エリアもカッコで囲む。
戻り値複数は事故の元やな…

package main

import "fmt"

func main() {
	fmt.Println(greeting("Hello!", "World!!"))
}

func greeting(ret1 string, ret2 string) (string, string) {
	return ret1, ret2
}

名前付き戻り値

戻り値欄には名前をつけられる。その名も「名前付き戻り値」。

package main

import "fmt"

func main() {
	fmt.Println(greeting1()) // こんにちは新世界
	fmt.Println(greeting2()) // こんにちは新世界
	fmt.Println(greeting3()) // こんにちは
}

// ↓おとなしいほう
func greeting1() string {
	message1 := "こんにちは"
	message2 := message1 + "新世界"
	return message2
}

// ↓名前付き戻り値
func greeting2() (message2 string) {
	message1 := "こんにちは"
	message2 = message1 + "新世界" // 戻り値欄で先に宣言してるので := ではダメ
	return
}

// ↓名前付き戻り値を書いておきながら、それを戻さない
func greeting3() (message2 string) {
	message1 := "こんにちは"
	message2 = message1 + "新世界"
	return message1 // こうしておくと戻り値は「こんにちは新世界」ではなく「こんにちは」になる
}

名前付き戻り値を活用(greeting2()のほう)したほうが、名前がない戻り値よりもメモリ効率がとてもよいらしいので、こっちを使うといいっぽいです。

FORループとIF分岐

for文はよくある形。丸括弧は不要。
for 初期化; 継続条件; 周回後処理 { … }

if文もよくある形。こっちも丸括弧は不要(不要なのに入れてるとVSCodeさんに消される)

どっちも{}は必要。処理が1行であれ、{}は端折れないみたい。

func main() {
	var ret string
	for i := 1; i <= 30; i++ {
		ret = ""
		if i%3 == 0 {
			ret += "Fizz"
		}
		if i%5 == 0 {
			ret += "Buzz"
		}
		if ret == "" {
			ret = fmt.Sprint(i)
			// goは暗黙的型変換ができないため、明示的に変換してあげる
			// 文字列型にするときはfmt.Sprint()を使用する。
		}
		fmt.Println(ret)
	}
}

↑みんな大好きFizzBuzz

for文は、継続条件以外は端折れます。
また、if文は当然elseもあります。

func main() {
	var onakasuita = true
	for onakasuita {

		if onakasuita {
			fmt.Println("カレーをお腹に入れよう")
			onakasuita = false
		} else {
			fmt.Println("満腹だから、もう入らないよ")
		}
	}
}

↑これを書いているのは、まさにカレーを作ってるときだった。

ちなみにwhile文は無いとのこと。

Switch分岐

switch{…}の中に、case 条件:を書いて、その後ろの行に処理を書く。

func main() {
	curry := "甘口"

	switch curry {
	case "甘口":
		fmt.Println("あっ、お子様ですね!仲間!")
	case "中辛":
		fmt.Println("あっ、立派ですね!")
	case "辛口":
		fmt.Println("あっ、めっちゃすごいですね!")
	case "激辛":
		fmt.Println("正気か?")
	default:
		fmt.Println("カレー、食べないんですか…?")
	}
}

Goのswitch-case文は、breakがなくても1ブロックだけ実行して終わるみたい。

また、switchの後に条件を書かずにいると、switch trueと同じ扱いになる。
どういうことかというと、シンプルなif文っぽくなる。

func main() {
	watashi := "髪"
	switch {
	case watashi == "神":
		fmt.Println("私が神だ")
	case watashi == "髪":
		fmt.Println("また髪の話してる")
	default:
		fmt.Println("一般ピーポー")
	}
}

配列

変数宣言時に、型名の前に[]で要素数を書くと配列になります。
宣言と同時に初期化するときは、型名の後に{}で初期値を書きます。
要素数が初期化のときに分かる時は[...]で要素数を省けます。

func main() {
	// 宣言と代入を分けるときは、こう。
	var arr1 [3]string
	arr1[0] = "a"
	arr1[1] = "b"
	arr1[2] = "c"

	// 宣言同時代入時は、こう。
	// [...]は個数未定のときに使える。
	// 未定とはいえ、配列要素数は後から変えられない。
	arr2 := [...]string{"a", "b", "c"}
	fmt.Println(arr2[2])
}

なお、Goの配列は「複数の要素を持った値」とのこと。
多くの言語ではポインタだけれど、Goではあくまで値です。

クソデカ配列を関数に引数として渡すと、クソデカ配列がメモリ上にまるまる増えるということ。

あと、これは融通がきかない配列あるあるだけれど、配列要素数は後から変えられない、と。

スライス

配列と違いポインタで持つモノ。こっちのほうが他言語の配列っぽい。

func main() {
	// 配列宣言時の[]は空にするとスライスになる
	slice1 := []string{"sun", "mon", "tue"}
	// fmt.Println(slice1[3]) // ※3番目がないのでランタイムエラーになる

	// 別変数を掘っ立てて、appendで要素を追加する
	// ※appendの戻り値は新しいスライス
	slice11 := append(slice1, "wed")
	fmt.Println(slice11[3]) // 答え:wed

	// 予め幅を決めて予約しておきたいときはこうなる
	slice2 := make([]string, 3)
	fmt.Println(slice2[2])

	// そもそも何も宣言せずにスライスを作れる
	// ただ、このslice3には後から要素を入れることはできないので
	// 使いみちとしては、何があるんだろう?
	var slice3 []string
	fmt.Println(slice3)
}

ただ結局配列の仲間なので、好き勝手に要素を増やすことはできないみたい。