header image
header image

Go言語の文法を勉強して印象にのこったところまとめ。

Go言語の文法を勉強して印象にのこったところまとめ。

今年はGo言語を新たにはじめてみたいなということで、 とりあえず文法をなめるために一冊本を買って勉強しました。

そのまとめとしてGoの文法をまとめていきたいと思います。 ただ単に全ての文法事項をリスト的にまとめてもおもしろくないので 個人的に感じたGoはこういうところが特徴的なのね。というコメントをを書きつつまとめていければと思います。

勉強に使用した本

とりあえず入門としてGoの文法をさらいたかったので、 こちらの本を選択しました。

改訂2版 基礎からわかる Go言語

まだ一周読んだだけですが、 これ一冊でバランスよくGoの文法がまとめられているので 一回通しでよんだ後に、簡単なコマンドベースのコードを書きつつ 不明なところを再度読み直せば結構かけるようになる気がします。

Go言語(golang)について

ここらへんはすでに知っている人も多いのかもしれませんが一応まとめておくと、 Go言語は、Googleが2009年に公開したプログラミング言語で、まだ公開されてから 10年弱といったところのようです。この記事を書いている時点で最新版のバージョンは1.11 です。

Goの特徴としては、シンプルな構文でコンパイルが速い、並列処理をサポートしている点に あります。C言語のようなインクルードファイルを採用しなかったのでCに比べてコンパイルが格段に 速いそうで、並列処理に関しては、ゴルーチンと呼ばれる実行単位で複数の処理を同時かつ 効率的に実行することができます。

他にも、継承がなかったりメソッドが複数の値を返却できたりと様々な特徴があるようです。

まだ本を読んだだけで実際のコードは書いていないのですが、 Javaとかみたいなもっさりしたコードを書く必要はなさそうで、行末のセミコロンを省略できたり if文の丸括弧がいらなかったりで読んでるだけでもシンプルに書けそうだなーという感じでした。

文法のまとめ

ここからは実際にGo言語の文法に触れていきますが、 特徴をリスト化して書くのでなく個人的に印象に残ったところを書いています。 さーっと読むとGoってこんな感じなのねというのが雰囲気で伝われば良いのかなと 思っています。

ちゃんと学びたいのであればリファレンスをみるなり本を買ったりして勉強した方が よいので流し読みで雰囲気を感じとって頂ければ

コンパイルと実行

Go言語の実行は

go run hello.go

と書け、go runとするとコンパイルと実行を一発でやってくれるそうです。 Java, Cとかだとコンパイルと実行はは完全に別でやるので地味に便利です。

コンパイルだけしたい場合は、

go build hello.go

として実行したくなった場合はそのまま、ビルドされたファイルを叩くだけです。

基本的な文法

Hello Worldを出力するプログラムは以下になります。

package main

import "fmt"

func main() {
  fmt.Println("Hello World")
}

Go言語のソースファイルは必ずpackage文から始まり, mainパッケージのmain関数が ファイル実行時に最初に呼び出されます。

見てわかるように行末のセミコロンが不要です。Go言語ではセミコロンを書かなくてよくなったことで、

func main()
{
  fmt.Println('Hello World')
}

このような関数定義の開始の{ を行頭にする書き方ができなくなっているようです。 (正確には誤って解釈されるかコンパイルエラーになっているそう)

型について

Go言語にはクラスという概念が存在しません。クラスはなく型しかありません。 値の型を他の型へ変換することをGoでは「変換」(キャストとは言わない?)というそうで結構型チェックが厳しく、 明示的に変換を使用しないと整数同士の型であっても代入、比較できないようです。

変数について

もっとも基本的な変数の宣言方法は

var 変数名 型 = 初期値

ですが、初期値を与えた時は型を省略して記述でき

var i = 123

さらにvarキーワードも:=を使用することで省略できます。

i := 123

:=はなにか特別な演算子のようにもみえ最初にGoのコードを見た時には「?」となりましたが、 、変数宣言時の記述量減らせて便利ですね。

 

定数の宣言にはconstを使います。

const c = 123

さらには、Goには「iota」列挙子なるものが存在するらしくiotaを 使うと連番が簡潔に書けちゃいます。

const (
  ZERO = iota
  ONE = iota
  TWO = iota
)

 

こうすると、

定数ZEROには0の値、定数ONEには1、定数TWOには2が代入されて自動的に連番を作れるそうです。定数に割り振る数字を変えたいなどの場合に間に新しい定数をさしこむだけで変更できるので連番の定数を使うときなどは使えそうですね。

男性、女性の連番の数字の定数にその他を0の値で追加したいという時に サクッと追加できますね。 (普通に書くとMALE, FEMALEの値を繰り上げて書き直さないといけない)

 

const (
  MALE = iota   // 0
  FEMALE = iota // 1
)

 

const (
  OTHER = iota  // 0
  MALE = iota   // 1
  FEMALE = iota // 2
)

繰り返し処理について

Go言語にも他の言語と同様にFor文が用意されていますが、 GoのFor文はかっこがいりません。

for i :=0; i < 5; i++ {
  fmt.Println(i)
}

また、Goにはwhile文がなく、多言語ののwhile文にあたる処理は for文で書きます。

for i < 5 {
  fmt.Println(i)
  i++
}


// 無限ループはこちら
for {

}

rubyのeach文、jsのforEach、Javaの拡張for文?のような構文はrange式を 使用して書いていきます。

arr := [...]int{0, 1, 2, 3, 4}
for i := range arr {
  fmt.Println(i)
}

条件分岐

何回か書いていますが、Go言語では条件文に丸括弧がいりません。 そのためif文はこのような形になっています。

if i % 2 == 0 {
  fmt.Println(i, "は偶数")
} else {
  fmt.Println(i, "は奇数")
}

switch文は

switch i {
  case 0:
    fmt.Println("0")
  case 1, 2:
    fmt.Println("1 or 2")
  default:
    fmt.Println("other")
}

 

のように書けますが、多言語の場合case式の終わりにbreak文をつけないと他のケースでも 条件が一致していれば処理が継続されてしまいますがGo言語の場合はデフォルトでbreakされる ようになっており、breakしたくない場合はfallthrough文をつける必要があるようです。

(自分で書いていてbreak書かなかったことがあまりないので記述量が減って嬉しそう)

ポインタ

Go言語ではポイントが使用できます。変数が存在するメモリ上の場所をアドレスといい、 そのアドレスを格納できるのがポインタです。

単純な変数のアドレスを取得したい場合には,「&」を使い

fmt.Println("iのアドレス", &i)

のようにします。ポインタを格納したい変数には、*をつけて型宣言を行います。

var ptr *int

使い方はほぼ、C言語と同じはず(あまり自信がない)なのですが、 唯一ポインタの演算だけ不正なメモリアクセスのリスクを回避するために許可されていないようです。

関数

関数について

Go言語は複数の値を関数で返せるので、複数の値を返す場合には 一旦オブジェクトに詰め込んでオブジェクト自体を返すなどの必要があるのですが Go言語ではそれがなくなるようです。

まず単純な関数の宣言を見てみてると

/* func 関数名(パラメータリスト)戻り値の型 {
 *  return 戻り値
 * }
 */
func plus (a int, b int) int {
 return a + b
}

このような形で、多値を返す場合はカンマで区切って値を 返却します。

func calc(a int, b int) (int, int, int, float32) {
  return a + b, a - b, a * b, float32(a) / float32(b)
}

こうやって見ると、受け取り側の受け取り方が気になりますが 受け取り側も同じようにカンマ区切りで受け取りことができます。

add, sub, mul, div := calc(1, 2)

戻り値には名前をつけることもでき、

func calc(a int, b int) (add int, sub int, mul int, div float32) {
  // あらかじめ宣言した変数に値を代入
  add = a + b
  sub = a - b
  mul = a * b
  div = float32(a) / float32(b)

  // return文を呼び出せば多値がかえる
  return
}

このようにすることでreturn文で返す値を書きつらねなくても多値を返すことができます。

メソッドについて

Go言語では型にメソッドをもたせることができるらしく、 funcキーワードとメソッド名のまdに「レシーバ」を記述することでメソッドが どの型に所属するのか決めることができます。

※レシーバとして使用する型はメソッドと同じパッケージ内で宣言されている必要があります。

type myType init

func (value myType) println() {
  fmt.Println(value)
}

func main() {
  var z myType = 1234

  z.println()
}

さらにメソッド値なる概念があるらしく、

type myType int

func (value *myType) add(increment myType) myType {
  *value += increment
  return *value
}

func main() {
  var i myType

  // 変数i に対し、addメソッドを呼び出し、3を加算
  // => 3が出力される。
  fmt.printLn(i.add(3))

  // メソッド値を取得。このメソッド値の型は「func(myType) myType」
  methodValue := i.add

  // メソッド値に対し、メソッドを呼び出し、再度3を加算
  // => 4が出力される。
  fmt.println(methodValue(3))
}

メソッドを自体を変数に代入して、レシーバなしで変数からメソッドを呼び出せるようです。 JavaScriptでも変数にfunctionを詰め込めるのでそれと同じと理解してます。

関数の遅延実行

defer文を使うと関数の遅延実行が可能で、defer文を使うと関数の戻り値を使うことはできませんが、 呼び出し元の関数が終了したときに呼び出されます。

これは、リソースの解放などに便利で ファイルのオープンを例にとると以下のようなコードになります。

package main

import "os"

func main() {
  file.err := os.OpenFile("test.txt", os.O_WRONLYlos.0_CREATE, 0666)

  // オープンに失敗したときは終了
  if err != nil {
    os.Exit(1)
  }

  defer file.Close()

  file.WriteString("あいうえお")
}

例では、defer文を使ってファイルのクローズが遅延実行されるようにしてあります。 defer文がひとつの関数に複数ある場合は、呼び出し元が終了したあと宣言された順番に 実行されるようです。

まとめ

長くなったので一旦ここで区切りとします。 次回は構造体、インタフェース、配列・スライス・マップ、エラーハンドリング、並列処理 などなどです。

この前半パートではやっぱり関数の戻り値として多値を返せるというのが印象的でした。 記事にも書いたように他のプログラム言語だとわざわざオブジェクトにつけたりしていたので、 それがなくなりすっきりとした関数が書けそうです。(まだ書いていないけど)

他はiota文が地味に便利そうなのと、記事で触れるのわすれたのですがswitch文は普通の 値のswitchの他に型switchなるものがあるそうなのでこっちもちょっと使ってみてみたいです。