この記事は2019新卒 エンジニア Advent Calendar 2019の15日目の記事です。
今回は、爆速でGoに入門していきます。
この記事を読み終えれば、Goの基礎は身についたと言っても過言ではないでしょう。
#Hello World
package main
import "fmt"
func main() {
fmt.Println("Hello World")
}
$ go run main.go
Hello World
#Packages
Packageは名前空間を分けるための仕組みです。
同パッケージ内のメンバは無制限に参照することができますが、他のパッケージのメンバにアクセスするにはimport文が必要になります。
上のHelloWorldのコードでは、以下のようなfmtパッケージをimportしています。
package fmt
func Println(...) {
...
}
#Exported names
最初の文字が大文字で始まる名前は、外部のパッケージから参照できるエクスポート(公開)された名前(exported name)です。
小文字から始まる名前は、外部から参照することができません。
package main
import (
"fmt"
"math"
)
func main() {
fmt.Println(math.Pi) //参照できる
fmt.Println(math.pi) //参照できない
}
$ go run main.go
# command-line-arguments
./main.go:10:14: cannot refer to unexported name math.pi
./main.go:10:14: undefined: math.pi
#Imports
複数のimport文をまとめて書くことができます。
package main
// import "fmt"
// import "math"
import (
"fmt"
"math"
)
func main() {
fmt.Println(math.Pi)
}
$ go run main.go
3.141592653589793
#Functions
足し算をするadd関数を定義します。
package main
import "fmt"
func add(x int, y int) int {
return x + y
}
func main() {
fmt.Println(add(46, 9))
}
$ go run main.go
55
2つ以上の引数が同じ型の場合は、省略して記述できます。
package main
import "fmt"
// func add(x int, y int) int {
// return x + y
// }
func add(x, y int) int {
return x + y
}
func main() {
fmt.Println(add(46, 9))
}
複数の戻り値を返すことができます。
package main
import "fmt"
func swap(x, y int) (int, int) {
return y, x
}
func main() {
fmt.Println(swap(46, 9))
}
$ go run main.go
9 46
戻り値の変数に名前をつけることができます。戻り値に名前をつけると、関数の最初で定義した変数名として扱われます(var sum intしたのと同じことになる)。
名前をつけた戻り値の変数を使うと、return文に何も書かずに戻すことができます。これを"naked return"と呼びます。
package main
import "fmt"
func add(x, y int) (sum int) {
// var sum int
sum = x + y
return
}
func main() {
fmt.Println(add(46, 9))
}
$ go run main.go
55
#Variables
var文で変数を宣言します。変数名の後ろに型名を書くことに注意してください。
package main
import "fmt"
var hoge, piyo bool
func main() {
var i int
fmt.Println(i, hoge, piyo)
}
$ go run main.go
0 false false
var宣言では、変数ごとに初期化子(initializer)を与えることができます。
また、初期化子が与えられている場合は型宣言を省略でき、その変数は初期化子が持つ型になります。
package main
import "fmt"
var hoge, piyo bool = true, false // 初期化子を与える
func main() {
var i = 46 // 初期化子を与え、型宣言を省略
fmt.Println(i, hoge, piyo)
}
$ go run main.go
46 true false
import文と同様に、まとめて書くこともできます。
package main
import "fmt"
var (
i = 46
hoge = true
piyo = false
)
func main() {
fmt.Println(i, hoge, piyo)
}
関数の中では、var宣言の代わりに、 := と書くことで暗黙的な型宣言ができます。
ただし、関数の外では、キーワードではじまる宣言(var, func など)必要で、 := での宣言はできません。
package main
import "fmt"
// i := 46 関数の外ではムリ
func main() {
i := 46 // 暗黙的な型宣言
fmt.Println(i)
}
型変換の際には、明示的な変換が必要です。
package main
import "fmt"
func main() {
var i int = 46
var hoge float64 = float64(i)
var piyo float64 = i // エラーになる
fmt.Println(i, hoge, piyo)
}
$ go run main.go
# command-line-arguments
./main.go:8:6: cannot use i (type int) as type float64 in assignment
明示的な型宣言をしない場合(:= や var = のいづれか)、変数の型は右側の変数から型推論されます。
右側の変数が型宣言された変数の場合、左側の新しい変数は右と同じ型になります。
右側が型を指定しない数値である場合、左側は右側の定数の精度に基づいた型になります。
package main
import "fmt"
func main() {
var i int
j := i
fmt.Printf("j is of type %T\n", j)
hoge := 46
piyo := 46.123
fmt.Printf("hoge is of type %T\n", hoge)
fmt.Printf("piyo is of type %T\n", piyo)
}
$ go run main.go
j is of type int
hoge is of type int
piyo is of type float64
#Constants
定数は、constキーワードを使って変数(var)と同じように宣言します。型宣言は省略できます。
なお、 := を使っての宣言はできません。
package main
import "fmt"
func main() {
// const Name string = "gunsoo"
const Name = "gunsoo" // 型宣言は省略できる
fmt.Println("Hello", Name)
Name = "heichoo" // エラーになる
}
$ go run main.go
# command-line-arguments
./main.go:8:7: cannot assign to Name
#For
C言語やJavaと違い、初期化; 条件式; 後処理の部分をくくる括弧()はありません。中括弧{}は必要です。
package main
import "fmt"
func main() {
for i := 0; i < 10; i++ {
fmt.Print(i, " ")
}
fmt.Println()
}
$ go run main.go
0 1 2 3 4 5 6 7 8 9
なお、初期化と後処理の部分は省略可能です。
package main
import "fmt"
func main() {
i := 0
for i < 10 {
fmt.Print(i, " ")
i++
}
fmt.Println()
}
これはCなど他の言語における while と同じです。
Goには while はなく、 for だけを使います。
無限ループを次のようにコンパクトに表現できます。
package main
func main() {
for {
}
}
#If
If文もforと同様に、()が不要です。
package main
import "fmt"
func main() {
i := 46
if i == 46 {
fmt.Println("hoge")
}
}
$ go run main.go
hoge
forのように条件の前に、評価するための簡単な文を書くことができます。
ここで宣言された変数は、ifのスコープ内のみで有効です。
package main
import "fmt"
func main() {
if i := 64; i == 46 {
fmt.Println("hoge")
} else {
fmt.Println(i)
}
// fmt.Println(i) スコープ外なのでエラーになる
}
$ go run main.go
64
#Switch
GoのswitchはCやJavaなど他の言語と似ていますが、Goでは選択されたcaseだけを実行してそれに続くすべてのcaseは実行されません。break文が自動的に提供されているからです。
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Print("Go runs on ")
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
// break いらない
case "linux":
fmt.Println("Linux.")
default:
fmt.Printf("%s.", os)
}
}
$ go run main.go
// your OS
#Defer
defer文は、deferへ渡した関数の実行を、呼び出し元の関数の終わり(returnする)まで遅延させるものです。
deferへ渡した関数の引数はすぐに評価されますが、その関数自体は呼び出し元の関数がreturnするまで実行されません。
package main
import (
"fmt"
)
func main() {
defer fmt.Println("World")
fmt.Println("Hello")
}
$ go run main.go
Hello
World
deferへ渡した関数が複数ある場合、その呼び出しはスタックされ、LIFOで実行されます。
package main
import (
"fmt"
)
func main() {
fmt.Println("Countdown...")
defer fmt.Println("GO!!")
defer fmt.Println("1...")
defer fmt.Println("2...")
defer fmt.Println("3...")
}
$ go run main.go
Countdown...
3...
2...
1...
GO!!
#Pointers
ポインタは値のメモリアドレスを指します。
&オペレータがオペランドへのポインタを引き出し、*オペレータがポインタの指す先の変数を示します。
package main
import (
"fmt"
)
func main() {
i := 46
p := &i // iへのポインタ
fmt.Println(*p) // ポインタpを通してiの値を読む
*p = 64 // ポインタpを通してiの値を変更
fmt.Println(i)
}
$ go run main.go
46
64
#Structs
struct(構造体)は、フィールドの集まりです。
package main
import "fmt"
type Name struct {
A string
B string
}
func main() {
fmt.Println(Name{"hoge", "piyo"})
}
$ go run main.go
{hoge piyo}
structのフィールドは、ドット( . )を用いてアクセスします。
package main
import "fmt"
type Name struct {
A string
B string
}
func main() {
n := Name{"hoge", "piyo"}
n.A = "fuga"
fmt.Println(n.A)
}
$ go run main.go
fuga
フィールド名:値と書くことで、一部のフィールドだけを列挙することができます。
&をつけると、新しく割り当てられたstructへのポインタを返します。
package main
import "fmt"
type Name struct {
A string
B string
}
func main() {
n1 := Name{"hoge", "piyo"}
n2 := Name{A: "hoge"}
n3 := Name{}
p := &Name{"hoge", "piyo"}
fmt.Println(n1, n2, n3, p)
}
$ go run main.go
{hoge piyo} {hoge } { } &{hoge piyo}
#Arrays
以下では、長さ2の配列を宣言しています。
配列の長さは型の一部のため、配列のサイズを変えることはできません。
package main
import "fmt"
func main() {
var i [2]int
i[0] = 1
i[1] = 2
fmt.Println(i)
}
$ go run main.go
[1 2]
なおこのようにvarを使わずに書くこともできます。
package main
import "fmt"
func main() {
i := [2]int{1, 2}
fmt.Println(i)
}
#Slices
配列は固定長な一方で、スライスは可変長です。より柔軟な配列と言ってもいいでしょう。
i[low:high]のように境界を指定することによって、スライスが形成されます。
package main
import "fmt"
func main() {
i := [5]int{1, 2, 3, 4, 5}
var s []int = i[1:4]
fmt.Println(s)
}
$ go run main.go
[2 3 4]
スライスは配列への参照のようなものです。
スライスはどんなデータも格納しておらず、単に元の配列の部分列を指し示しています。
スライスの要素を変更すると、その元となる配列の対応する要素が変更されます。
同じ元となる配列を共有している他のスライスは、それらの変更が反映されます。
package main
package main
import "fmt"
func main() {
i := [5]int{1, 2, 3, 4, 5}
var s1 []int = i[1:4]
var s2 []int = i[0:3]
fmt.Println(s1, s2)
s1[0] = 46
fmt.Println(s1, s2)
fmt.Println(i)
}
$ go run main.go
[2 3 4] [1 2 3]
[46 3 4] [1 46 3]
[1 46 3 4 5]
配列を宣言するのと同じように、スライスを宣言できます。
package main
import "fmt"
func main() {
// i := [5]int{1, 2, 3, 4, 5}
i := []int{1, 2, 3, 4, 5} // 上と同じ配列を作成し、それを参照するスライスを作成する
fmt.Println(i)
s := []struct {
i int
b bool
}{
{2, true},
{3, false},
{5, true},
{7, true},
{11, false},
{13, true},
}
fmt.Println(s)
}
$ go run main.go
[1 2 3 4 5]
[{2 true} {3 false} {5 true} {7 true} {11 false} {13 true}]
スライスは、長さと容量を持っています。
長さは、そのスライスの要素数。容量は、そのスライスの最初の要素から数えて、元となる配列の要素数です。
それぞれlen()とcap()という式で得ることができます。
package main
import "fmt"
func main() {
s := []int{1, 2, 3, 4, 5}
printSlice(s)
s = s[:0]
printSlice(s)
s = s[:4]
printSlice(s)
s = s[2:]
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
$ go run main.go
len=5 cap=5 [1 2 3 4 5]
len=0 cap=5 []
len=4 cap=5 [1 2 3 4]
len=2 cap=3 [3 4]
スライスのゼロ値は nil です。
nil スライスは 0 の長さと容量を持っており、何の元となる配列も持っていません。
package main
import "fmt"
func main() {
var s []int
fmt.Println(s, len(s), cap(s))
if s == nil {
fmt.Println("nil!")
}
}
$ go run main.go
[] 0 0
nil!
スライスは、組み込みのmake関数を使用して作成することができます。
make([]int, len, cap)のように指定します。
package main
import "fmt"
func main() {
a := make([]int, 5)
printSlice("a", a)
b := make([]int, 0, 5)
printSlice("b", b)
c := b[:2]
printSlice("c", c)
d := c[2:5]
printSlice("d", d)
}
func printSlice(s string, x []int) {
fmt.Printf("%s len=%d cap=%d %v\n",
s, len(x), cap(x), x)
}
$ go run main.go
a len=5 cap=5 [0 0 0 0 0]
b len=0 cap=5 []
c len=2 cap=5 [0 0]
d len=3 cap=3 [0 0 0]
組み込みのappend関数を使うことにより、スライスへ新しい要素を追加できます。
package main
import "fmt"
func main() {
var s []int
printSlice(s)
s = append(s, 0)
printSlice(s)
s = append(s, 1, 2)
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
$ go run main.go
len=0 cap=0 []
len=1 cap=1 [0]
len=3 cap=4 [0 1 2]
#Range
for ループに利用する range は、スライスや、マップ( map )をひとつずつ反復処理するために使います。
スライスをrangeで繰り返す場合、rangeは反復毎に2つの変数を返します。 1つ目の変数はインデックス( index )で、2つ目はインデックスの場所の要素のコピーです。
package main
import "fmt"
func main() {
s := []int{1, 2, 3, 4, 5}
for i, v := range s {
fmt.Println("index:", i, " value:", v)
}
}
$ go run main.go
index: 0 value: 1
index: 1 value: 2
index: 2 value: 3
index: 3 value: 4
index: 4 value: 5
インデックスや値は、アンダーバー( _ )へ代入することで捨てることができます。
package main
import "fmt"
func main() {
s := []int{1, 2, 3, 4, 5}
for _, v := range s {
fmt.Println("value:", v)
}
}
$ go run main.go
value: 1
value: 2
value: 3
value: 4
value: 5
#Maps
make関数でマップを作成できます。
package main
import "fmt"
func main() {
m := make(map[string]int)
m["Nogizaka"] = 46
m["AKB"] = 48
fmt.Println(m)
}
$ go run main.go
map[AKB:48 Nogizaka:46]
map m に対し、要素の挿入/更新/削除と、キーに対する要素が存在するかどうかの確認をしていきます。
要素の存在確認は、 elem, ok = m[key] のように書きます。
もし、 m に key があれば、変数 ok は true となり、存在しなければ、 ok は false となります。
なお、mapに key が存在しない場合、 elem はmapの要素の型のゼロ値となります。
package main
import "fmt"
func main() {
m := make(map[string]int)
// 挿入
m["Nogizaka"] = 46
fmt.Println("The value:", m["Nogizaka"])
// 更新
m["Nogizaka"] = 64
fmt.Println("The value:", m["Nogizaka"])
// 削除
delete(m, "Nogizaka")
fmt.Println("The value:", m["Nogizaka"])
// 要素の存在確認
v, ok := m["Nogizaka"]
fmt.Println("The value:", v, "Present?", ok)
}
$ go run main.go
The value: 46
The value: 64
The value: 0
The value: 0 Present? false
#Function values
関数も変数です。他の変数のように関数を渡すことができます。
package main
import "fmt"
func addHello(s string) string {
return "Hello " + s
}
func main() {
f := addHello
fmt.Println(f("World"))
}
$ go run main.go
Hello World
無名関数を定義することもできます。
package main
import "fmt"
// func addHello(s string) string {
// return "Hello " + s
// }
func main() {
f := func(s string) string { return "Hello " + s }
fmt.Println(f("World"))
}
#Function closures
Goの関数は クロージャ( closure ) です。
関数と関数の処理に関連する関数の外の環境をセットにして閉じ込めた状態で取り扱うことができます。
この関数は、参照された変数へアクセスして変えることができ、その意味では、その関数は変数へ"バインド"( bind )されています。
package main
import "fmt"
func add() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
f1 := add()
f2 := add()
for i := 0; i < 10; i++ {
fmt.Println(f1(i), f2(-i))
}
}
$ go run main.go
0 0
1 -1
3 -3
6 -6
10 -10
15 -15
21 -21
28 -28
36 -36
45 -45
sum変数は見かけ上は関数内のローカル変数ですが、実際にはクロージャに属する変数として利用されます。
クロージャによって束縛された変数の領域は、何らかの形でクロージャが参照され続ける限りは破棄されることがありません。
そのため、この例では関数呼び出し毎の合計値をsum変数に保持することができています。
#まとめ
こんなに長くなるとは思ってませんでしたが、めちゃくちゃ長くなってしまいました。。
Part2はこちら【爆速】Go入門2
(参考)
https://go-tour-jp.appspot.com/list
http://cuto.unirita.co.jp/gostudy/post/go-package/
https://blog.y-yuki.net/entry/2017/05/04/000000