この記事の目的
こちらのUdemyのコースを学んで重要だと思った部分や感想等をセクション毎に記録していきます。備忘録的な目的で記事を作成しているため、学習の途中で気になって調べたことや本筋とは関係ない内容も含まれています(都度「講座外」と言及)。..
本記事はセクション7まで。
そもそもなぜGoを学ぶのか
主な理由
主な理由は将来性、設計思想、学習のしやすさの3つ。
学習という観点だけで見るともっと良い言語はあるかもしれないけど、どうせなら将来性のある言語に少しでも親しんでおきたい。
そして設計思想で実用性を重視しているのが良いと思った。特にシンプルさを徹底的に追求していて、その結果可読性が高く、誰が書いてもコードの一貫性が保たれる点は学習においても大きなメリットに感じた。他にも静的型付け言語だけど動的型付け言語のような書きやすさを両立してるところも学習する上でポイントが高い。
(あとみんな言ってるけどGopherくんがかわいい。)
余談
複数言語を学ぶのは思考の幅を広げるのに重要という話もあるので、Goに限らず別の言語もしっかり学んでみたい。ちなみにこれまでサーバーサイド言語はPython、Java、C++をかすってたけど、どれも基本文法は書けます程度で応用力の欠片も無いのでこれからしっかり学んでいきたい。
参考
「Go言語らしさ」とは何か? Simplicityの哲学を理解し、Go Wayに沿った開発を進めることの良さ
Goらしさとは何なのか考える
やったこと
セクション3:変数
- Goにおける基本的な変数宣言を学んだ。
- 慣習として、関数外(パッケージレベル)での変数宣言は避ける(講座外)。
参考
セクション4:基本型
- Goの様々な型の特性を学んだ。
- Goにおけるエラー処理の考え方を学んだ(講座外)。
参考
Goのエラーハンドリングの考え方が良く分からない
Go言語の例外処理がないってのは結局どういうこと?
「例外」がないからGo言語はイケてないとかって言ってるヤツが本当にイケてない件
int型
- 以下のような演算もコンパイルエラーになる。
int型(環境依存) + int64型
配列型(+スライス型)
1. 配列型は要素数の変更が不可能(要素数も型の一部として定義される)。
array := [5]int{}
fmt.Printf("%T\n", array)
[5]int
2. 以下のように要素数を省略してリテラル値から長さを推論した場合も配列型になる。
[...]string{"a", "b"}
3. スライス型は要素数の変更が可能(可変長配列)
interfae型
1. Interface型は具体的な型を持たないため演算に使うとコンパイルエラーになる(型変換すれば演算できる)。
interface型の主な使い方等はセクション12で整理する。
文字列型
1. バッククォートで囲めば\n
を使わずに改行を直接を表現できる。
型変換
1. 基本的にint, int64, float64等の数値型間の変換は
型名(型変換したい変数/値)
で型変換できる。
2. 数値型と文字列型といった異なる種類間の型変換はstrconvパッケージを用いて行う。
3. 文字列型とバイト型間の型変換は以下のように記述
文字列型からバイト型 → []byte(変換したい文字列型の変数/値)
バイト型から文字列型 → string(変換したいバイト型の変数/値)
セクション5:定数
1. 定数の基本を学んだ。
2. 定数名の1文字目を大文字にすることで外部パッケージから呼び出せるようになる。呼び出さなくてもコンパイルエラーにはならない。定数に限らず外部パッケージから呼び出したい場合は1文字目を大文字にする仕様。
3. itoaは変数の連番は生成できない(定数のみ)。
セクション6:演算子
Goの演算子を学んだ。
セクション7:関数
関数
1. 関数は引数、返り値ともに複数扱える
2. 返り値は型の指定、変数の宣言ができる(返り値で変数宣言した場合Return の値は省略できる)。何をする関数なのかわかりやすいという利点がある。
関数を引数に取る関数
1. 流れ(とりあえず言葉で書いた)。
main関数内で関数を引数に取る関数を呼び出す時、引数に関数を渡す
↓
渡した先の関数(関数を引数に取る関数)は引数として渡された関数を関数内で`f()`等で呼び出す
↓
main関数内で関数を引数に取る関数を呼び出す時に、引数として渡した関数の内部の処理を実行する
この流れをコードで表すと以下。
func CallFunction(f func()) {
f()
}
func main() {
CallFunction(func() {
fmt.Println("CallFunction関数内のf()で呼び出されてこの処理が出力される。")
})
}
クロージャー
関数内のローカル変数は関数の実行完了時に破棄されるが、クロージャーに参照されている間は破棄されない。
クロージャーとは、以下のコードで言うところの”これ”の部分。
func Later() func(string) string {
// クロージャーによってキャプチャされる変数
var store string
//これ
return func(next string) string {
/*
ここで、このLater関数が返そうとしている無名関数(クロージャー)がstoreを参照することで、
storeはLater関数実行完了時に破棄されるローカル変数ではなく、
このクロージャーに属する変数になる(Later関数の実行が完了しても値は破棄されず保存されたまま)
*/
s := store // ここでクロージャーによってstoreがアクセスされキャプチャされる
store = next
return s // この場合一回目は空文字。
}
}
func main() {
f := Later()
fmt.Println(f("1回目の呼び出しで保存された文字列")) // 保存されている値は空文字
fmt.Println(f("2回目の呼び出しで保存された文字列")) // "1回目の呼び出しで保存された文字列"を出力
fmt.Println(f("3回目の呼び出しで保存された文字列")) // "2回目の呼び出しで保存された文字列"を出力
}
調べたこと(講座外)
- クロージャーにキャプチャされた変数はエスケープ解析によってヒープに割り当てられる(変数が関数のスコープを超えて参照されるとヒープに割り当てられる。)
- Goのエスケープ解析とは、変数を使うスコープ範囲を解析し、適切なメモリ領域(スタックかヒープ)に自動で割り当ててくれる仕組みのこと。これによって不要なヒープ割り当てを防いでメモリアロケーションとやらを最適化してくれる。
- ヒープはGoのガベージコレクターが、不要になったタイミングでメモリを解放する。
- クロージャーとは、関数の外側の環境と一体化した関数のこと。つまり、外側の変数をキャプチャしていない関数はクロージャーではなくただの関数(理解足りてないかも。今度確認)。
参考
クロージャーの理解
Go言語でクロージャーを使う
スターティングGo言語: Go1.6に対応
Go言語(golang)のクロージャ(closure)
エスケープ解析の理解
【Go】エスケープ処理とその解析
メモリの理解
Go言語でクロージャーを使う
スタックとヒープについて自分でまとめ直してRustの概念を理解する
メモリの4領域〜ヒープ領域・スタック領域・テキスト領域・静的領域〜
ジェネレーター
1. クロージャーを使ってジェネレーターを作れる。
2. 以下のようにクロージャーを複数生成することで、ジェネレーターを複数生成できる。
func integers() func() int {
i := 0
return func() int {
i++
return i
}
}
func main() {
generateIndex1 := integers()
fmt.Println(generateIndex1()) // 1
fmt.Println(generateIndex1()) // 2
fmt.Println(generateIndex1()) // 3
generateIndex2 := integers()
fmt.Println(generateIndex2()) // 1
fmt.Println(generateIndex2()) // 2
fmt.Println(generateIndex2()) // 3
}
3. 各クロージャーのiはそれぞれ別々のヒープメモリを参照しているため、generateIndex2
には新たなクロージャーが生成され、新たなジェネレーターを生成できる。
最後に
Goの慣習等は都度把握するより、一度言語の基本を一通り理解してから勉強した方が効率の面では良いと感じた。