Go言語には何度か入門してその都度なんとなく挫折してきましたが、いよいよそうも言ってられない状況になってきたので本腰を入れて入門します。
参考にした本はこちらです。
スターティングGo言語(松尾 愛賀)|翔泳社の本
なおこの本ではGo1.6を対象に書かれており、そのまま動かすためにはgo run
時にGO111MODULE=off
という環境変数を定義する必要があるようでした。
最小限のプログラム
これが最小限のプログラムである。
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello, World!")
}
実行方法はこちら。oオプションを指定しない場合カレントディレクトリの名前がファイル名になる。
go build時のファイル指定がなかった場合は*.goが指定された事になる。
go build -o hello hello.go
./hello
ビルドを隠蔽して直接プログラムを実行する方法はこちら
go run hello.go
package
Goでは変数や関数などの全ての要素はなんらかのパッケージに属する。そのため全てのプログラムはパッケージの宣言が必須となる。
また原則1ファイル1パッケージかつ、1ディレクトリ1パッケージとなる。
なおパッケージは複数のファイルに分割して書いても良い。
importにディレクトリを指定すると、そのディレクトリ内からパッケージを検索してくれる。基本的にパッケージ名をディレクトリ名にする。
mainパッケージを分割することもできるが、実行するとき下記二つに注意。
- go runする場合: mainパッケージを定義しているgoファイルを全て列挙する
- go buildする場合: go runと同様だが、前述の通りファイル名を指定しない場合*.goを指定したことになるので、go run同様mainパッケージを定義しているgoファイル全て列挙するかファイル名を指定しない
ファイル名が_test.goで終わるファイルはテストファイルとして扱われる。
※1.11以降?modules(vgo)という機能が追加され、goコマンド実行時GOPATH外のディレクトリの場合はGO111MODULE=offが必要
テスト
デフォルトでテストが組み込まれている。
ファイル名のサフィックスとして_test.goをつけ、testingパッケージをインポートすると使える。
テストの実行はgo test (ファイル名)。
基本
-
コメントは単一行はスラッシュ2個、複数行は
/*
と*/
でくくる -
一応文はPHPのようにセミコロンで区切ることになっているが省略可能なのでセミコロンを書くことはほとんどない
- 配列の要素を複数行に渡って書く場合、最後の要素にもカンマをつけないとセミコロンが勝手に認識されてしまう
-
fmt.Printfではフォーマット文字列を指定できる。
%v
だと任意の型、%#v
ではリテラル表現、%T
では型情報が出力される -
print, printlnでは標準エラー出力に文字列を出力する。printlnの方は改行がつく
-
変数定義
-
明示的な定義
var n int
var x, y int
var ( x, y int name string )
- 代入は一度に複数できる
x, y = 1, 2
-
暗黙的な定義
i := 1
- 再代入は不可
- 明示的な定義のようにまとめて定義も不可
- 基本的にはこちらを使って開発効率を上げるのが良い
-
パッケージ変数
- 関数の外に定義された変数はパッケージ変数となり、同一パッケージからならどこからでも参照できる
- グローバル変数的なものなので乱用注意
-
ローカル変数
- 関数の中に定義された変数
-
数値型について
- int8, unit16などMySQLみたいな感じでサイズを決められる
-
型変換
-
int16()
などといった関数を使って型変換する - その変数のサイズ以上の数を代入するとラップアラウンドされる
- int8の変数に255+1をすると結果は0になる
-
-
rune型
- Unicodeコードポイントを示す
- 実際にはint32の別名となっている
- シングルクォートで表現する
- 'あ'とか
- Unicodeのコードポイントで表現する場合
-
\
: 8進数3桁 -
\x
: 16進数2桁 -
\u
: 16進数4桁 -
\U
: 16進数8桁
-
-
RAW文字列リテラル
- バッククォートで囲むと改行コードなど無視してそのまま入る
-
配列型
- int型初期値ありで5つ定義する場合
a := [5]int{1, 2, 3, 4, 5}
- 要素数は
...
として省略することができる
- 配列代入時は要素のコピーが行われる
- int型初期値ありで5つ定義する場合
-
interface{}型
-
{}
も含めて型の名前 - 全ての型と互換がある
- 初期値はnil
- ただしこの型に入れられた値は演算できない
-
-
-
関数
- 一番最初に書いた感じ
- 戻り値型は波括弧の前に定義する。複数指定する場合は括弧で閉じる必要がある
- 戻り値の一部を破棄する場合は
_
を割り当てる
- 例外機構がないのでエラーの有無は戻り値の一部として示す
- 戻り値の指定の時に戻り値の変数も一緒に指定した場合、
return
だけ書いておけば勝手に返してくれる - JavaScriptのように無名関数を定義できる
- 無名関数を変数に定義するのと通常の関数を変数に入れるのとは同じ意味を示す
- returnとして関数を返すこともできる
- 返された関数を呼び出すときはこんな感じで省略した書き方ができる
returnFunc()()
- 返された関数を呼び出すときはこんな感じで省略した書き方ができる
- 関数を引数として渡すこともできる
- クロージャーとしてもいける
- 一番最初に書いた感じ
-
定数
- 定数はconstで定義できる
const A = 1
- varと同様括弧を使って複数定義できる
- 複数定義する時、値の設定を省略することができる。その場合最後に設定した値が自動的にセットされる
- 値として任意の式を埋め込むことができる
- 整数値の定数は最大値が存在しない
- 定数同士の演算はコンパイル時に行われる
- iota (定義済み識別子)を使うとenum的な動作が実現できる
- 呼ばれるたびに1加算された値が返される
- 定数はconstで定義できる
-
スコープ
- パッケージ内の定数、変数、関数が他パッケージから呼び出すことができるかどうかは、名前の一文字目が大文字かどうかで決まる
- インポート時パッケージ名を変更することができる
- パッケージ名の前に変更したい名前を書く
- 使うときのパッケージ名を省略することもできる
- パッケージ名の前にドットを書く
- パッケージが複数のファイルが分割されている場合
- 定数や変数は相互に参照可能
- インポート宣言はファイルごとに独立
- 関数内で
{}
ブロックを書くとその中では変数、定数のスコープが別になる
-
制御構文
- for
- 繰り返しはforのみ
- 初期値、条件式を省略すると無限ループになる
for i := 0; i < 10; i++ { // 処理 }
- if
- else ifはあるが、他の言語のように条件式を省略することはできない
if x == 1 { // 処理 }
- 簡易文付きif
- 簡易文とは式や代入などの複雑な構造を持たない単一の文
- ifブロックの中はスコープが別。そのため簡易文で変数を定義することでifブロック内のみで有効な変数を生み出すことができる
if [簡易文] ; [条件式] { // 処理 }
- goには例外機構がないのでこんな感じにするのが多い
if _, err := func1(); err != nil { // エラー処理 }
- for
- 無限ループ
- for
for {
// 処理
}
- ループは
break
で中断できる- continueも使える
- 条件式だけ書いてwhileのようにも書けるし、初期化文、条件式、後処理文を書くこともできる
- 範囲式を使ってforeach的な処理が書ける
- 範囲式は下記のように書く
for [配列のインデックス], [配列の要素] := range [変数] {
// 処理
}
- switch
- forと同様簡易文を書くことができる
switch [簡易文;] [式] {
case X:
// 処理
default:
// 処理
}
- case節の値はカンマ区切りで複数書くことができる
- デフォルトではcase節の実行が終わるとの頃のcase節は実行されない
- 実行したい場合はfallthroughを書く
- case節の値は式でも問題ない
- 型アサーション
- interface{}型で変数を入れた後型を確定したり型を判定できる
-
var x interface{} = 1
とした時、i, isInt := x.(int)
とすると、それぞれ値とtrueが代入される
-
- 型アサーションを使うことで型に応じた処理がswitchで書ける
-
switch v := x.(type) {
とすると、case節で型に応じた処理が書ける上、v
を参照すれば値を変数に代入できる
-
- interface{}型で変数を入れた後型を確定したり型を判定できる
- ラベル付き文
-
HOGE:
という行はラベルという - gotoやbreak、continueの時にこのラベルを指定できる
-
- defer
-
defer funcName()
とすることで、関数の終了時に任意の関数を呼び出すことができる - リソースの解放処理などを書くと便利
-
- go
-
go funcName()
とすると処理と並行してfuncName()の処理も実行される
-
- init
- initという名前の関数はmain関数より先に実行される
参照型
- 特殊なデータ構造で、スライス、マップ、チャネルの3つがある
- make関数で生成する
- スライス
- 可変長配列
-
make(T, N, M)
でT型でN要素数があり容量がMのスライスを生成 -
var x []int
で宣言。生成はy := make([]int, 10)
とする- もしくは
z := []int{1, 2, 3}
とやってもいい
- もしくは
- 要素数を調べるにはlen関数を使う
- 容量を調べるにはcap関数を使う
- 簡易スライス式
- 配列型かスライスを示す変数に
[n:m]
という形式で範囲を指定すると、その範囲で新しいスライスを生成できる
- 配列型かスライスを示す変数に
- スライスの要素の追加はappend関数を使う
-
append(a, 1, 2)
とすると、aに1と2を追加できる - 追加する値はスライスでも良い
- スライスの容量が足りなくなると自動で拡張が行われるが、コストの高い処理なのでなるべく避けた方が良い
-
- スライスにスライスをコピー(追加)する場合はcopy関数が使える。戻り値としてコピーされた要素数が返る
- 完全スライス式を使うと容量を指定できる
- goでも可変長引数がサポートされており、その引数はスライスとなる
- 関数に値を渡すとき、配列型はコピーされる。参照型のスライスではその名の通り参照渡しとなる
- スライスの初期値は配列と違ってnilになる
- マップ
- 連想配列のようなもの
-
var m map[int]string
で定義(intはキーの型、stringは要素の型) -
m := map[int]string)
で生成-
m := map[int]string{1: "one", 2: "two"}
とやると一度に生成できる
-
- スライスと違って存在しないキーを参照した場合はその型のデフォルトの値が返ってくる
- 値が存在したかどうかは参照した時の2つ目の戻り値でわかる`
-
m := map[int[string]{1: "hoge"}
とした時s, ok := m[1]
とすればsに"hoge"が、okにはtrueが代入される - 慣習的にokという変数を使う
-
- 値が存在したかどうかは参照した時の2つ目の戻り値でわかる`
- マップにおいても範囲節は使用できる
* ただし順序の保証がないため注意 - スライスと同様len関数を使って要素数を取得できる(capは使えない)
- 要素を削除するときはdelete関数を使う
- チャネル
- go文を使うとゴルーチンが生成され並列実行ができる
- ゴルーチン間のデータ受け渡しを行うのがチャネル
-
var ch chan int
でint型のチャネルが定義できる- 受信専用の場合は
<-chan
とし、送信専用の場合はchan<-
とする
- 受信専用の場合は
- 他の参照型同様make関数で生成できる
- データ構造
- キューの性質を備える
- 順序が保証される
- 送信は
ch <- 1
などとする - 受信は
a := <-ch
などとする - バッファサイズを上回った場合は場合ゴルーチンは停止する
- バッファ内のデータの個数を調べるためにlen関数が使える
- バッファサイズを取得するためにcap関数が使える
- close関数を使ってチャネルをクローズできる
- クローズしたチャンネルに対する送信はランタイムパニックになる
- 受信は通常通りできる
- 受信時に戻り値を二つ用意すると二つ目にboolが入る
- バッファが空でクローズされている場合falseが入る
- 複数のチャンネルを扱う場合、受信待ちで処理が止まってしまう場合がある
-
select構文を使うといい
select { case c1 := <- ch1 // ch1からの受信に成功した場合の処理 case c2 := <- ch2 // ch2からの受信に成功した場合の処理
-
default:
// case節の条件が成立しなかった場合の処理
}
```
構造体
- ポインタ
- 型の前に
*
をつけるとポインタとして定義できる - 変数の前に
&
をつけると任意の型からポインタ型を生成することもできる - ポインタ変数の前に
*
を置くと、デリファレンスと言って値が取得できる- 配列の場合自動でデリファレンスしてくれるので、
(*p)[i]
としなくてもp[i]
でアクセスできる - len, cap, スライス式もデリファレンスを省略できる
- 配列の場合自動でデリファレンスしてくれるので、
- string型はimmutableで破壊的変更ができない。そのため配列の要素に対するポインタ型は生成できない
- string型は常に参照渡しとなりコピーは行われない
- 型の前に
- 構造体
- Cとほぼ同じ
-
type [定義する型] [既存の型]
とすることで型名のエイリアスを付けることができる- import同様複数指定も可能
- 型の表記が大変だったり、関数型に便利
- 構造体の定義
-
struct {}
とtypeを使う- structで定義された構造体にtypeを使って新しい型名を与える
-
type Struct1 struct {
X int
Y int
}
-
- 複合リテラル
- 構造体型にまとめて代入できる
- 複合リテラル
pt := Struct1{1, 2}
pt := Struct2{X: 1, Y: 2}
-
- 構造体のフィールドにさらに別の構造体を定義することもできる
- その構造体のフィールドを呼び出す際には一意に絞り込まれる場合は中間のフィールドを省略できる
- 複数の構造体に同一の構造体を定義することでオブジェクト指向における継承のようなことができる
- 構造体は参照型ではなく値型のため、関数の引数として渡された場合はコピーが生成されることになる
- 構造体に限った話ではないが、new関数を使うと指定した型のポインタ型を取得できる
- 構造体のフィールドにさらに別の構造体を定義することもできる
- メソッド
- 任意の型に特化した関数を定義するための仕組み
- 型とその内部に強く結びついた処理を描くための仕組み
- オブジェクト指向のメソッドとは違う
- 定義方法
func (p *Point) Hoge()
- 変数pをレシーバーという
- もちろん関数と同じように引数と戻り値を設定できる
- レシーバーの型さえ違えば同名のメソッドを定義できる
- レシーバーは構造体型の他にエイリアスでもOK
- 呼び出し方
p.Hoge()
- 登録したレシーバーの型に対して関数が生える感じ。オブジェクト指向のクラスに対するメソッド的な
- 実態は関数
- レシーバーは値型よりポインタ型にするべき
- メソッド呼び出し時にレシーバーのコピーが動いてしまい、パッケージ変数を変更することができない
- 型のコンストラクタ
- Goにはコンストラクタはないが、型のコンストラクタというパターンはある
- 「New型名」という関数名でコンストラクタ処理を記載する
- パッケージ内部だけで使う場合は「new型名」としてパッケージ内部でしか呼び出せないようにするといい
- 可視性について
- 構造体の名前の最初が大文字でも、フィールドの最初の文字が小文字であればそのフィールドに外からアクセスすることはできない
- タグ
- 構造体のフィールドにメタ情報を付与できる
- プログラムに影響は及ぼさない
- 文字リテラルかRAW文字列リテラルかのどちらかが選べる
Goのツール
-
go build
は引数に何も与えないこともできるし、ファイル名を与えたりパッケージを与えたりできる - vendorというディレクトリを用意すると、GOPATHより優先的に読み込んでくれる