Help us understand the problem. What is going on with this article?

Go言語はじめてのハンズオン実施に向けた、Go言語勉強軌跡 前半

More than 1 year has passed since last update.

Go言語構文説明

流れでやるならGo速習会の内容がとても良さそう。ハンズオンでの内容作成に向けて、講義する側がGo言語の勉強をするためにまとめた内容です。

ここではGoハンズオンにあたり、Goの構文説明をします。構文について事前に知識を得ることで、Goプログラミングを始める際の、わからないことに対するハードルを解消します。

やること

  • import
  • 言語付属のツール フォーマッタ gofmt
  • 変数、定数、関数の定義
  • 型、構造体
  • 配列、スライス
  • if, for, switch
  • パッケージ
    • go dep
  • テスト

import

ここでは標準パッケージのimportの範囲で、importの書き方と、できることを説明する。

書き方

import "パス(絶対パス or 相対パス)"
import "パス(絶対パス or 相対パス)"

相対パスは推奨されませんので、基本的には絶対パスです。

下記のようにまとめることができます。

import (
    "パス(絶対パス or 相対パス)"
    "パス(絶対パス or 相対パス)"
)

機能

パッケージ名を省略して使えるようにする

関数呼び出すときに、わざわざimport先のパッケージを書く必要がなくなる。

import (
    . "パス(絶対パス or 相対パス)"
)

例えば、

import (
    . "fmt"
)

と書けば、本来fmt.Println("~")とするところをPrintln("~")と書くことができる。

パッケージ名を再命名して使えるようにする

import (
    エイリアス名 "パス(絶対パス or 相対パス)"
)

例えば、

import (
    f "fmt"
)

と書けば、本来fmt.Println("~")とするところをf.Println("~")と書くことができる。

言語付属のツール フォーマッタ gofmt

メモ:http://is-tech-labo.com/blog/golang%E3%81%AE%E3%81%A8%E3%81%A3%E3%81%A6%E3%82%82%E4%BE%BF%E5%88%A9%E3%81%AA%E9%96%8B%E7%99%BA%E6%94%AF%E6%8F%B4%E7%94%A8%E3%83%84%E3%83%BC%E3%83%AB/

メモ:https://blog.amedama.jp/entry/2016/03/31/222050

Goの標準コマンドで、Goのソースコードを整形してくれる。

これが、

package main

import ()

func main(){
s := 0
s += 1
if(s == 1){
s += 1
}
}

こうなる。

package main

import ()

func main() {
    s := 0
    s += 1
    if s == 1 {
        s += 1
    }
}

特定のファイルをフォーマット

コマンドライン引数に対象ファイルを指定する。

go fmt 対象Goファイル

go fmt test.go

カレントディレクトリ全てのファイルをフォーマット

コマンドライン引数をつけない。

go fmt

カレントディレクトリのサブディレクトリも含めて、全てのファイルをフォーマット

コマンドライン引数に./..をつける。

go fmt ./...

変数、定数、関数の定義

変数

var 変数名 型 = 初期値

Go言語は型推論あるので、型を省略できる。

var 変数名 = 初期値

varも省略できる。Makefileとかで見るような := を使って省略する。宣言と代入を同時にする。

変数名 := 初期値

なお、varをわざわざ書くことは、あえて先に空の変数を宣言しておきたい時くらいで、ほとんどは := を使うらしい。
参照されず使用されない変数はコンパイルエラーとなる。不要な変数作るのは許されない。

再代入

再代入は、上記いずれの方法で宣言しても変わらない。ただ、 := は宣言を含むので、重複した変数名では使用できない。

変数名 = 値

定数

constを使う。もちろん、constで宣言した変数に対して、再代入することは、コンパイルエラーになるので、できない。

const 変数名 型 = 初期値

型は省略できる。

const 変数名 = 初期値

変数宣言で使った、:=は使えない。初期代入で型が決まるようなものは、定数に対して使用できないということらしい

関数

メモ:https://blog.y-yuki.net/entry/2017/05/03/000000

Go言語は関数の戻り値を複数返すことができる。戻り値1つの場合と、複数の場合を説明する。

戻り値一つ

func 関数名(引数名 引数の型, ...) 戻り値の型 {
    処理
    ...
}

呼び出し側。

変数名 := 関数名(引数, ...)

package main

import (
    "fmt"
)

func main() {
    plusNum := plus(2, 3)

    // 2 + 3 で 5
    fmt.Println(plusNum)
}

func plus(a int, b int) int {
    return a + b
}

戻り値複数

カンマ区切りで複数並べる。

func 関数名(引数名1 引数の型1, ...) (戻り値の型1, 戻り値の型2, ..., 戻り値の型n) {
    処理
    return 戻り値1, 戻り値2, ..., 戻り値n
}

呼び出し側。

変数名1, 変数名2, ..., 変数名n := 関数名(引数1, 引数2, ..., 引数n)

package main

import (
    "fmt"
)

func main() {
    plusNum, multiNum := plusMulti(2, 3)
    // 2 + 3 で 5
    fmt.Println(plusNum)
    // 2 * 3 で 6
    fmt.Println(multiNum)
}

func plusMulti(a int, b int) (int, int) {
    plus := a + b
    multi := a * b
    return plus, multi
}

型、構造体

メモ:https://qiita.com/atsaki/items/3554f5a0609c59a3e10d

C言語のtypedefのような形で、type句を使って方を宣言する。

Go言語の仕様で事前に宣言されている型

unsignedがあったり、バイト数指定があったり。

  • bool
  • byte
  • complex64
  • complex128
  • error
  • float32
  • float64
  • int
  • int8
  • int16
  • int32
  • int64
  • rune
  • string
  • uint
  • uint8
  • uint16
  • uint32
  • uint64
  • uintptr

型を宣言する

type 型の名前 継承する元の型

例えば

type MyInt int

構造体

メモ:https://qiita.com/tenntenn/items/45c568d43e950292bc31

メモ:https://qiita.com/cotrpepe/items/b8e7f70f27813a846431

構造体の宣言

struct {
    フィールド名1 型名1
    フィールド名2 型名2
    ...
}

構造体を使う

変数に代入して使うには。

var 構造体名 struct {
    フィールド名1 型名1
    フィールド名2 型名2
    ...
}

作成した構造体の変数への、フィールドへの代入。

変数名.フィールド名 = 値

package main

import (
    "fmt"
    "strconv"
)

func main() {

    var person struct {
        Name string
        Age  int
    }

    person.Age = 20
    person.Name = "tester"

    fmt.Println("age=" + strconv.Itoa(person.Age) + ", name=" + person.Name)
}

実行結果。

age=20, name=tester

構造体のフィールドは大文字始まりのキャメルケースが参考で見てて多かった。それがスタンダード?

構造体と型の組み合わせ

typeとstructを組み合わせる。型宣言と構造体の宣言を同時にする。

type 構造体名 struct {
    Name string
    Age  int
}

上述のpersonをtype structに書き換えた。

package main

import (
    "fmt"
    "strconv"
)

// Person 人間の要素を扱います(このような、構造体に対するコメントをつけないとコンパイラに警告される)
type Person struct {
    Name string
    Age  int
}

func main() {

    person := Person{}
    person.Age = 20
    person.Name = "tester"

    fmt.Println("age=" + strconv.Itoa(person.Age) + ", name=" + person.Name)
}

なお初期化方法は下記でも可能。

    person := Person{
        Name: "tester",
        Age:  20,
    }

配列、スライス

配列は他の言語の配列と同等で、ある型の連続した値を持つだけのもの。スライスは配列を内部で保持し、それを外部から操作しやすくしたもの。

配列は指定した要素数分、かならず箱が用意される(intならintの初期値、構造体等であれば)

配列

空の配列の宣言方法。

var 変数名 [要素数]型名

値を初期化しつつ宣言。[]の中身の要素数を数値で指定した場合、実際の値の羅列の数が宣言した要素数より多いと、コンパイルエラーとなる。

変数名 := [要素数 or ...]型名{値1, 値2, ..., 値n}

上述での構造体Personを利用して、ベタだけど下記のような形。

package main

import (
    "fmt"
    "strconv"
)

// Person 人間の要素を扱います(このような、構造体に対するコメントをつけないとコンパイラに警告される)
type Person struct {
    Name string
    Age  int
}

func main() {

    person1 := Person{
        Name: "tester1",
        Age:  20,
    }

    person2 := Person{
        Name: "tester2",
        Age:  30,
    }

    person3 := Person{
        Name: "tester3",
        Age:  40,
    }

    var persons [3]Person

    persons[0] = person1
    persons[1] = person2
    persons[2] = person3

    fmt.Println("age=" + strconv.Itoa(persons[0].Age) + ", name=" + persons[0].Name)
    fmt.Println("age=" + strconv.Itoa(persons[1].Age) + ", name=" + persons[1].Name)
    fmt.Println("age=" + strconv.Itoa(persons[2].Age) + ", name=" + persons[2].Name)
}

実行結果。

age=20, name=tester1
age=30, name=tester2
age=40, name=tester3

配列の宣言を代入と同時にした場合、下記。

persons := [3]Person{person1, person2, person3}
persons := [...]Person{person1, person2, person3}

スライス

空のスライスの宣言方法。配列の[]の中に要素数を指定しない。

var 変数名 []型名

値を初期化しつつ宣言。

変数名 := []型名{値1, 値2, ..., 値n}

要素の追加

append(スライス, 値) 戻り値スライスメソッドで要素追加する。使い方としては、引数で指定したスライスに値を追加したスライスが、返却されるという動きらしい。

要素が追加されたスライス = append(要素を追加するスライス, 要素に追加する値)

package main

import (
    "fmt"
    "strconv"
)

// Person 人間の要素を扱います(このような、構造体に対するコメントをつけないとコンパイラに警告される)
type Person struct {
    Name string
    Age  int
}

func main() {

    person1 := Person{
        Name: "tester1",
        Age:  20,
    }

    person2 := Person{
        Name: "tester2",
        Age:  30,
    }

    person3 := Person{
        Name: "tester3",
        Age:  40,
    }

    persons := []Person{person1, person2}
    persons = append(persons, person3)

    fmt.Println("age=" + strconv.Itoa(persons[0].Age) + ", name=" + persons[0].Name)
    fmt.Println("age=" + strconv.Itoa(persons[1].Age) + ", name=" + persons[1].Name)
    fmt.Println("age=" + strconv.Itoa(persons[2].Age) + ", name=" + persons[2].Name)
}

要素の分割

元が配列でもスライスでも、下記の書き方で要素を分割したスライスを取得することができる。開始要素数と終了要素数の間の要素を持つスライスが返却される。

変数名 = 配列orスライス[開始要素数:終了要素数]

package main

import (
    "fmt"
    "strconv"
)

// Person 人間の要素を扱います(このような、構造体に対するコメントをつけないとコンパイラに警告される)
type Person struct {
    Name string
    Age  int
}

func main() {

    person1 := Person{
        Name: "tester1",
        Age:  20,
    }

    person2 := Person{
        Name: "tester2",
        Age:  30,
    }

    person3 := Person{
        Name: "tester3",
        Age:  40,
    }

    person4 := Person{
        Name: "tester4",
        Age:  50,
    }

    personSlice := []Person{person1, person2, person3, person4}
    sliced1 := personSlice[1:3]

    fmt.Println("age=" + strconv.Itoa(sliced1[0].Age) + ", name=" + sliced1[0].Name)
    fmt.Println("age=" + strconv.Itoa(sliced1[1].Age) + ", name=" + sliced1[1].Name)

    fmt.Println("---")

    personArray := [...]Person{person1, person2, person3, person4}
    sliced2 := personArray[2:4]

    fmt.Println("age=" + strconv.Itoa(sliced2[0].Age) + ", name=" + sliced2[0].Name)
    fmt.Println("age=" + strconv.Itoa(sliced2[1].Age) + ", name=" + sliced2[1].Name)

}

実行結果。

age=30, name=tester2
age=40, name=tester3
---
age=40, name=tester3
age=50, name=tester4

if, for, switch

メモ:https://qiita.com/megu_ma/items/ea9edd87d9c29f0d5e0f

メモ:https://qiita.com/high5/items/3fe34d2feeff2c11f5ca

if

boolを受け取る評価式の部分の前に、変数宣言(初期化にといてメソッドも呼び出せる)をすることで、そのif, else ifのブロック内のみがスコープとなる変数を作れる。else ifしたときに、もしその変数自体の値が〜なら、という処理を条件判定の前に含められる上、ブロック抜けたら参照がなくなるので、便利。

if (変数宣言;) 評価式 {
    処理
    ...
}else if (変数宣言;) 評価式 {
    処理
    ...
}else{
    処理
    ...
}

評価式は括弧をつけても良い。||&&などで複数の評価式を書くならば、括弧をつけたほうが良い。

package main

import (
    "fmt"
)

func main() {
    flag := true

    if flag {
        fmt.Println("hello, if")
    } else {
        fmt.Println("hello, if(sorry not process)")
    }

    print(0)
    print(1)
}

func print(n int) {
    if i := 0; n == 0 {
        i = 0
        fmt.Printf("i=%d\n", i)
    } else {
        i = 1
        fmt.Printf("i=%d\n", i)
    }
}

実行結果。

hello, if
i=0
else ...

for

Go言語にwhileはないが、forの構文の仕様が広いので、いろいろできる。ifと同じで評価式の部分に、括弧は必要ない。

while的な使い方

例えば評価式に、int値 > 数値のような形で、for内でループが終了する条件を満たしたりする。

for 評価式 { 
    処理
    ...
}

評価式をなくすと無限ループ。

for { 
    処理
    ...
}

よく使うオーソドックスなfor

変数宣言 → ループ指定 → インクリメント、のような定義をforのブロック直前に書くやつ。

sum := 0
for i := 0; i < 10; i++ {
    sum += i
} 

foreachのような書き方

range句をforに追加し、対象の配列やスライスの要素分ループする。

配列に対する繰り返し

配列、スライスの場合。rangeの返却で、第一返却値に要素が何番目かの値と、第二返却値に要素の値、が帰ってくるので、indexとnumber変数(変数名は任意)で受け取っている。

numberSlice := []int{0, 10, 20, 30}

for index, number := range numberSlice {
    fmt.Println("index=", index, " number=", number)
}

要素数が必要なければ、_で参照できないようにすると捨てることができる。

for _, number := range numberSlice {
    fmt.Println("number=", number)
    // これはコンパイルエラー
    // fmt.Println("index=", _, " number=", number)
}

Mapに対する繰り返し

メモ:https://ashitani.jp/golangtips/tips_map.html#map_Delete

(ここまでの説明に載ってないけど)Mapの場合。

numberMap := map[int]int{
    0:  0,
    10: 100,
    20: 200,
    30: 300,
}

for key, value := range numberMap {
    fmt.Println("key=", key, " value=", value)
}

使用しない返却を_で捨てられるのはMapも同様。

for _, value := range numberMap {
    fmt.Println("value=", value)
}

switch

メモ:https://github.com/golang/go/wiki/Switch

値比較

switch 比較対象の値 {
case 比較基準の値1:
    処理
case 比較基準の値2:
    処理
...
case 比較基準の値n:
    処理
default:
    処理
}

intの比較なら下記。

n := 0

switch n {
case 0:
    fmt.Println("n is zero")
case 1:
    fmt.Println("n is one")
case 2:
    fmt.Println("n is two")
default:
    fmt.Println("n is other")
}

実行結果。

n is zero

複数比較

n := 1

switch 比較対象の値 {
case 比較基準の値1, 比較基準の値2, ..., 比較基準の値n:
処理
case 比較基準の値3:
処理
default:
処理
}
```

n := 1

switch n {
case 0, 1:
    fmt.Println("n is zero or one")
case 2:
    fmt.Println("n is two")
default:
    fmt.Println("n is other")
}

実行結果。

n is zero or one

評価式

比較対象の値だけでなく、評価式を書くことができる。

switch {
case 評価式1:
    処理
case 評価式2:
    処理
...
case 評価式n:
    処理
default:
    処理
}

n := 3

switch {
case n == 0:
    fmt.Println("n is zero")
case n > 0 && n <= 2:
    fmt.Println("n is one to two")
case n >= 3:
    fmt.Println("n is over three")
default:
    fmt.Println("n is other")
}

fallthrough

他の言語では隣接する次のcaseに処理が流れたりするため、そうしたくない場合はbreakを書く必要があります。ただ、あえてbreakを書かずに次のcaseの処理を流用するのは、処理を再利用できる効率性の反面、読みにくく、想定する処理の流れを把握しにくいデメリットがあります。

Go言語のswtichはどれかのcaseに入っても続く次のcaseには入りません。そのためあえて次のcaseに移りたい場合は、fallthroughを使用します。

switch 比較対象の値 {
case 比較基準の値1:
    処理
    fallthrough
case 比較基準の値2:
    処理
    fallthrough
...
case 比較基準の値n:
    処理
    fallthrough
default:
    処理
}

n := 0

switch n {
case 0:
    fmt.Println("n is zero")
    fallthrough
case 1:
    fmt.Println("n is one")
    fallthrough
case 2:
    fmt.Println("n is two")
case 3:
    fmt.Println("n is two")
default:
    fmt.Println("n is other")
}

実行結果。

n is zero
n is one
n is two

以下、次回記事にて

パッケージ

import管理 goimports

go dep

テスト

その他役立ちそうなこと

Go言語はまりどころ:
http://www.yunabe.jp/docs/golang_pitfall.html#defer-%E3%81%AE%E4%B8%AD%E3%81%A7%E7%99%BA%E7%94%9F%E3%81%97%E3%81%9F-error-%E3%82%92%E5%87%A6%E7%90%86%E3%81%97%E5%BF%98%E3%82%8C%E3%82%8B

mediado
私たちメディアドゥは、電子書籍を読者に届けるために「テクノロジー」で「出版社」と「電子書店」を繋ぎ、その先にいる作家と読者を繋げる「電子書籍取次」事業を展開しております。業界最多のコンテンツラインナップとともに最新のテクノロジーを駆使した各種ソリューションを出版社や電子書店に提供し、グローバル且つマルチコンテンツ配信プラットフォームを目指しています。
https://mediado.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away