Go 入門
公式チュートリアルまとめ
<https://tour.golang.org/welcome/1>
特徴
- 文の区切りに
;
を使用しない - 型の宣言はシンボル名の後
- 多値返却
- 型推論に対応(ただし、専用の宣言方法が必要)
- 変数のデフォルトは mutable
- for 文などは、必ずブロックを持つ。
関数
func add(x int, y int) int {
return x + y
}
同じ型の引数が複数ある場合、最後以外の型宣言を省略できる。
func add(x, y int) int {
return x + y
}
可変長引数
可変長引数は次のように ...
を使って定義する。
func hoge1( vals ...int ) {
for _, val := range( vals ) {
fmt.Printf( "%v ", val )
}
fmt.Println()
}
func main() {
hoge1( 1, 2 )
}
上記 hoge1 は、 int 型の値を複数指定してコールすることができる。 引数 vals は、 関数内で slice として処理できる。
可変長引数への slice 展開
可変長引数に値を渡す場合、個別に値を指定する以外に、 slice の値を展開して渡すことも出来る。
次の例の hoge1( 1, 2)
と hoge( work... )
は、同じ結果となる。
func hoge1( vals ...int ) {
for _, val := range( vals ) {
fmt.Printf( "%v ", val )
}
fmt.Println()
}
func main() {
hoge1( 1, 2 )
work := []int{ 1, 2 }
hoge1( work... )
}
多値返却
多値返却をサポートする。
多値返却は ()
で括る。 これはタプルではない。
func swap(x, y string) (string, string) {
return y, x
}
戻り値を関数宣言時に指定できる。 次の場合、宣言時に x, y を戻り値に指定し、関数本体では return だけを書いている。
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
多値返却による関数コール
次のように、関数コールの引数に多値返却の値を使用できる。
func gets() (int,int) {
return 1, 2
}
func add( val1, val2 int ) {
fmt.Println( val1 + val2 )
}
func main() {
add( gets() ) // 1 + 2 = 3
}
ただし、次の条件を全て満す必要がある。
- コールする関数の引数に渡す値は、多値返却の関数だけ
-
コールする関数の引数が可変長引数を含ない場合
- 多値返却で返す値の数と、コールする関数の引数数が一致する
-
コールする関数の引数に可変長引数を含む場合
- 多値返却で返す値の数と、コールする関数の可変長引数以外の引数数が一致する
次の例では、 多値返却する gets() の戻り値を使用して hoge1(), hoge2(), hoge3() をコールしている。
- hoge1() は、可変長引数のみ持つ。
- hoge2() は、int 型の引数 1 つと可変長引数を持つ。
- hoge2() は、int 型の引数 2 つと可変長引数を持つ。
func gets() (int,int) {
return 1, 2
}
func hoge1( vals ...int ) {
test( vals )
}
func hoge2( val int, vals ...int ) {
test( append( []int{ val }, vals... ) )
}
func hoge3( val int, val2 int, vals ...int ) {
test( append( []int{ val, val2 }, vals... ) )
}
func main() {
hoge1( 1, 2 )
hoge1( gets() )
hoge2( 1, 2 )
hoge2( gets() )
hoge3( 1, 2 )
hoge3( gets() )
}
func test( vals []int ) {
for _, val := range( vals ) {
fmt.Printf( "%v ", val )
}
fmt.Println( "" )
}
この例では、全て 1 2 を出力する。
関数 body 内の関数宣言
次のような関数 body 内の関数宣言は出来ない。
func hoge() {
func sub() int { // error
return 1
}
print( sub() )
}
しかし、次のように anonymous 関数オブジェクトを変数に入れてコールすることは出来る。
func hoge() {
sub := func() int {
return 1
}
print( sub() )
}
ただこの場合、 hoge 関数コール毎にオブジェクトが作られて、実行性能が悪くなるようだ。
試しに次のようなコードを作成し、 Test() と Test2() の実行性能を比較すると、 Test2() の実行時間は Test() の 3 倍以上かかった。
func hoge() int {
return 1
}
func Test() int {
return hoge()
}
func Test2() int {
hoge2 := func() int {
return 1
}
return hoge2()
}
つまり、クロージャが目的で無いのなら、通常の関数宣言を行なうべき。 関数コール可能なスコープを制限するテクニックとして 関数 body 内部に関数を定義することがあるが、 パフォーマンスを考えるとそれは非推奨となる。
もちろん、パフォーマンスと可読性・メンテナンス性は トレードオフになるケースがあるので、どちらを優先すべきかは状況次第である。 どちらにせよ、「パフォーマンスに影響がある」という知識を持っておくことは重要。
変数
var で宣言する。 初期化していない場合の値はゼロ値で初期化される。 ゼロ値は型毎に決っている
var c, python, java bool
func main() {
var i int
fmt.Println(i, c, python, java)
}
初期化。
var i, j int = 1, 2
func main() {
var c, python, java = true, false, "no!"
fmt.Println(i, j, c, python, java)
}
:=
を使って型推論。
ただし、これは関数内部でのみ有効。
func main() {
var i, j int = 1, 2
k := 3
c, python, java := true, false, "no!"
fmt.Println(i, j, k, c, python, java)
}
型
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // uint8 の別名
rune // int32 の別名
// Unicode のコードポイントを表す
float32 float64
complex64 complex128
int/uint のサイズは、処理系によって異なる。 サイズを限定する場合以外は int/uint の使用を推奨。
ゼロ値
初期値を与えない変数の初期値。
数値型(int,floatなど): 0
bool型: false
string型: "" (空文字列( empty string ))
文字列
- 文字列は byte 列で、終端文字はない。その代わりに、長さ情報を持つ。
- 文字列データは immutable。
- 文字列 str の byte 長は len(str) で取得する。
- str[ i ] は、 i 番目の byte データを取得する。
- &str[ i ] はアクセスできない。
- 文字列リテラルは
""
と``
を使用する。 ``
は、改行や \ のクオートを無視してそのまま文字列にする。
"abc" // abc
`
\n` // \n\\n
"\"" // "
型変換
ある値 v を、型 T に変換する場合、 T(v) で変換する。
i := 42
f := float64(i) // <- 42 を float64 に変換
u := uint(f) // <- 42 を uint に変換
Constant
定数は Constant で変数を宣言する。 定数なので、当然初期値を設定する。 この初期値を使って型推論も行なうので、型宣言は不要。 なお、変数の型推論は関数内だけで有効だが、 Constant は関数外でも有効。
const Pi = 3.14
func main() {
const World = "世界"
fmt.Println("Hello", World)
fmt.Println("Happy", Pi, "Day")
const Truth = true
fmt.Println("Go rules?", Truth)
}
数値の Constant
数値型の範囲(64bit)では表現できない値も、 Constant であれば表現できる。
const (
// Create a huge number by shifting a 1 bit left 100 places.
// In other words, the binary number that is 1 followed by 100 zeroes.
Big = 1 << 100
// Shift it right again 99 places, so we end up with 1<<1, or 2.
Small = Big >> 99
)
func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
return x * 0.1
}
func main() {
fmt.Println(needInt(Small))
fmt.Println(needFloat(Small))
fmt.Println(needFloat(Big))
}
for
- ほぼ C と同じ。
- スコープは for ループで閉じる。
func main() {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum)
}
For の 3 つのループ制御ステートメントはそれぞれ省略可能。 これにより、 while/無限ループを表現する。 For で while と等価な表現ができるため、 go は while をサポートしない。
while と等価の for。
func main() {
sum := 1
for sum < 1000 {
sum += sum
}
fmt.Println(sum)
}
無限ループの for。
func main() {
for {
}
}
if
()
が無い- {} が必須
-
条件式の前に文を書ける。
- ここで宣言した変数は if と else のスコープ。
- else は、 if の
}
と同じ行に書かなければならない。
次の場合はコンパイルエラー。
if val {
}
else {
}
次のように }
の行に else を書く。
if val {
} else {
}
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
}
return lim
}
switch
- switch は if/else のシンタックスシュガー。
- 条件文の前の文も書ける。
- シンタックスシュガーなので、 case の各式は上から順に評価される。
-
C のような fall-through はない。
- break はなくても、一致した case/default を実行したら終わる。
func main() {
fmt.Print("Go runs on ")
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.", os)
}
}
switch の値を省略
switch の値を省略すると switch true と同義。
func main() {
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
}
defer
-
関数コールを呼び出し元関数終了時に実行するように予約する。
- defer はブロック終了時ではなく、関数終了時に実行される
- 関数コールの引数に与えている式は、 defer 評価時に実行される。
次は hello hoge ではなく、hello world が表示される。
func main() {
txt := "world"
defer fmt.Println( txt )
txt = "hoge"
fmt.Println("hello")
}
defer の予約は、スタックに Push される。
func main() {
fmt.Println("counting")
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
}
ポインタ
- 値を格納しているポインタを扱える。
- ただし、ポインタの演算はできない。
- C++ の参照と考えれば良い。
- ゼロ値は nil。
- 演算子は C と同じ。 &val でポインタ取得。 *val でポインタが格納する値を取得。
-
C と同じで、構造体は値渡しとポインタ渡しで意味が異なる。
- 値渡しはコピーされる。
- ポインタ型は 型名の前に * を付ける。
*int
等。
func main() {
i, j := 42, 2701
p := &i // point to i
fmt.Println(*p) // read i through the pointer -- 42
*p = 21 // set i through the pointer
fmt.Println(i) // see the new value of i -- 21
p = &j // point to j
*p = *p / 37 // divide j through the pointer
fmt.Println(j) // see the new value of j -- 73
}
構造体
- メンバアクセスは C と同じで
.
を使用する。 -
ただし、ポインタ経由のアクセス方法が異なる
- 時に
->
は使用せず、.
を使用する。 - (*p).val のようにも書けるが、 p.val と同義。
- 時に
- Println は、構造体のデータを出力可能
- 構造体のポインタを Println すると、 & を付加した
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
p := &v
p.X = 1e9
fmt.Println(v)
}
構造体リテラル
- 構造体の初期化データ。
- メンバの初期化は宣言順に処理される。
- 初期値を与えないメンバは、ゼロ値で初期化される。
- 構造体リテラルのポインタも取れる。
type Vertex struct {
X, Y int
}
var (
v1 = Vertex{1, 2} // has type Vertex
v2 = Vertex{X: 1} // Y:0 is implicit
v3 = Vertex{} // X:0 and Y:0
p = &Vertex{1, 2} // has type *Vertex
)
配列
- 要素数固定のシーケンス。
- 要素数は、宣言時に指定する。
- 配列は
[N]T
として宣言する。ここで N は要素数、T は型。 - 要素アクセスは 0 〜 N-1 まで。
- 範囲外アクセスはエラー
func main() {
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)
primes := [6]int{2, 3, 5, 7, 11, 13}
fmt.Println(primes)
}
スライス
- スライスは、配列の一部を参照する。
- スライスの型は
[]T
として宣言する。要は配列の N がない形になる。 - スライスの要素アクセスは 0 〜。
- 範囲外アクセスはエラー
func main() {
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
fmt.Println(s)
}
- 上記の primes[1:4] がスライス
- ここで primes[1:4] は、 {3,5,7} を示す。 つまり 1 から (4-1) 番目まで。
-
参照元の配列の範囲内であっても、要素アクセスにマイナスは指定できない。
- 例えば
s := primes[1:]
の時のs[-1]
は NG。
- 例えば
- スライスは参照なので、スライスの要素を変更すると、参照元の値も変更になる。
- 次の場合、スライス
s[1] = 0
しているが、これによって、 primes[ 2 ] が変わる。
func main() {
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
fmt.Println(s)
s[1] = 0;
fmt.Println( primes ) // [2 3 0 7 11 13]
}
スライスの範囲
- スライスの範囲は、省略できる。
- 省略した場合、最小、あるいは最大になる。
- 次の s1 〜 s4 は同じ範囲を示す。
func main() {
primes := [6]int{2, 3, 5, 7, 11, 13}
s1 := primes[ 0: 6 ]
s2 := primes[ : 6 ]
s3 := primes[ 0: ]
s4 := primes[ : ]
}
スライスのスライス
- スライスから更にスライスを作れる。
-
この場合、スライスの範囲は生成元スライスのインデックスを指定するが、 範囲の上限値は生成元スライスの上限値を越えて、 元の配列の最終要素に該当するインデックスまで指定できる。
- ただし下限値は 0 〜。
func main() {
s := []int{2, 3, 5, 7, 11, 13}
s = s[3:5]
fmt.Println( s ) // [7 11]
s = s[1:3] // [11 13]
fmt.Println( s )
}
スライスの len と cap
- len は、スライスの要素数
- cap は、次の式から得られる
スライスが参照する元の配列の要素数 - スライスが先頭が参照する元の配列インデックス
- つまり cap は、そのスライスを生成元にした新しいスライスの最大サイズ
スライスのゼロ値
- ゼロ値は nil
- len( nil ) と cap( nil ) は 0
多次元スライス
- 多次元のスライスを生成できる
board := [][]string{
[]string{"o", "o", "o"},
[]string{"o", "o", "o"},
[]string{"o", "o", "o"},
}
print( board[0][0] )
スライスへの append
- スライスは append によって、末尾に要素を追加できる
-
スライスの上限値によって、 append の動作が変わる
-
スライスの上限値が参照元の配列より小さい場合
- 参照元の配列の該当位置に append した値がセットされる
-
スライスの上限値が参照元の配列と同じ場合
- append に必要な要素数分だけ拡張した参照元の配列のコピーが生成され、 それを参照するスライスが生成される
-
func main() {
ss := [4]int{1,2,3,4}
var s []int = ss[:3]
printSlice(s)
// ここでは、 ss[3] に 0 がセットされる
s = append(s, 0)
printSlice(s)
// ここで、ss のサイズ + α の配列が生成され、ss の内容がコピーされる
// + αが幾つになるかは???
s = append(s, 1)
printSlice(s)
// ここで s[0] に代入しているが、参照元配列がコピーした物に
// 置き換わっているため当初の参照先の ss [0] は書き変わらない。
s[0] = 10
fmt.Printf("%v", ss)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
上記の結果は次になる。
len=3 cap=4 [1 2 3]
len=4 cap=4 [1 2 3 0]
len=5 cap=8 [1 2 3 0 1]
[1 2 3 0]
range
- range は for ループで制御するイテレータ制御を行なう。
- スライスを range で処理する場合、 要素の index, 要素のコピーを返す
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
}
- range の戻り値は、格納先を
_
とすることで値を捨てられる。 _
を使用することで、記述の省略が可能。
map
- map のゼロ値は nil
-
map 型の表現は次になる
- これは、キーが int、値が string の map
map[int]string
- map リテラルは次になる。
{ key1:val1, key2:val2, }
map アクセス
-
m[key] = val
- map の key に val を設定
-
val = m[key]
- map の key の要素を取得
-
val, ret = m[key]
- map の key の要素を取得し、 key に対する要素の有無が ret に取得。
- ret は bool
-
delete( m, key )
- map の key の要素を削除
レシーバー
- Rust のトレイトのような仕組み。
- 次は関数 Abs の Vertex 型のレシーバーを定義している。
- レシーバーは構造体だけでなく全ての型に対して定義できる。
- ただし、同じパッケージ内で定義している型でなければならない。
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs())
}
-
レシーバーの型は、ポインタで宣言しないとコピーが発生する。
- つまり、構造体のレシーバは通常ポインタ型で宣言する。
- 次の Scale() は *Vertex 型で渡している。 これを Vertex に変更すると、Scale() 内で変更した結果は Scale() 呼び出し元には反映されない。
- このとき Scale() 呼び出し側は、 レシーバの型が *Vertex と Vertex どちらでも呼び出し方は変わらない。
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(10)
fmt.Println(v.Abs())
}
- 非ポインタ型の値からポインタ型のレシーバをコールできるように、 ポインタ型の値から非ポインタ型のレシーバをコールできる。
interface
- レシーバの集りを定義したもの
- 次は、 Abs() レシーバを持つ Abser インタフェースを定義している
type Abser interface {
Abs() float64
}
- 次の
a = &v
はコンパイルが通るが、a = v
はコンパイルエラーになる。 - これは、 Abs() のレシーバが *Vertex であり、 Vertex ではないため。
type Abser interface {
Abs() float64
}
func main() {
var a Abser
v := Vertex{3, 4}
a = &v // a *Vertex implements Abser
a = v
fmt.Println(a.Abs())
}
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
- どの型が、どの interface を実装しているか宣言はしない
- ある型 T が、ある interface I を実装できているかどうかは、 T 型のデータを I 型の変数に代入する時にコンパイルエラーするかどうかで判断する
インタフェース型の値
- インタフェース型の値は、レシーバの型をもつ。
- 次の T は、 *T でレシーバを定義しているため、 I の型は *T となる。
type I interface {
M()
}
type I2 interface {
M2()
}
type T struct {
S string
}
func (t *T) M() {
fmt.Println(t.S,1)
}
func (t T) M2() {
fmt.Println(t.S,2)
}
func main() {
t := T{"Hello"}
describe(&t)
describe2(t)
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i) // (&{Hello}, *main.T)
i.M()
}
func describe2(i I2) {
fmt.Printf("(%v, %T)\n", i, i) // ({Hello}, main.T)
i.M2()
}
レシーバの値が nil だった場合の処理
インタフェース型の変数が nil になるケースは次の 2 つある。
- 変数を未初期化の場合
var i IF
- 変数に nil 値を持つ型をセットした場合
var t *T
var i IF
i = t
ただし、2番目の方は実際には nil ではない。
これは、インタフェースの値の持ち方から来ている。
インタフェースの値は、型情報の *T と nil をセット (*T,nil) を保持している。
よって、次に示すように i == nil
は false となる。
var t *T
var i IF
i = t
fmt.Println( i == nil, t == nil ) // false true
- 上記の i のように、インタフェースの型が確定していて、 その型のオブジェクトが nil の場合、そのレシーバの関数はコールできる。
- 一般的なオブジェクト指向言語では、 nil オブジェクトのメソッドコールはランタイムエラーするが、 go では関数が実行される。
- この時のレシーバの値は、 nil として関数が実行される。
type I interface {
M()
}
type T struct {
S string
}
func (t *T) M() { // この t が nil となる
if t == nil {
fmt.Println("nil")
return
}
fmt.Println(t.S)
}
func main() {
var t *T;
describe(t)
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i) // (<nil>, *main.T)
i.M()
}
上記のように型が確定しているインタフェースは型の値が nil でも関数コールされる。 一方で、型も確定していないゼロ値であるインタフェースを関数コールすると、 ランタイムエラーする。
type I interface {
M()
}
func main() {
var i I
describe(i)
i.M()
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
空のインタフェース
関数を持たないインタフェースを空のインタフェースと呼ぶ。 この空のインタフェースは、全ての値を保持できる。
interface {}
func main() { var i interface{} i = 1 i = "abc" }
型アサーション
インタフェースの値を、具体的な型に変換する。
i.(T)
を書き、インタフェースの値 i を T に変換する。- 戻り値は 2 つあり、1つ目は T に変換した値、 2 つ目は変換が成功したかどうかの bool。
- 変換が失敗し、2 つ目の戻り値を変数に格納しなかった場合は panic する。
func main() {
var i interface{} = "hello"
s := i.(string)
fmt.Println(s)
s, ok := i.(string)
fmt.Println(s, ok)
f, ok := i.(float64)
fmt.Println(f, ok)
f = i.(float64) // panic
fmt.Println(f)
}
- 上記コードでは、空のインタフェース i に "hello" をセットしている。
-
次に i を string に変換する
- ここでは成功するので s は "hello" で、 ok は true が入る
-
次に i を float64 に変換する
- ここでは失敗するので s は 0 で、ok は false が入る
-
最後に i を float64 に変換し、 2 つ目の戻り値を格納していない
- ここでは panic する
型 switch
型情報で switch する。
switch v := i.(type) {
case T:
// here v has type T
case S:
// here v has type S
default:
// no match; here v has the same type as i
}
stringer インタフェース
値を文字列表現で出力する際に使用する。
type Stringer interface {
String() string
}
error インタフェース
エラーを保持する。
type error interface {
Error() string
}
error が nil 以外 のときエラーが発生している。
goroutine
go が管理する軽量スレッド。
go f(x, y, z)
f, x, y, z が、呼び出し元スレッドで評価され、 f の実行は新しいスレッドで実行される。
goroutine は、同じメモリ空間内で実行される。
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
channel
- 値の送受信を行なうための FIFO。
-
コピーした値を、送受信する。
- 参照渡しする場合は、ポインタを送信する。
ch <- v // v をチャネル ch へ送信する
v := <-ch // ch から受信した変数を v へ割り当てる
int のチャネル型の生成。 デフォルトの場合、 0 個のバッファ。
ch := make(chan int)
ch2 := make(chan int,2)
ブロック
- 送信処理は、バッファに空きがあればブロックしない。
- 受信処理は、データの送信が行なわれるまでブロックされる。
close
- チャネルの送信完了を明示するため close を呼ぶ。
- close したチャネルへの送信は panic する。
- close された空のチャネルからデータを受信すると、そのデータ型のゼロ値が取得される。
- close されていたかどうかは、次のように 2 つ目の戻り値を取得する
v, ret := <-ch
select
- 対応する case の式がブロックしていない(ready)場合、その case を実行する。
-
全ての case の式がブロックしている場合、いずれかの式がブロック解除されるまで待つ。
- default がある場合、 default を実行する。
- 複数の case の式が ready の場合、ランダムで実行する。
Mutex
排他制御を行なう。
sync.Mutex の Lock()/Unlock() を使用して排他区間を明示する。
var lock sync.Mutex
lock.Lock()
fmt.Println( lock )
lock.Unlock()
パッケージ
- 公開シンボルは大文字で始める。
import
パッケージをインポートする。
import (
"fmt"
"math"
)
import "fmt"
import "math"