GOの基本が知りたくなった
以前、少し書いた(多分interfacesの辺り)が手違いで消してしまった。
やる気が失われたので、基礎をすっ飛ばして
自分が欲しい物を作ってみて学習するスタイルへシフトした。
いや、基本は学んどこう。と反省して復習兼ねて最初からもう一度やる。
TourOfGoやる
Packages
GOのプログラムはpackageで構成される。
プログラムのエントリポイントはmainパッケージである必要がある。らしい
パッケージ名はインポートパスと同じ名前にする必要がある。
package/hoge という場所に作るものはhogeというパッケージにする。
Imports
書き方は2種類
- グループ化して書く方法(基本的にこれでよかろう)
import (
"fmt"
"math"
)
- 1行ずつ書く方法
import "fmt"
import "math"
Exported names
goにおいて最初の文字が大文字で始まるものは外部から参照可能なものである。という意味になる
(他の言語でいうところのパブリックですね)
package main
import (
"fmt"
"math"
)
func main() {
fmt.Println(math.pi)//←これはありえない。「math.Pi」になる
}
Functions
関数の書き方
基本
func 〜〜〜で書く。
多くの多言語との違いとして引数と戻り値の型を名前の後ろに書く。という所。
func add(x int, y int) int {
return x + y
}
同じ型の引数が2つ以上ある場合は省略できる
func add(x int, y int, a string, b string) int {
って書いてエラーではないが
↓のように書けば良い。
func add(x, y int, a, b string) int {
return x + y
}
複数の戻り値を返す関数
()で囲んで複数書く
func swap(x, y string) (string, string) {
return y, x
}
Named return values
関数の宣言部分で戻り値の変数を宣言できる。
func split(sum int) (x, y int) {//←ここで宣言されているので内部では宣言せずに利用できる
x = sum * 4 / 9
y = sum - x
return x, y
//↑これは実は省略できる。
//↓省略パターン。return x, y と同意味。
return
//変数名のチェックまでは行われず型の一致だけチェックされる。
return 1, 1
とか
return y, x
とかでもOK
}
(正直意味わからなくなりそうなのであまり使いたくないかも。と思った)
Variables
変数宣言
varです。
同じ型の変数はカンマで連結して複数一括で宣言できる(引数と一緒)
var c, python, java bool
Variables with initializers
変数の初期化子の話
var i, j int = 1, 2
この時に型を省略することが出来ます。との事
var i, j = 1, 2
//この際には初期化子から型が推論され自動セットされる。
//goをかじったばかりの私はi, j := 1, 2 って出来ないの?と思ってやったら出来なかった。
//↑関数の中ならできる。関数外(パッケージ直下)では出来ない
//関数の外で変数を宣言するときはvarが必要。
Short variable declarations
関数の中ならvar省略して宣言できますよ。の話。
func main() {
var i, j int = 1, 2
//↑こう書き換える事が出来る
//i, j := 1, 2
k := 3
c, python, java := true, false, "no!"
fmt.Println(i, j, k, c, python, java)
}
Basic types
型の話
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // uint8 の別名
rune // int32 の別名
// Unicode のコードポイントを表す
float32 float64
complex64 complex128
Zero values
型によって初期値が決まっているから初期化子を与えずに変数宣言してもnullにはならんよ。という話
型ごとの初期値
数値型(int,floatなど): 0
bool型: false
string型: "" (空文字列( empty string ))
Type conversions
型変換の話。
基本 変換したい型(値)
でできる。
//例
i := 42
f := float64(i)
u := uint(f)
Constants
定数の話。
定数は、文字(character)、文字列(string)、boolean、数値(numeric)のみで使える。
定数は const を使って宣言必須 (変数の様にvar省略して書く。という記法は無い)
package main
import "fmt"
const Pi = 3.14
func main() {
const World = "世界"
World = "hoge"
fmt.Println("Hello", World)
fmt.Println("Happy", Pi, "Day")
const Truth = true
fmt.Println("Go rules?", Truth)
}
For
ループの話。
GOではループはforのみ。
これがwhileでありforでありforeach。
func main() {
sum := 0
//いわゆるforとして使う場合はいわゆるfor,括弧がないくらいか
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum)
}
For continued
forの初期化と後処理ステートメントは任意です。
for ; i < 10; {
って書けますよ。さらに;
が省略可能で
for i < 10 {
と書けますよ。という話。
Forever
無限ループさせたい時の話。
条件を全部省略すれば無限ループになる
for true {
//無限ループ内処理
}
としたくなるが
for {
//無限ループ内処理
}
で良い
If
IFの話。
if x < 0 {
return sqrt(-x) + "i"
}
これだけ。括弧は不要
If with a short statement
if文の中にもforみたいなステートメントが書けますよ。という話
if v := math.Pow(x, n); v < lim {
return v
}
//このvはifのカッコ内だけが使える。
If and else
if文ないのステートメントで宣言している変数はelseでも使えますよ。という話
if v := math.Pow(x, n); v < lim {
a := 1
return v
} else
fmt.Printf("%g >= %g\n", v, lim)
}
Switch
breakは無いんだよ。という
(各caseのセクションが終わると自動的にブレイクする)
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.\n", os)
}
}
Switch with no condition
条件のないSwitch
IF~ELSE~IF~ELSE~IF系をわかりやすく書くことが出来る(かも)
if t.Hour() < 12 {
fmt.Println("Good morning!")
} else {
if t.Hour() < 17 {
fmt.Println("Good afternoon.")
} else {
fmt.Println("Good evening.")
}
}
//↑みたいな腐ってきているIFをスッキリと書くことが出来る↓
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
Defer
deferをつけて呼び出した関数はfunctionの最後に実行される。
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
Stacking defers
関数内で複数Deferが使われる場合、それらはスタック(後入先出、LIFO)されて最後に実行される。
最初いきなりTourOfGoやったときは、これ何に使うん??って思ったけど普通に便利だった。
多言語でのfinaly的な用途で使える。
例えば↓ファイルAをファイルBに書き出すfunction
それぞれファイルを開いた後にすぐdeferをつけてファイルクローズの関数を呼んでいる。
これで関数終了時にはファイルがクローズされる事が約束される。
(いや、何に使うん?からあら便利ねーっに変わった)
func main() {
var reader *bufio.Reader
var writer *bufio.Writer
read_file, _ := os.OpenFile("A", os.O_RDONLY, 0600)
defer read_file.Close() //←最後にファイルAを閉じる
reader = bufio.NewReader(read_file)
write_file, _ := os.OpenFile("B", os.O_WRONLY|os.O_CREATE, 0600)
defer write_file.Close() //←最後にファイルを閉じる
writer = bufio.NewWriter(write_file)
for {
line, err := reader.ReadBytes('\n')
if err == io.EOF {
return
}
writer.Write(line)
writer.Flush()
}
}
Pointers
ポインタの宣言
*(アスタリクス)で宣言する。
var p *int //pはint型の変数を指すポインタである。
変数のポインタをポインタ変数に代入する時
&を使う
i := 42
p = &i //&をつけることでpにはiのポイントが入る。
ポインタの先にある値(変数の中身を使いたい時)
*を使う
fmt.Println(*p) // ポインタpを通してiから値を読みだす
*p = 21 // ポインタpを通してiへ値を代入する
Structs
構造体。(最初クラスか?と思ったけどクラスとはちょっと話が違う様だ…)
type Vertex struct {
X int
Y int
}
Struct Fields
ドットを使うことでプロパティにアクセスできます。
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
v.X = 4
fmt.Println(v.X)
}
Pointers to structs
構造体のフィールドはポインタを通してアクセスすることが出来る
↓どういうことか
func main() {
v := Vertex{1, 2} //変数としての実態はv
p := &v //pにはvのポインタが入っている
p.X = 1e9 //←これ
//↑普通に考えるとpはポインタであってstructではないのだからp.Xなんてエラーになりそうなもんだが使える。
//↑このp.Xは(*p).Xと書くことも出来る。これはまぁそうだろうな。という感じ
//↑これでは面倒くさかろう(まぁ確かに…)ということで、p.Xでも使えるようにしてくれている模様。
fmt.Println(v)
}
Struct Literals
structリテラルは、フィールドの値を列挙することで新しいstructの初期値の割り当てを示しています。
正直、日本語がよくわからない…
↓どういうことか
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
)
func main() {
fmt.Println(v1, p, v2, v3)
}
構造体を使う時の初期化方法の話だった
Vertex{1, 2}
ただ値をカンマで区切ると宣言順に格納
Vertex{X: 1}
フィールドの値を指定して初期化することもできる
Vertex{}
していなければ対象の型の初期値が入る(例だとintなので0)
&Vertex{1, 2}
実体となった構造体のポインタを返す
Arrays
配列の話
[n]T 型は、型 T の n 個の変数の配列( array )を表します。
頭が悪いのでこういう書き方をされるとすぐわからなくなる。
↓例を見たらすぐわかった。
var a [10]int
配列は 要素数+型で宣言する
int型の配列[10]int
//varを省略する場合{}は必須
a := [2]int{}
//→[0 0]
a := [2]int{1,1}
//→[1 1]
a := [2]int{1}
//→[1 0}
Slices
可変長の配列の話
宣言時に要素数を指定すると配列、葉数を指定しないとスライスになる。
func main() {
//配列
primes := [6]int{2, 3, 5, 7, 11, 13}
//配列からスライスに
var s []int = primes[1:4] //配列名[スタートインデックス:終了インデックス]で配列から指定した範囲のスライスを生成出来る
fmt.Println(s)
}
Slices are like references to arrays
配列から値を引っ張った場合に、スライスに入っている値はコピーではなく参照なんですよ。という話
スライスの値を変更すると参照元の配列も変更される。
Slice literals
q := []int{2, 3, 5, 7, 11, 13}
Slice defaults
スライスするときの省略形について
var a [10]int
//↓は全て同じ意味
a[0:10]
a[:10] //最初の数字を省略すると先頭を指したのと同意になる
a[0:] //2番めの数字を省略すると最後尾を指したのと同意になる
a[:] //なので両方省略すると全部
Slice length and capacity
スライスは長さ( length )と容量( capacity )の両方を持っています。という話
スライスの長さとは
スライスの要素数のこと
スライスの容量とは
スライスの最初の要素から数えたときの配列の要素数
サンプルコード
package main
import "fmt"
func main() {
//printSliceでlen(長さ)cap(容量)スライスの中身を出力する。
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)
//len=6 cap=6 [2 3 5 7 11 13]
//↑配列{2, 3, 5, 7, 11, 13}から作成した長さ6、容量6のスライス
// Extend its length.
s = s[:2]
printSlice(s)
//len=2 cap=6 [2 3]
//↑最初から2個目までで再スライスする。
// Drop its first two values.
s = s[2:]
printSlice(s)
//len=0 cap=4 []
//↑2番目以降から最後までで再スライスすると何もなくなった
s = s[:4]
printSlice(s)
//len=4 cap=4 [5 7 11 13]
//↑値がなくなったように見えるが、省略形ではなく数値を指定したやると後方の値は復活する
//おそらくもとになる配列が残っているから(多分)ポインタのため、後方には戻れない模様
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
Nill slices
sliceの初期値に関してはnil(NULL)になってます。という話。
Creating a slice with make
sliceはmakeで作れるんです。という話
a := make([]int, 5)
//[0 0 0 0 0]
//3番目の引数としてスライスの容量を指定できる
b := make([]int, 0, 5)
//[]
Appending to a slice
スライスへ要素を追加するにはappendを使うんだよ。という話
Range
foreachみたいな事する時の話。
(途中)
(続く)