スターフェスティバル Advent Calendar 2020 の16日目です。
弊社のプロダクト「ごちクル」の一部処理がGoに改修されたこともあり勉強し始めたので基本的な文法(主に変数などの定義)をまとめてみました。
※ Udemyのこちらの教材を参照元にしています。
packageとimport
Goではプログラムのエントリーポイントとなるのがmainパッケージ
のmain関数
。
パッケージのグループは名前空間とも呼ばれる。
// パッケージの宣言
package main
// ライブラリ
import "fmt"
// main関数の定義と実行
func main() {
fmt.Println("こんにちは!")
}
// こんにちは!
ソースファイルは必ず1つのパッケージに所属させる必要がある
ため、
今回の場合はmain宣言とmain関数が存在しないとエラーになる。
また1つのファイルに複数のパッケージを設定することはできない。
パッケージはディレクトリ単位で管理される。
1つのディレクトリに複数のパッケージは置けない。
fmt(フォーマット)は、Goが標準で提供するライブラリ(パッケージ)の1つ。
変数宣言
var
を使った変数宣言の場合
package main
import "fmt"
func main() {
// 色々な型で変数宣言
var i = 1
var f64 float64 = 1.2
var s string = "test"
var t, f bool = true, false // 複数の変数宣言も可能
// 出力
fmt.Println(i, f64, s, t, f)
}
// 1 1.2 test true false
var変数を()丸括弧
で囲ってまとめて宣言することもできる。
// 省略
func main() {
var (
i int = 1
f64 float64 = 1.2
s string = "test"
t, f bool = true, false
)
fmt.Println(i, f64, s, t, f)
}
// 1 1.2 test true false
:=
を使った変数宣言
:=
を使った Short variable declaration の書き方はこう。
// 省略
func main() {
xi := 1
xf64 := 1.2
xs := "test"
xt, xf := true, false
fmt.Println(xi, xf64, xs, xt, xf)
}
// 1 1.2 test true false
:=
使った変数宣言のvarとの違いは、
関数の中でしか宣言できない
こと。
上記例の場合、func main() {}
の外で宣言すると、エラーになる。
この宣言では型が指定されていないuntyped
となり、型は自動判定される。
このuntyped
の変数に型を付けたい場合は、下記のようにする。
// 省略
func main() {
xf64 := 1.2
fmt.Printf("%T", xf64)
// 型を確認できるPrintfで確認
// float64
var xf32 float32 = 1.2
// 型をfloat32と明示的に宣言
fmt.Printf("%T", xf32)
// 型を確認できるPrintfで確認
// float32
}
型を明示的に宣言したいときはvar
で、
簡単に宣言したいときは:=
を使う。
const
を使った変数宣言
const
は定数宣言。
変数名は、頭文字を大文字にすると他のファイルからも参照できるグローバル変数
になる。
const Pi = 3.14
constも型を指定しないuntyped
の変数。
型について
数字型
数値型は下記のように宣言する。
数値型の一覧ドキュメントはこちら。
package main
import "fmt"
func main() {
var (
u8 uint8 = 225
i8 int8 = 127
f32 float32 = 0.2
c64 complex64 = -5 + 12i
)
fmt.Println(u8, i8, f32, c64)
fmt.Printf("type=%T value=%v", u8, u8)
}
// 225 127 0.2 (-5+12i)
// type=uint8 value=225
(Printfのドキュメントはこちら)
Printf
で型やvalueを確認することができる。
// 省略
x := 1 + 1
fmt.Println(x)
fmt.Println("1 + 1 =", 1+1)
fmt.Println("10 - 1 =", 10-1)
fmt.Println("10 / 2 =", 10/2)
fmt.Println("10 / 3 =", 10/3)
fmt.Println("10.0 / 3 =", 10.0/3)
fmt.Println("10 / 3.0 =", 10/3.0)
fmt.Println("10 % 2 =", 10%2)
fmt.Println("10 % 3 =", 10%3)
}
// 2
// 1 + 1 = 2
// 10 - 1 = 9
// 10 / 2 = 5
// 10 / 3 = 3
// 10.0 / 3 = 3.3333333333333335
// 10 / 3.0 = 3.3333333333333335
// 10 % 2 = 0
// 10 % 3 = 1
インクリメントを使うこともできる。
// 省略
x := 0
fmt.Println(x) // 0
x++
fmt.Println(x) // 1
x--
fmt.Println(x) // 0
}
文字列型
文字列型は下記のように定義できる。
package main
import "fmt"
func main() {
fmt.Println("Hello World")
fmt.Println("Hello " + "World")
}
// Hello World
// Hello World
stringのキャスト
文字列の一番初めの文字を表示したい時、
fmt.Println("Hello World"[0])
このようにインデックスを指定するだけでは、アスキーコードが取得されてしまい、取得できない。
string
を使ってキャストする必要がある。
package main
import (
"fmt"
"strings" //追加
)
func main() {
fmt.Println("Hello World"[0]) // 72
fmt.Println(string("Hello World"[0])) // H
}
エスケープ
バッククオートで囲うと、エディタの改行がそのまま反映できる。
ダブルクオートの中にダブルクオートを入れたい場合は \(バックスラッシュ)でエスケープ
fmt.Println(`Test
Test`)
fmt.Println("\"")
}
// Test
// Test
// "
論理値型
package main
import "fmt"
func main() {
// %T = 型
// %v = 値
// %t = 単語、true または false 指定した型じゃないと正しく表示されない
// var t, f bool = true, false
t, f := true, false
fmt.Printf("%T %v %t\n", t, t, t)
fmt.Printf("%T %v %t\n", f, f, f)
// 論理演算子 && (and)
fmt.Println(true && true) // 真かつ真 = 真
fmt.Println(true && false) // 真かつ偽 = 偽
fmt.Println(false && false) // 偽かつ偽 = 偽
// 論理演算子 || (or)
fmt.Println(true || true) // 真もしくは真 = 真
fmt.Println(true || false) // 真もしくは偽 = 真
fmt.Println(false || false) // 偽もしくは偽 = 偽
// 論理演算子 ! (not)
fmt.Println(!true) // false 条件の反対の結果
fmt.Println(!false) // true 条件の反対の結果
}
型変換(キャスト)
Goではinteger -> float
のような数値同士の型変換は簡単にできるが、文字列 -> 数値
のような型変換は少しコツがいる。
integer->floatへの型変換(簡単)
下記のようにスムーズに変換できる。
(Printfのドキュメントはこちら)
package main
import "fmt"
func main() {
var x int = 1
xx := float64(x) // int を float64 に型変換
fmt.Printf("%T %v %f\n", xx, xx, xx)
}
// float64 1 1.000000
// int -> float64 へ型変換できている
string -> int
に型変換(ちょっとコツいる)
文字列の相互変換(コンバージョン)用のライブラリstrconv
を使用して型変換する。
(strconvのドキュメントはこちら)
package main
import (
"fmt"
"strconv" //追加
}
func main() {
var s string = "14"
i, _ := strconv.Atoi(s) // int型に変換
fmt.Printf("%T %v\n", i, i)
}
// int 14
// string -> int へ型変換できている
Atoi関数はAscii to integerの略。
i, _ := strconv.Atoi(s)
の_
について。
Atoiは返り値を2つ(int, error)返す関数。
2つ目の返り値はエラーハンドリング用だが、
i := strconv.Atoi(s)
このように省略するとエラーになってしまう。
今回のように使わない場合は_
で省略することができる。
配列
goでの配列の基本的な書き方はこんな感じ。
[]
スクエアブラケットの中身は、配列の個数が入る。
配列の場合は、[2]int
この部分が型になる。
package main
import "fmt"
func main() {
// 配列
var a [2]int
a[0] = 100
a[1] = 200
fmt.Println(a)
// この書き方も可
var b [2]int = [2]int{100, 200}
fmt.Println(b)
}
// [100 200]
// [100 200]
配列は[2]int
このように配列の個数と型が決まっているため、配列にappend
などで追加してリサイズすることはできず、エラーとなる。
// 省略
func main() {
var b [2]int = [2]int{100, 200}
b = append(b, 300) //このように配列に追加しようとするとエラー
fmt.Println(b)
}
// first argument to append must be slice; have [2]int
// b redeclared in this block
ではどうやって追加するのか?
配列ではなくスライス
を使用する。
スライス
スライスは配列と違い、個数を指定せず下記のように宣言する。
// 配列
var b [2]int = [2]int{100, 200}
// スライス
var b []int = []int{100, 200}
スライスは個数を追加することができる。
package main
import "fmt"
func main() {
n := []int{1, 2, 3, 4, 5, 6}
// スライスに値を追加
n = append(n, 100, 200, 300, 400)
fmt.Println(n)
}
// [1 2 3 4 5 6 100 200 300 400]
配列はリサイズできない。
スライスはリサイズできる。
スライスにインデックスを指定する
スライスをインデックス指定で取得できる。
●〜●番目という風にレンジで取得する際の数え方が独特。
package main
import "fmt"
func main() {
n := []int{1, 2, 3, 4, 5, 6}
// 配列の●番目
fmt.Println(n[2])
// 配列の●〜●番目
fmt.Println(n[2:4])
// 配列の〜●番目まで
fmt.Println(n[:2])
// 配列の●番目以降
fmt.Println(n[2:])
// 配列すべて表示
fmt.Println(n[:])
}
// 3
// [3 4]
// [1 2]
// [3 4 5 6]
// [1 2 3 4 5 6]
スライスの値を書き換える
指定したインデックスの値を書き換える。
// 省略
func main() {
n := []int{1, 2, 3, 4, 5, 6}
n[2] = 100
fmt.Println(n)
}
// [1 2 100 4 5 6]
スライスを入れ子にする
// 省略
func main() {
n := []int{1, 2, 3, 4, 5, 6}
var board = [][]int{
[]int{0, 1, 2},
[]int{3, 4, 5},
[]int{6, 7, 8},
fmt.Println(board)
}
// [[0 1 2] [3 4 5] [6 7 8]]
スライスに追加する
// 省略
func main() {
n := []int{1, 2, 3, 4, 5, 6}
n = append(n, 100, 200, 300, 400)
fmt.Println(n)
}
// [1 2 3 4 5 6 100 200 300 400]
map(連想配列)
mapの基本的な書き方は下記の通り。
package main
import "fmt"
func main() {
m := map[string]int{"apple": 100, "banana": 200}
fmt.Println(m)
}
// map[apple:100 banana:200]
mapをキーを指定して取り出し
// 省略func main() {
m := map[string]int{"apple": 100, "banana": 200}
// キーを指定してvalueを取り出す
fmt.Println(m["apple"])
// banana のvalueを 300 で上書き
m["banana"] = 300
fmt.Println(m)
}
// 100
// map[apple:100 banana:300]
mapに追加する
// 省略
func main() {
m := map[string]int{"apple": 100, "banana": 200}
// 追加する
m["new"] = 500
fmt.Println(m)
}
// map[apple:100 banana:300 new:500]
mapで存在しないキーを使った場合
存在しないキーを指定した場合は0
が返る。
mapの宣言の際、2つ目の返り値を指定すると、値が存在するかどうかbool型で確かめることができる。
// 省略
func main() {
m := map[string]int{"apple": 100, "banana": 200}
// 存在しないキーを指定
fmt.Println(m["nothing"])
// 2つ目の返り値を指定
v, ok := m["apple"]
fmt.Println(v, ok)
}
// 0
// 100 true
makeを使ったmapの初期化
make
を使うと、空のmapを作成できる。
下記では空のmapを:=
で定義した。
func main() {
m := make(map[string]int)
m["pc"] = 5000
fmt.Println(m)
}
// m["pc"] = 5000
対してvar
で同じように定義すると、panic
というエラーが起きる。
func main() {
var m map[string]int
m["pc"] = 5000
fmt.Println(m)
}
// panic: assignment to entry in nil map
これは宣言はしているものの、メモリー上に入れるmapがないのでエラーとなる。
以上、Goの色々な定義の仕方をまとめてみました。
定義編はここまでにします。
また後日、続きを書きたいと思います。