はじめに

前回の記事の続きです。
前回の記事はこちら

フローと関数

フロー制御

if

1
2
3
4
5
if condition {
    // statement
} else {
    // statement
}

他の言語とかと違って、 if (condition) ではないのですね。

Goのifはすごいことに、条件分岐の中で変数を宣言できます。
この変数のスコープはこの条件ロジックブロック内のみ存在し、他の場所では作用しません。

これも他の言語では見たことないかも。
ifスコープ内でしか使わない一時変数は、ここで宣言する感じでしょうか。

goto

あの goto
C言語だと コーディング規約(たしかANSIの方) で使用できないやつ

引用

1
2
3
4
5
6
7
func myFunc() {
    i := 0
Here:   //この行の最初の単語はコロンを最後に持ってくることでタグとなります。
    println(i)
    i++
    goto Here   //Hereにジャンプします
}

goto 文が生涯で 2回くらいしか試したことないので、久しぶりに見ます。
この例のコードであれば for で事足りそう。
goto が複雑にならずに有用に使える場面が想像できないので、調べてみます。

for

ループ制御してくれるやつ

1
2
3
for expression1; expression2; expression3 {
    //...
}

例えば配列の合計を求める場合

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package main
import "fmt"
func main () {
    // 合計を求める
    numbers := []int{1,2,3,4,5,6,7,8,9,10}
    sum := 0
    for i:=0;i<len(numbers);i++ {
        sum += numbers[i]
    }
    fmt.Println(sum)
}

while がないかわりに ; を省略して下記のようにかけます。

1
2
3
4
5
i := 0
for sum > 1000 {
    sum += i
    i++
}

break, continue もあるようです。

また、PHP みたいにいわゆる foreach 的なことも range キーワードを使用してできるようです。

1
2
3
4
5
m := map[string]int{"lemon":1, "melon":2, "apple":3}
// k:key v:value ですね
for k,v := range m {
    fmt.Println(k, v)
}

switch

他の言語と同じswitch-case です

1
2
3
4
5
6
7
8
9
i := 1
switch i {
    case 1:
        fmt.Println("1")
    case 2:
        fmt.Println("2")
    default:
        fmt.Println("default")
} 

のように書きます。 break がないのが特徴ですね。

また同時に、Goのswitchはデフォルトでcaseの最後にbreakがあることになっているので、
マッチに成功した後は他のcaseが実行されることはなく、switch全体から抜け出します。
ただし、fallthroughを使用することであとに続くcaseコードを強制的に実行させることができます。

fallthrough って最初の方のキーワード一覧にありましたがここで使用できるのですね。

break しないで下の case 文を実行できるらしい.
他の言語でも 1~3までは同じ処理させたいというときは

1
2
3
4
5
case 1:
case 2:
case 3:
    // 処理
    break;

のようなコードを書いてたので。

ただ switchは if-else ケースを置き換えられるので

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
switch {
    case i > 10:
        // 処理
    case i > 20:
        // 処理
    case i > 30:
        // 処理
    default:
        break
}

上記のように 制御式が caseで使えます。 なので、 case i > 1 && i < 3 : でも上のコードと同じですね。 fallthrough とどっちを使ったほうがいいのかはケースバイケースでしょうか。

関数

関数はGoの中心的な設計です。キーワードfuncによって宣言します。
形式は以下の通り:
func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
//ここはロジック処理のコードです。
//複数の値を戻り値とします。
return value1, value2
}

これも、形式は他の言語と似ていますね。
型と引数名を定義しています。戻り値が 2値以上返せるというのが特徴的です。
他の言語ですと、2値以上返せないので クラスのオブジェクト返したり、構造体を返したりとかをしていたと思います。 あとは配列とか.
Golang ですと、そのままの形で返せるようです。

例があったので、 paiza.io を使用して試してみました。
paiza-io-golang-01

複数の戻り値

return A, Bのように、変数1と変数2をカンマで区切って 返すことで2値を返せるようです。

複数の戻り値を使用する際に簡単な関数を作ってみました。
値を交換する関数です。

1
2
3
func swap(a, b int) (int, int) {
    return b, a
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package main
import "fmt"
func main(){
    a, b := 1, 2
    fmt.Println(a,b)
    fmt.Println(swap(a,b))
}
func swap(a, b int) (int, int) {
    return b, a
}
paiza-io-golang-02

どうやら、戻り値に設定する変数の名前を決めることができ、それが宣言されていたらそれを返してくれるらしいです。
引用

1
2
3
4
5
func SumAndProduct(A, B int) (add int, Multiplied int) {
    add = A+B
    Multiplied = A*B
    return
}

PHPcompact 関数に似てますね。

可変長引数

... を使うと、可変長引数を定義できます。

1
func myfunc(arg ...int) {}

... については配列を展開することもできるので結構便利です。 myfunc(numbers...) のところがみそです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package main
import "fmt"
func main(){
    numbers := []int{1,2,3,4,5,6,7,8,9,10}
    myfunc(numbers...)
}
func myfunc(arg ...int) {
    for _ , v := range arg {
        fmt.Println(v)
    }
}

値渡しと参照渡し

C言語とかでも話題に上がる、関数呼び出し時の引数の渡し方についてですね。
値のコピーを渡すのか、値の参照(メモリのアドレス)を渡すのか違いです。

defer

これは見たことない言語機構です。

Go言語のすばらしいデザインの中に、遅延(defer)文法があります。
関数の中でdefer文を複数追加することができます。
関数が最後まで実行された時、このdefer文が逆順に実行されます。
最後にこの関数が返ります。

便利そうですね。
引用元にも記載の通り、 ファイルのクローズし忘れとかをしないようにすることができそうです。

値、型としての関数

関数ポインタに近いですね。
これをすることで、関数を引数として渡せるようになります。

filter 関数が[]inttestInt(引数に int を受け取って bool を返すような関数)を引数にとっています。
ここでfilter の中身は、[]int にその関数を適用するようなロジックになっています。
なので、[]int に適用する汎用的な部分を共通化できるようです。 (testInt の中身の関数は個別の動作をする)

PanicとRecover

GoにはJavaのような例外処理はありません。例外を投げないのです。
その代わり、panicとrecoverを使用します。
ぜひ覚えておいてください、これは最後の手段として使うことを。
つまり、あなたのコードにあってはなりません。もしくはpanicを極力減らしてください。

なるほど・・・!?

paiza.io で実験してみました.
どうやら下記コードは正常に終了するようです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main
func main() {
    throwsPanic(fp)
}

func throwsPanic(f func()) (b bool) {
    defer func() {
        if x := recover(); x != nil {
            b = true
        }
    }()
    f() //関数fを実行します。もしfの中でpanicが出現したら、復元を行うことができます。
    return
}

func fp() {
    panic("panic!!")
}

main関数とinit関数

Goでは2つの関数が予約されています
init関数(すべてのpackageで使用できます)とmain関数(package mainでしか使用できません)です。
この2つの関数は定義される際いかなる引数と戻り値も持ちません。
packageのなかで複数のinit関数を書いたとしても、もちろん可読性か後々のメンテナンス性に対してですが、
packageの中では各ファイルに一つだけのinit関数を書くよう強くおすすめします。

main 関数は全部で1つ init 関数は package で1つで覚えていればOKですね。(たぶん)
init 関数は初期化コードを色々書くような感じでしょうか。

おわりに

Go言語の基礎の部分で結構時間かかっていますが、 次回は struct型 の章をやっていきます。
無理のないペースで備忘録を記載していきます。