概要
普段はgolang以外を書いている人が、
golangを始めたい時にとりあえず押さえておきたい言語仕様のまとめ。
golang の特徴
- Google 開発
- コンパイル型の言語
- シンプルな言語仕様
- コンパイル・実行が高速
- 並行プログラミングに強い
基本知識
- .go の拡張子
- golang のプログラムは何らかの package に属している必要があり、そのうちの 1 つは必ず main でなければならない。
- main パッケージの中で main 関数があれば、それを実行する決まりになっている
- タブ区切り推奨
- セミコロンなし
ビルドと実行
# ビルド
$ go build
# ビルド + 実行
$ go run
コメント
// コメント
/* */
変数
- var で定義する。
1 文字目に注意
- 1 文字目が 小文字 の場合は、そのパッケージだけで見える変数
- 1 文字目が 大文字 の場合は、他のパッケージからも見える変数
- 変数・定数・関数全てに言える事。
定義パターン
// 宣言した後、値を代入パターン
var msg string
msg = "hello world"
// 宣言と代入を一緒にするパターン
var msg string = "Hello World"
// 宣言と代入を一緒にするパターン (型省略可能)
var msg = "Hello Hello"
// 宣言と代入を一緒にするパターン (var省略)
msg := "Super Hello"
// 複数の同じ型の変数を同時に定義
var a, b int
a, b = 10, 15
// 複数の同じ型の変数を同時に定義 (var省略)
a, b := 10, 14
// 複数の型違いの変数を同時に定義
var (
c int
d string
)
c = 20
d = "hoge"
// 複数の型違いの変数を同時に定義 (型省略パターン)
var (
c = 20
d = "hoge"
)
データ型
主なデータ型
- string
- int
- float64
- bool
- nil
変数定義時の初期値
- var s string
- s = ""
- var a int
- a = 0
- var b bool
- b = false
package main
import "fmt"
func main() {
var a int
var b float64
var c string
var d bool
fmt.Printf("a: %d, b:%f, c:%s, d:%t\n", a, b, c, d)
}
// a: 0, b:0.000000, c:, d:false
定数
- const で定義する。
- 変更されたら困るデータ
- 定数で iota を使えば、簡単に連番の定数を作成できる
const (
sun = iota // 0
mon // 1
tue // 2
)
ポインタ
- 宣言した変数のメモリ領域のアドレスを確保する為の変数
- golang も他の言語と同じ様に変数を宣言するとメモリ上に領域を確保する。
package main
import "fmt"
func main() {
a := 5
var pa *int // int型を格納する領域のアドレスを格納する準備
pa = &a // &a = aのアドレス
// paの領域にあるデータの値 = *pa
fmt.Println(pa) // 0xc420018078
fmt.Println(*pa) // 5
}
関数
- 引数の型は必須
- 返り値の型は必須
- 複数の返り値を返す事ができる
- 関数もデータ型なので、変数に格納可能
- 即時関数の様に宣言して、その場で実行する事も可能
package main
import "fmt"
// 引数なし関数
func sayHi() {
fmt.Println("hi!")
}
// 引数あり関数
func sayName(name string) {
fmt.Println(name)
}
// return関数
func getMessage(name string) string {
msg := "hi! my name is " + name
return msg
}
// 名前付きreturn関数
// 関数内で使った変数名を返す
func getHelloMessage(name string) (msg string) {
msg = "Hello " + name
return
}
// メイン関数
func main() {
sayHi()
sayName("gcfuji")
fmt.Println(getMessage("gcfuji"))
fmt.Println(getHelloMessage("Gemcook"))
}
package main
import "fmt"
// 複数の返り値を返す事ができる
func swap(a int, b int) (int, int) {
return b, a
}
func main() {
fmt.Println(swap(5, 2))
// 関数もデータ型なので、変数に代入可能
// その際は関数名はいらない
f := func(a int, b int) (int, int) {
return b, a
}
fmt.Println(f(3, 8))
// 即時関数的な事も可能
func(msg string) {
fmt.Println(msg)
}("Fujimoto")
}
配列
package main
import "fmt"
// メイン関数
func main() {
// 宣言と代入を分ける
var a [5]int // a[0] - a[4]
a[2] = 3
a[4] = 10
fmt.Println(a)
// 宣言と代入を同時にする
b := [3]int{1, 3, 6}
fmt.Println(b)
// 配列の個数が未定の場合
c := [...]int{2, 4, 7, 5, 5}
fmt.Println(c)
}
スライス
golang の配列じゃダメなところ
- 配列はあくまでも複数の要素を持った値
- C 言語なら要素 0 のポインタになったりした
- 関数に配列を渡す場合には値を丸ごと渡す事になるので、メモリ的に非効率になる。
- 配列の要素数は固定になっていて、動的に変化できない。
golang のスライス
- golang では配列よりもスライスをよく使う。
- 配列の一部または全部を指し示す参照型のデータ
- スライスは配列への参照なので、値を変更すると元の配列も値が変更される
package main
import "fmt"
// メイン関数
func main() {
a := [5]int{2, 10, 8, 15, 4}
b := a[2:4] // [8, 15]
c := a[2:] // [8, 15, 4]
d := a[:4] // [2, 10, 8, 15]
e := a[:] // [2, 10, 8, 15, 4]
fmt.Println(b)
fmt.Println(c)
fmt.Println(d)
fmt.Println(e)
// スライスの長さを取得
fmt.Println(len(c)) // 3
// スライスの最大容量を取得
fmt.Println(cap(c))
}
package main
import "fmt"
// メイン関数
func main() {
// make関数でいきなりスライスを作成する
s1 := make([]int, 3) // [0 0 0]
// いきなり値を割り当てたスライスを作成する
s2 := []int{1, 3, 5} // 配列の宣言と似ている
fmt.Println(s1)
fmt.Println(s2)
// appendでスライスの末尾に要素を追加
s3 := append(s2, 8, 2, 10)
fmt.Println(s3)
// copyでスライスをコピー
// 返り値は要素数
t := make([]int, len(s3))
s4 := copy(t, s3)
fmt.Println(s4)
}
マップ
- ハッシュ・連想配列・オブジェクト
package main
import "fmt"
// メイン関数
func main() {
// mapを宣言する
// m := make(map[string]int)
//
// // mapに値を設定
// m["fujimoto"] = 200
// m["arita"] = 300
// 値を指定しながら宣言する
m := map[string]int{"fujimoto": 100, "arita": 200} // map[fujimoto:100 arita:200]
// キーの存在を調べる
v, ok := m["fujimoto"]
fmt.Println(v) // 100
fmt.Println(ok) // true
// 要素を削除する
delete(m, "fujimoto")
fmt.Println(m) // map[arita:200]
}
条件分岐
if
- golang の特徴として、if 文内でしか使わない変数は if 文の中で定義できる。
package main
import "fmt"
// メイン関数
func main() {
// 普通の条件分岐
score := 61
if score > 80 {
fmt.Println("Great!")
} else if score > 60 {
fmt.Println("Good!")
} else {
fmt.Println("soso")
}
// golangの特徴の条件分岐
if score := 52; score > 80 {
fmt.Println("Great!")
} else if score > 60 {
fmt.Println("Good!")
} else {
fmt.Println("soso")
}
}
switch
- if-else 的にも書ける。
package main
import "fmt"
// メイン関数
func main() {
signal := "blue"
switch signal {
case "red":
fmt.Println("Stop")
case "yellow":
fmt.Println("caution")
case "green", "blue":
fmt.Println("Go")
default:
fmt.Println("wrong")
}
// if-else的にも書く事が可能
score := 82
switch {
case score > 80:
fmt.Println("Great!")
default:
fmt.Println("Bad")
}
}
繰り返し
for
- for 文しかない
- while 文がない
package main
import "fmt"
// メイン関数
func main() {
// 普通のforの繰り返し
for i := 0; i < 10; i++ {
if i == 3 {
continue
} else if i == 8 {
break
}
fmt.Println(i)
}
// while文的にも作成可能
n := 0
for n < 10 {
fmt.Println(n)
n++
}
}
range
- 配列・スライス・マップの要素分だけ何らかの処理を繰り返し行いたい時に使える命令
package main
import "fmt"
// メイン関数
func main() {
// スライス
s := []int{2, 3, 8}
for i, v := range s {
fmt.Println(i, v)
}
// ブランク修飾子
// _にしておくと何が入ってきてもそれを廃棄してくれるという仕様
for _, v := range s {
fmt.Println(v)
}
// マップ
m := map[string]int{"fujimoto": 200, "arita": 300}
for k, v := range m {
fmt.Println(k, v)
}
}
構造体
構造体の定義とフィールド
- 複数の値を意味のあるまとまりとして新しい型を定義する事ができる。
package main
import "fmt"
// 構造体を定義
type user struct {
// フィールド
name string
score int
}
// メイン関数
func main() {
// newでuser構造体分の領域を確保して、初期化して、そのポインタを返す
u1 := new(user)
// ポインタが返ってきているので下記の書き方でもOK
// (*u).name = "fujimoto"
u1.name = "fujimoto"
u1.score = 200
fmt.Println(u1)
// 初期化する時に直接値を与える事も可能
// こちらの場合はポインタではない構造体のデータが入ってくる。
u2 := user{name: "arita", score: 300}
fmt.Println(u2)
}
メソッド
- golang ではオブジェクト指向プログラミングで出てくるクラスやその中で定義されるメソッドはサポートしていない。
- golang のメソッドは構造体などのデータ型に紐付いた関数になっていて、データ型の定義の中ではなく、データ型とは別に書く
- 構造体とメソッド(関数)の紐付けには レシーバ を使う
package main
import "fmt"
// 構造体を定義
type user struct {
// フィールド
name string
score int
}
// レシーバで構造体とメソッド(関数)を紐付け
func (u user) show() {
fmt.Printf("name: %s, socre: %d\n", u.name, u.score)
}
// デフォルトではuserには値渡し(コピー)でメソッドに渡される。
// *をつける事で参照渡しになる
func (u *user) hit() {
u.score++
}
// メイン関数
func main() {
u := user{name: "fujimoto", score: 500}
u.hit()
u.show()
}
インタフェース
- メソッドの一覧を定義したデータ型
- それらのメソッドをある構造体が実装していればその構造体をそのインタフェース型として扱っても良いという特徴がある。
- インタフェースを上手に使うと、異なる構造体でも、同じインタフェースを満たす型として処理できるので便利。
package main
import "fmt"
type greeter interface {
greet()
}
type japanese struct{}
type american struct{}
func (ja japanese) greet() {
fmt.Println("こんにちは!")
}
func (us american) greet() {
fmt.Println("Hello")
}
// メイン関数
func main() {
greeters := []greeter{japanese{}, american{}}
for _, greeter := range greeters {
greeter.greet()
}
}
空のインタフェース
- 全てのデータ型が空のインタフェースを満たしている。
- うまく使うとあらゆる型を受け取る事ができる関数を作る事が可能。
- 空のインタフェース型で引数を受け取った場合、引数が何かを知る必要がある。
空のインタフェース型が何かを知る手法
package main
import "fmt"
type greeter interface {
greet()
}
// 空のインタフェース型で引数を受け取る
func show(t interface{}) {
// 型アサーション
// 2つの値が返る
_, ok := t.(japanese)
// okを使って条件分岐
if ok {
fmt.Println("i am japanese")
} else {
fmt.Println("i am not japan")
}
// 型Switch
switch t.(type) {
case japanese:
fmt.Println("僕は日本人だよ")
default:
fmt.Println("僕は日本人じゃないよ")
}
}
type japanese struct{}
type american struct{}
func (ja japanese) greet() {
fmt.Println("こんにちは!")
}
func (us american) greet() {
fmt.Println("Hello")
}
// メイン関数
func main() {
greeters := []greeter{japanese{}, american{}}
for _, greeter := range greeters {
greeter.greet()
show(greeter)
}
}
goroutine
- 並行処理を簡単に書ける
package main
import (
"fmt"
"time"
)
func task1() {
// 重い処理を想定
time.Sleep(time.Second * 2)
fmt.Println("task1 finished!")
}
func task2() {
fmt.Println("task2 finished!")
}
// メイン関数
func main() {
// goを付けて並行処理にする
go task1()
go task2()
// goroutineが終わる前に main 関数自体が終わる為、待ち時間をつける。
time.Sleep(time.Second * 3)
}
channel
- それぞれのタスクからメインの方で使いたい。
- タスクでreturnなどをしたいところだが、goroutineではそれができない。
- channelはデータの受け渡しをするパイプの様なイメージ。
- 例えば、task1 の方で channel に対してデータを渡して、main の方でそのデータを取り出す。
- channel はスライスなどと同じ参照型のデータ
- makeで作成可能
package main
import (
"fmt"
"time"
)
func task1(result chan string) {
// 重い処理を想定
time.Sleep(time.Second * 2)
fmt.Println("task1 finished!")
result <- "task1 result"
}
func task2() {
fmt.Println("task2 finished!")
}
// メイン関数
func main() {
// chan型で受け渡すデータの型はstring
result := make(chan string)
// goを付けて並行処理にする
go task1(result)
go task2()
// resultの中に何も入ってなければ、入ってくるまで待つ仕様になっている
fmt.Println(<-result)
// goroutineが終わる前に main 関数自体が終わる為、待ち時間をつける。
time.Sleep(time.Second * 3)
}
実務でGoで開発したい人
GoやTypeScriptで開発したいバックエンドエンジニアを 募集 してます!