今回は、条件とループについて学んでいきたいと思います。
<目次>
Goの例外処理
Go言語には、try-catch構文がありません。Go言語はシンプルさを追求した言語のため、実装していないようです。
では、エラーをキャッチしたい場合はどうすればいいのでしょうか?
Go言語の関数は、戻り値を複数返すことができる特徴を持っています。この特徴を利用して、エラー発生時にErrorオブジェクトを返すように実装している関数があります。
例えば、Go言語の組み込み関数ある「ReadString」メソッドを見て見ましょう。
1 2 3 4 |
func (b *Reader) ReadString(delim byte) (string, error) { bytes, err := b.ReadBytes(delim) return string(bytes), err } |
ご覧の通り、戻り値に”string”と”error”が設定されています。
つまり、この関数を利用するには、戻り値とエラーの両方を受け取る変数が必要です。
例を挙げてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package main import ( "bufio" "fmt" "os" ) func main() { fmt.Print("Enter a grade: ") reader := bufio.NewReader(os.Stdin) input, err := reader.ReadString('\n') } |
errと変数宣言している箇所が、errorオブジェクトを受け取る変数です。err変数の例外処理の記述方法は、Go言語統一で以下のように書きます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package main import ( "bufio" "fmt" "log" "os" ) func main() { fmt.Print("Enter a grade: ") reader := bufio.NewReader(os.Stdin) input, err := reader.ReadString('\n') if err != nil { log.Fatal(err) } fmt.Println(input) } |
エラーの場合は、errorオブジェクトを返します。
エラー以外の場合は、nilを返却することになっていますので、err変数の値がnilかどうかをチェックします。
エラー処理をしたくない場合は、「_(アンダースコア)」で受け取ることもできます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package main import ( "bufio" "fmt" "os" ) func main() { fmt.Print("Enter a grade: ") reader := bufio.NewReader(os.Stdin) input, _ := reader.ReadString('\n') // Receive err by _ fmt.Println(input) } |
Go言語では、宣言した変数がソースコードの中で一度も使用されていないとコンパイルエラーになります。しかし、「_」については例外で、コンパイルエラーを回避することができます。
ただし、エラーを無視するメリットは「ない」と思いますので、nilの判定チェックにて、エラーハンドリングした方が無難だと思います。
また、エラー処理にlogパッケージの「Fatal」関数を利用しています。この関数は、「メッセージを出力する」機能と「プログラムの実行を止める」機能を提供します。
条件判定
Go言語は、「if文」にて条件判定を行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package main import ( "bufio" "fmt" "log" "os" "strconv" "strings" ) func main() { fmt.Print("Enter a grade: ") reader := bufio.NewReader(os.Stdin) input, err := reader.ReadString('\n') if err != nil { log.Fatal(err) } // change stirng type to int type without new line v, _ := strconv.Atoi(strings.TrimRight(input, "\r\n")) if v <= 0 { fmt.Println("Oh My God!") } else if v >= 1 && v <= 30 { fmt.Println("You got a F") } else { fmt.Println("Excellent!") } } |
or条件の場合は、「||」, and条件の場合は「&&」キーワードを指定できます。この辺りは他の言語と同じですね。
やってみよう1
プログラミングを学ぶ上で、アウトプットが大切です。学んだ知識をここで確かめてみましょう。
<問題>
ファイルを読み込んで、ファイルサイズを出力するプログラムを作成してください。
<制限事項>
・ファイル情報の取得は、osパッケージのStat関数を使うこと
・err 処理を入れること
こちらが、解答例です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package main import ( "fmt" "log" "os" ) func main() { fileInfo, err := os.Stat("file.txt") if err != nil { log.Fatal(err) } fmt.Println(fileInfo.Size()) } |
1 2 3 4 5 6 7 8 9 10 |
# 実行結果 # ファイルあり $ go run main.go 13 # ファイルなし $ go run main.go 2019/08/21 21:13:43 stat file.text: no such file or directory exit status 1 |
型変換(Strings->Numbers)
前回、float64とint型のタイプの型変換を学びました。その復習になりますが、以下の通りに実装すれば、型変換が行えましたね。
1 2 3 4 5 |
var i int = 30 var f float64 = 20.3 fmt.Println(i*int(f)) fmt.Println(float64(i)*f) |
1 2 3 4 |
# 実行結果 $ go run main.go 600 609 |
しかし、stirngsをnumbers型に変換するのは少し大変です。
単純にint型の値をstringで囲ってみましょう。
1 2 3 4 5 6 7 8 9 10 11 |
package main import ( "fmt" ) func main() { var i int = 30 fmt.Println(string(i)) fmt.Println("-----------") } |
何が出力されるでしょうか?答えは「空白」が出力されます。
1 2 3 |
$ go run main.go ----------- |
int -> string の場合はどうなるでしょうか。
1 2 3 4 5 6 7 8 9 10 11 |
package main import ( "fmt" ) func main() { var s string = "30" fmt.Println(int(s)) fmt.Println("-----------") } |
1 2 3 |
$ go run main.go # command-line-arguments ./main.go:9:17: cannot convert s (type string) to type int |
今度は、コンパイルエラーが発生しましたね。Strings をNumbers型に型変換するには、strconvパッケージを利用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package main import ( "fmt" "strconv" ) func main() { var s string = "30" // 10進数, 64bit fmt.Println(strconv.ParseInt(s, 10, 64)) fmt.Println("-----------") } |
1 2 3 |
$ go run main.go 30 <nil> ----------- |
やってみよう2
以下のコードをstrconvパッケージ関数を使って書き換えて下さい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package main import ( "bufio" "fmt" "log" "os" "strconv" "strings" ) func main() { fmt.Print("Enter a grade: ") reader := bufio.NewReader(os.Stdin) input, err := reader.ReadString('\n') if err != nil { log.Fatal(err) } // change stirng type to int type without new line v, _ := strconv.Atoi(strings.TrimRight(input, "\r\n")) if v <= 0 { fmt.Println("Oh My God!") } else if v >= 1 && v <= 30 { fmt.Println("You got a F") } else { fmt.Println("Excellent!") } } |
解答例です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
package main import ( "bufio" "fmt" "log" "os" "strconv" "strings" ) func main() { fmt.Print("Enter a grade: ") reader := bufio.NewReader(os.Stdin) input, err := reader.ReadString('\n') if err != nil { log.Fatal(err) } // newline character を取り除く input = strings.TrimSpace(input) v, err := strconv.ParseFloat(input, 64) if v <= 0 { fmt.Println("Oh My God!") } else if v >= 1 && v <= 30 { fmt.Println("You got a F") } else { fmt.Println("Excellent!") } } |
parseFloatを使った方が、コードが綺麗に見えますね。
変数のスコープ
変数のスコープ(有効範囲 )について、少し触れておきましょう。
以下のソースコードを見てください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package main import "fmt" func main() { point := 10 if point >= 60 { status := "pass" } else { status := "fail" } fmt.Println("You got ", point, "points! You are", status) } |
このソースコードを実行するとどうなるでしょうか?
答えは、「コンパイルエラー」になります。
1 2 |
# command-line-arguments ./main.go:13:52: undefined: status |
Println関数でstatusの出力を試みましたが、エラーになってしまいました。何が起こったのでしょうか?
Go言語では、ブロックごとに変数の有効範囲が決められています。ブロックとは、中括弧({})で囲まれた部分を指します。
上記の例では、if-else内でそれぞれstatusを初期化しているので、各々のブロック内でしか有効になりません。
ブロックは、ネストすることも可能で、内から外に向けて有効範囲が広がっていきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ File block ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ package main ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ Package block ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ func main() { // something else code ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ Function block ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ if XXXX { ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ if block ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ // something else code more } } |
やってみよう3
先ほどのコードの修正をしてみましょう。解答例を書いておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package main import "fmt" func main() { point := 10 var status string if point >= 60 { status = "pass" } else { status = "fail" } fmt.Println("You got ", point, "points! You are", status) } |
1 2 |
$ go run main.go You got 10 points! You are fail |
Short Variable Declarationの宣言
Short Variable Declarationを覚えているでしょうか?「:=」を使った変数宣言の方法の一つでしたよね。
1 2 |
// 10で初期化 v := 10 |
この方法で宣言された変数が同じスコープ内に存在する場合は、二度目の初期化はできません。
1 2 3 4 5 6 |
v := 10 // ok v = 10 // ng v := 10 |
では、問題ですが、以下の場合はどうなると思いますか? err 変数に対して、short variale declarationを使いました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
package main import ( "bufio" "fmt" "log" "os" "strconv" ) func main() { fmt.Print("Enter:") reader := bufio.NewReader(os.Stdin) input, err := reader.ReadString('\n') // err 宣言 if err != nil { log.Fatal(err) } grade, err := strconv.ParseFloat(input, 64) // err 宣言 if err != nil { log.Fatal(err) } fmt.Println(grade) } |
実は、これについてはコンパイルエラーになりません。
一つでも新しい変数名が一緒に宣言された場合は、short variable declarationを使うことができるのです。
Go関数は複数の変数を返却できる言語ですので、一つ一つの変数に対して新しく名前をつける(再利用できない)方法は、コーディングする上で大変になるからですね。
また、もう一つ「関数外からの宣言ができない」という特徴があります。
1 2 3 4 5 6 |
x := 1 => ng func main() { x := 1 => ok fmt.Println(x) } |
これが、varキーワードを使った変数宣言との最大の違いになります。
ループ
プログラミングの定番であるループ処理について学びましょう。
まずは、サンプルコードを見てください。
1 2 3 4 5 6 7 8 9 |
package main import "fmt" func main() { for x := 0; x <= 6; x++ { fmt.Println("x variable is ", x) } } |
1 2 3 4 5 6 7 8 9 |
$ go run main.go x variable is 0 x variable is 1 x variable is 2 x variable is 3 x variable is 4 x variable is 5 x variable is 6 |
ループは常に「for」キーワードから始まります。
・x := 0
=> 変数の初期化です。ループの繰り返し回数によく利用されます。
・x < 6
=> ループ条件です。この条件が「True」の間だけループします
・x++
=> ループが一回りする度に必ず実行されます。初期化変数に対してカウントを繰り上げる役割を与えられることが多いです(ループ終了条件に移行させる)
先ほど示したforループの型が「最も一般的」な型となりますが、次のような宣言も可能です。
1 2 3 4 5 6 7 8 9 10 11 |
package main import "fmt" func main() { x := 0 for x < 10 { fmt.Println(x) x++ } } |
1 2 3 4 5 6 7 8 9 10 11 |
go run main.go 0 1 2 3 4 5 6 7 8 9 |
continueとbreak
ループ処理に必須な「continue」と「break」について学びましょう。
この二つのキーワードは、ループ処理をコントロールすることができます。
キーワード | 意味 |
---|---|
continue | ループ処理のイテレーションをループブロック内のコードを実行せずにスキップする |
break | ループ処理を抜ける |
言葉ではわかりずらいと思いますので、サンプルコードを見てみましょう。
continueの場合
まずは、continueです。下記のサンプルコードを実行するとどうなるでしょうか?
1 2 3 4 5 6 7 8 9 10 11 12 |
package main import "fmt" func main() { for x := 1; x <= 3; x++ { fmt.Println("START") continue fmt.Println("END") } } |
答えは、「START」が3回出力されるだけになります。
1 2 3 4 5 |
go run main.go START START START |
「fmt.Println(“END”)」処理がスキップされて次のイテレーションに処理が移行したことがわかると思います。
breakの場合
breakの場合は、どうなるでしょうか?
1 2 3 4 5 6 7 8 9 10 11 |
package main import "fmt" func main() { for x := 1; x <= 3; x++ { fmt.Println("START") break fmt.Println("END") } } |
答えは、「START」が1回のみ出力されます。
1 2 3 |
go run main.go START |
breakキーワードは、ループ処理自体を抜けるので、1回しか出力されません。
現場では、continueとbreakキーワードはよく使用します。そして、このキーワードはバグを生みやすい部分なので、よく理解しておいて下さい。
まとめ
今回学んだことを最後にまとめて終わりにしたいと思います。
・Goの例外処理
-> Go言語には、try-catch構文はない
-> Go言語は、複数の戻り値を返却できる
-> nilチェックによりエラー判定を行う
if err != nil { log.Fatal(err) }
-> エラーを無視したい場合は、「_(アンダースコア)」を使用する
・Goの条件判定
-> if構文を用いる
・ Strings->Numbersへの型変換
-> strconvパッケージを利用して型変換を行う
・変数のスコープ
-> ファイル、パッケージ、if, for それぞれにスコープが存在する
・Short Variable Declaration(:=)
-> 同一スコープ内で、二度目の初期化はできない。ただし、複数の変数を一緒に宣言し、かつ、片方が一度目の初期化の場合は、可能。
-> 関数外からの初期化はできない
・ループ
-> for構文を用いる
-> 制御構文に、continueとbreakがある
参考書籍
次回
次回は、Go言語の関数について学びましょう。
コメントを残す
コメントを投稿するにはログインしてください。