目次
Goとは
GoはGoogleでRobert Griesemer、Rob Pike、Ken Thompsonによって設計された静的型付けコンパイル型プログラミング言語[11]。構文的にはC言語に似ているが、メモリーセーフ、ガベージコレクション、構造型付け、CSPスタイルの並行性[6]がある。以前のドメイン名golang.orgからしばしばGolangと呼ばれるが正式名はGoだ。
Goの特徴
- 文法がシンプル
- 実行速度が速い
- 言語レベルで並行処理をサポートしている
- Goルーチンとチャネルを使うとできるそう。
Goを書く時の暗黙のルール
- Goのインデントはスペース4つ
- Goでは文の末尾にセミコロンをつけない(省略可能)
- スコープが狭い場合、1文字の変数名が許される
新しく知ったこと
printlnのln
- printlnのlnは、line(行)の意味です。文字列を出力した後に次の行に自動的に改行されます。printlnを使わないと改行されません。
printlnを使わない場合
package main func main() { print("Hello World") print("Hello 世界") // => Hello WorldHello 世界 }
printlnを使った場合
package main func main() { println("Hello World") println("Hello 世界") /* // => Hello World // => Hello 世界 */ }
Goでは println
を使うことはほぼなく、fmtパッケージのfmt.Println
を使います。後者の方が表示できるデータ型が多いです。
ファイル内の構成
Goのコードが書かれているファイルは、パッケージ定義部と関数定義部によって構成されています。
// main.go // パッケージ定義部分 package main // 関数定義部分 func main() { // 下記の「世界」の部分を「Go」に書き換えてください println("Hello, Go") }
funcキーワード
Goで関数を定義する場合、funcを使います。
func getUser() { println("ユーザー") }
変数宣言
変数はvarで宣言します。関数内部だと :=
でvarと型を省略して変数を宣言できます。:=は変数の初期化時に使います。
package main // .付きインポートをすると、fmt.PrintlnをPrintlnと書くことができる import . "fmt" func main() { var name string = "yuki" // 変数宣言時に初期化をしなくても良い // その場合、型を書く var name2 string name2 = "yukihoge" // 値を代入する場合、何を代入するか明らかなので、型を定義しなくても良い var otherName = "hoge" // goにはvarと型を省略する書き方がある userName := "fuga" Println(name) // => yuki Println(name2) // => yukihoge Println(otherName) // => hoge Println(userName) // => fuga }
変数名はローワーキャメルケース(hogeFuga) or アッパーキャメルケース(HogeFuga)で書きます。 ローワーキャメルケースかアッパーキャメルケースかで、変数の可視性が変わります。前者はプライベート。後者はパブリックになります。パブリックの場合、パッケージを横断して使うことができます。基本的にはローワーキャメルケースで定義したほうが、プライベートになるので良いです。(アッパーキャメルケースだからというわけではなくて、1文字目が大文字だとエクスポートされるという仕組みがGoにあるだけです)
また、Goでは1文字の変数名が許されます。スコープが短い場合、変数名を1文字で書いても可読性が落ちないので、許されているそうです。しかし、その1文字は何でも良いわけではなくて、その変数が何を表しているのか・担っているのか、という本質的な意味を表現する変数名にした方が良いです。lineCount
をlではなくcと書くように。
package main import . "fmt" func main() { c := 1 Println(c) // => 1 }
定数
constキーワードを使うことで定数を定義できます。定数に :=
を使用することはできません。自分が見た感じだと定数の命名規則のルールは決まっていないのかと思いました。個人的には変数と同じでローワーキャメルケースとアッパーキャメルケースを使い分けるのがいいのかと思います(Goでは1文字名を大文字で書くとエクスポートされるからです)。
package main import . "fmt" func main() { const message = "hoge" Println(message) // => hoge }
switch文
Goのswitch文は、caseの最後に暗黙でbreak文が入ります。
package main func main() { n := 3 switch n { case 1: println("大吉です") case 2, 3: println("吉です") default: println("凶です") } }
標準パッケージ
Goには標準パッケージがあるので、自分のプログラムに標準パッケージをインポートすることで、自分で1からプログラムを書かなくても、便利な関数を利用することができます。fmtはよく使います。 Goでは println を使うことはほぼなく、fmtパッケージのfmt.Printlnを使います。後者の方が表示できるデータ型が多いです。
パッケージ名 | 概要 |
---|---|
fmt | コンソールに出力できる |
math/rand | ランダムな数値を生成できる |
time | 時間に関する処理ができる |
ドット付きインポートをすることで、パッケージの関数を呼び出すときに、パッケージ名を省略できます。通常、fmt.Println("hello world")
と書くところをドットをつけてインポートすると Println("hello world")
と書けます。
// グループ化をすることで、一つのimportステートメントで複数のパッケージをインポートできる import ( // ドット操作 . "fmt" "string" )
fmt.Printf
fmtパッケージのPrintfを使うことで、書式を指定して文字列を出力できます。書式とは出力する文字列の形です。TSのテンプレートリテラルのようなものです。
// 書式の中の%sの部分に、出力に用いる値が入る。 // %sを複数使う場合、出力に用いる値をカンマ区切りで並べていけば良い。 // 文字列を埋め込むなら%s。数値を埋め込むなら%dを使う // fmt.Printf(書式, 出力に用いる値) package main import . "fmt" func main() { a := 25 n := "yuki" Printf("年齢: %d, 名前: %s", a, n) // => 年齢: 25, 名前: yuki }
Goのfor文
Goのfor文のループで使う変数は、 :=
を使って宣言しないとエラーが起きます。
package main import . "fmt" func main() { for i := 0; i < 3; i++ { a := 25 n := "yuki" // Printfは改行されないので、改行したい場合、末尾に\n(特殊文字)を書きます Printf("年齢: %d, 名前: %s\n", a, n) /* 年齢: 25, 名前: yuki 年齢: 25, 名前: yuki 年齢: 25, 名前: yuki */ } }
乱数
ランダムな数のことを乱数と呼びます。
Goで乱数を扱うためには、math/randというパッケージを使います。
rand.Intn(10)で0 ~ 9の乱数を生成します。rand.Intn(10)では実行するたびに同じ乱数が表示されます。
そのため、完全な乱数を生成するためには、「rand.Seed(time.Now().Unix())」という1行を追加する必要があります。この1行はmain配下に書きます。事前にtimeパッケージをインポートする必要があります。
package main import ( "fmt" "math/rand" "time" ) func main() { rand.Seed(time.Now().Unix()) for i := 1; i <= 3; i++ { fmt.Printf("%d回目のおみくじ結果:", i) number := rand.Intn(6) switch number { case 0: fmt.Println("凶です") case 1, 2: fmt.Println("吉です") case 3, 4: fmt.Println("中吉です") case 5: fmt.Println("大吉です") } } /* 1回目のおみくじ結果:中吉です 2回目のおみくじ結果:中吉です 3回目のおみくじ結果:凶です */ }
main関数
main関数は、プログラムが実行されると最初に呼び出される特別な関数です。C++とRustと同じです。
fmt.Scan(&変数名)
fmt.Scan(&変数名)
を使うと、コンソールでの入力ができます。エンターキーを押すまで、変数の値として認識されます。
package main import ( . "fmt" "math/rand" "time" ) func main() { rand.Seed(time.Now().Unix()) var input string Scan(&input) Printf("入力された値: %s", input) }
関数の命名規則
変数と同じでアッパーキャメル or ローワーキャメルケースにします。 外部に公開する場合、アッパーキャメルケースを使います。
関数の戻り値の型
関数に戻り値がある場合、戻り値の型を定義しないとエラーになります。
package main import ( . "fmt" ) func main() { f := "Yuki" l := "Haga" n := createName(f, l) Println(n) // => Haga Yuki } // 関数を定義する順番に決まりはない func createName(first string, last string) string { return last + " " + first }
変数はどこで記録されている
変数はメモリ上のある「場所」に記録されています。この「場所」を表す16進数のことをアドレスと言います。入門者向けの本ではこの16進数を「~番地」で表現することが多いです。
メモリの説明に関しては、以下の記事「第3回:アドレスとポインタ変数 (5/18)」がわかりやすかったので引用させていただきます。
たとえば,C言語プログラム中で int a; と整数の変数を1つ定義すると,整数の値1個を格納する場所がメインメモリ上に確保され,a という名前を使ってこの場所に値を書き込んだり参照したりすることができるようになる。
変数のアドレスを取得するには、「&変数名」とします。 メモリ上の記録する場所はコンピュータによって変わるため、プログラムを実行する度に違うアドレスを出力する場合もあります。
package main import ( . "fmt" ) func main() { n := "Yuki" Println(n) // => Yuki Println(&n) // => 0xc00010a210 }
ポインタ
Goにおけるポインタとは、アドレスのことです。 アドレスは値なので、変数に代入できます。 アドレスが代入された変数のことを、ポインタ型変数と呼びます。 ポインタ型変数を定義するためには、変数のデータ型にアスタリスクをつけます。このデータ型には、ポインタで取得する変数の型を指定します(一般的かわかりませんが、ポインタ型変数の名前の末尾には、Ptrをつけてます)。:=を使ってポインタ型変数を宣言することもできます。
package main import ( . "fmt" ) func main() { c := 1 var cPtr *int = &c Println(cPtr) // => 0xc00010a210 n := "Yuki" var nPtr *string = &n Println(nPtr) // => 0xc00009e210 }
ポインタ型変数にをつけて「ポインタ型変数名」にすると、ポインタが指し示す変数の「値」を取り出すことができます。
package main import ( . "fmt" ) func main() { c, n := 1, "Yuki" cPtr, nPtr := &c, &n Println(*cPtr) // => 1 Println(*nPtr) // => Yuki }
「*ポインタ型変数名 = 更新する値」で、ポインタ型変数を使って、ポインタが指し示す変数の値を更新できます。
package main import ( . "fmt" ) func main() { c, n := 1, "Yuki" // ポインタ型変数を定義 cPtr, nPtr := &c, &n *cPtr = 10 Println(*cPtr) // => 10 Println(c) // => 10 Println(*nPtr) // => Yuki }
関数の仮引数をポインタ型にすることができます。関数を呼び出すときの実引数にはアドレスを指定します。
package main import ( . "fmt" ) func main() { c := 0 for i := 0; i < 2; i++ { addCount(&c) Println(c) } // addCountの引数の型をポインタ型にしない場合、0 0 が出力される /* 1 2 */ } func addCount(cPtr *int) { *cPtr += 1 }
スコープが違うと、同じ変数名でも別の変数として扱われる
スコープが違うと、同じ変数名でも別の変数として扱われます。 本当に別の変数として扱われることを証明するには、ポインタを使います。アドレスが違うので、同じ変数名でも別の変数として扱われていることが分かります。
package main import ( . "fmt" ) func main() { c := 0 Println(&c) addCount() /* 0xc0000b2000 0xc0000b2008 */ } func addCount() { c := 0 Println(&c) }
ポインタの良いところ
ポインタの良いところは、スコープを超えて値を参照・更新できることです。 ポインタ型を関数の引数に指定することで、ポインタ型変数を使って別のスコープの変数を更新できます。
もしポインタを使わない場合、別の関数の結果を受け取りたい場合、変数を定義する必要があります。
package main import ( . "fmt" ) func main() { c := 0 c = addCount(true, c) c = addCount(false, c) c = addCount(true, c) Println(c) // => 2 } func addCount(isOk bool, c int) int { if isOk { c += 1 return c } else { return c } }
ポインタを使うことで、戻り値を指定したり、変数に再代入させずに、 main関数に定義した変数を別の関数内で更新できます。
package main import ( . "fmt" ) func main() { c := 0 addCount(true, &c) addCount(false, &c) addCount(true, &c) Println(c) // => 2 } func addCount(isOk bool, c *int) { if isOk { *c += 1 } }
&と*の使い分けを以下の表にまとめます。
ポインタに関する記号 | いつ使う |
---|---|
& | 変数名の前につけて使う。変数のアドレスを取得したい時に使う。 |
* | ポインタ型変数を宣言したり(型につける)、ポインタ型変数を使ってポインタの指し示す値を参照したり更新する時に使う。関数の引数をポインタ型にしたい時にも使う。 |
ファイル名
Goを各ファイルはスネークケースで書くのが一般的だそうです。
// 例 main.go addressed_types_test.go addressed_types.go
感想
ゴールーチンというものがGoの醍醐味というのをみたので、今度やってみようと思います。
参考記事
Goルーチンで並行化する方法: 6秒かかる処理を3秒にしよう - Qiita
go言語のプロジェクトの雛形を作る | コーラは1日500mlまで
【Go】PackageのPublicとPrivateについて | POST OUTPUT