goの基礎を学ぶ|勉強メモ
goの基礎を勉強しているので、この記事にメモとして勉強したことを書いていきます。3万字超えのコンテンツとなっていて、僕のようにこれからgo言語を学ぶ人にとって役立つ内容となっているので参考にして頂けたらと思います。
goの変数宣言
変数の頭にvarをつける。
package main
import "fmt"
func main() {
var i int = 1
var f64 float64 = 1.2
var s string = "test"
var t, f bool = true, false
fmt.Println(i, f64, s, t, f)
}
上記のbool型のように同じ型の場合は、カンマで続けて宣言できる。
複数のvarはカッコでまとめることができる。スッキリ見やすくできる。
package main
import "fmt"
func main() {
var (
i int = 1
f64 float64 = 1.2
s string = "test"
t, f bool = true, false
)
fmt.Println(i, f64, s, t, f)
}
上記変数の値を初期化せずに宣言だけすると、デフォルトの値が暗黙にセットされる。intやfloatなら0、stringなら空文字、boolならfalseがセットされる。
簡潔に変数を書く方法
<変数名> := <値>
の書式で簡単に書くことができる。
この変数の書き方を使う場合は、関数内しかスコープがないので関数外からはアクセスできない。関数の外で宣言するとエラーになる。
var <変数名> の書き方だと関数外からもアクセスできる。関数の外で宣言もできる。
といった違いがある。
package main
import "fmt"
func main() {
i := 1
f64 := 1.2
s := "test"
t, f := true, false
fmt.Println(i, f64, s, t, f)
}
コード例
省略した書き方をfoo関数内で使い、varをmain関数の外側で宣言している例。
package main
import "fmt"
var (
i int = 1
f64 float64 = 1.2
s string = "test"
t, f bool = true, false
)
func foo() {
xi := 1
xf64 := 1.2
xs := "test"
xt, xf := true, false
fmt.Println(xi, xf64, xs, xt, xf)
}
func main() {
fmt.Println(i, f64, s, t, f)
foo()
}
// 実行結果
1 1.2 test true false
1 1.2 test true false
変数の型を調べて出力する方法
fmt.Printf("%T", <型を調べたい変数名>)
コード例の変数名xf64の型を調べる場合(xf64 := 1.2)
fmt.Printf("%T", xf64)
// float64 と出力される
変数の型をfloat32に変えたい場合、var xf32 float32 = 1.2などのように、varで厳密に宣言する。
fmt.Printfの出力は通常は改行されないので、改行を入れたい場合は、\nをいれてあげて、
fmt.Printf("%T\n", <型を調べたい変数名>)
とする。
constについて
const修飾子はグローバルで使われることが多いので、メイン関数の外側に定義することが多い。
const についても先頭を大文字にすることでpublicの意味になり、
小文字だとprivateの意味になる。
package main
const Pi = 3.14 // 大文字で書いているのでPiはpublicになる
func main() {
}
constについても()でまとめることができる。
const (
Username = "test_user"
Password = "test_pass"
)
constで定義した定数の表示
package main
import "fmt"
const Pi = 3.14
const (
Username = "test_user"
Password = "test_pass"
)
func main() {
fmt.Println(Pi, Username, Password)
}
constなので他のところで書き換えしようとしても書き換えできない。
constで宣言した時点では型は見ていない
int型のbigという変数名で変数を普通に定義すると、64bitの符号ありの整数の扱える最大の数値=9223372036854775807 なので、この最大値を変数の値として変数名bigに代入すると、例えばこの変数に+1したらオーバーフローして扱えないのでエラーとなる。
数値型の扱える範囲は下記のドキュメントに載っていました。
// これはエラーにならない
var big int = 9223372036854775807
// +1することでint型の扱える最大値を超えるのでエラーになる
var big int = 9223372036854775807 + 1
一方、constで
const big = 9223372036854775807 + 1
とオーバーフローするような値を定義してもエラーにならない。
constはアンタイプなので宣言時は型がついていないため。
コンパイラに認識はされている状態。
使用時に型がつけられる。
package main
import "fmt"
const Pi = 3.14
const (
Username = "test_user"
Password = "test_pass"
)
const big = 9223372036854775807 + 1
func main() {
fmt.Println(Pi, Username, Password)
fmt.Println(big-1)
}
数値型について
goの公式ドキュメントを参照。
int型で32ビットと64ビットを特に指定しない場合は、自動で32ビットか64ビットどちらかのint型が適用される。PCの環境に依存する。
complex64は複素数の64ビット。
package main
import "fmt"
func main() {
var (
u8 uint8 = 255
i8 int8 = 127
f32 float32 = 0.2
c64 complex64 = -5 + 12i
)
fmt.Println(u8, i8, f32, c64)
fmt.Printf("type=%T value=%v", u8, u8)
}
// 実行結果
// 255 127 0.2 (-5+12i)
// type=uint8 value=255
各種型の変数をまとめて書く際の書き方として推奨されているのが、上記コードのように一番文字数が多い型の = に合わせてそろえて書く方法。
fmt.Printf()関数の引数の説明もドキュメント参照。fmtパッケージで扱う引数の説明で%T, %vなどについて説明が記載されている。
fmt.Println()関数で計算を出力させることもできる。
どちらか片方の数値がフロートだったらフロートで計算結果を出力してくれる。
package main
import "fmt"
func main() {
fmt.Println("1 + 1 =", 1+1)
fmt.Println("10 - 1 =", 10-1)
fmt.Println("10 / 2 =", 10/2)
fmt.Println("10 / 3 =", 10/3)
fmt.Println("10.0 / 3 =", 10.0/3)
fmt.Println("10 / 3.0 =", 10/3.0)
fmt.Println("10 % 2 =", 10%2)
fmt.Println("10 % 3 =", 10%3)
}
// 実行結果
1 + 1 = 2
10 - 1 = 9
10 / 2 = 5
10 / 3 = 3
10.0 / 3 = 3.3333333333333335
10 / 3.0 = 3.3333333333333335
10 % 2 = 0
10 % 3 = 1
Process finished with the exit code 0
書き方のフォーマットとしてスペースの有り無しも決められている。
正)x := 1 + 1
誤)x:= 1+1
書いたコードをもとに、正しいフォーマットを表示してくれるgofmtというツールがある。
ターミナルで下記のように実行すると、書いたコードをもとに正しいフォーマットを表示してくれる。
$ gofmt <ファイル名.go>
// 実行結果
fmt.Println("1 + 1 =", 1+1)
fmt.Println("10 - 1 =", 10-1)
fmt.Println("10 / 2 =", 10/2)
fmt.Println("10 / 3 =", 10/3)
fmt.Println("10.0 / 3 =", 10.0/3)
fmt.Println("10 / 3.0 =", 10/3.0)
fmt.Println("10 % 2 =", 10%2)も
自動でフォーマットを修正したい場合は、上記のコマンドに -wオプションを渡すとフォーマットを自動的に整えてくれる。これは便利。
$ gofmt -w <ファイル名.go>
インクリメントやデクリメントの書き方はc++と同じ。
package main
import "fmt"
func main() {
x := 0
fmt.Println(x)
x++
fmt.Println(x)
x--
fmt.Println(x)
}
// 実行結果
0
1
0
シフト演算
package main
import "fmt"
func main() {
fmt.Println(1 << 0) // 0001 0000
fmt.Println(1 << 1) // 0001 0010
fmt.Println(1 << 2) // 0001 0100
fmt.Println(1 << 3) // 0001 1000
}
// 実行結果
1
2
4
8
fmt.Println(1 << 1) // 0001 0010
1は2進数で0001
1回シフトするので0010となり、これは10進数で2。
シフト演算の実行結果は10進数で出力される。
文字列型
+演算子による文字列の連結ができる。
[0]などと表記することで文字列の文字を表示できる。
package main
import "fmt"
func main() {
fmt.Println("Hello World")
fmt.Println("Hello " + "World")// +で文字連結
fmt.Println("Hello World"[0])// 72というASCIIコードが表示される
fmt.Println(string("Hello World"[0]))// 1番目の文字 H が表示される
}
ASCIIコードじゃなく普通に文字を表示させる場合は、上記のようにキャストする。
package main
func main() {
var s = "Hello World"
s[0] = "X" // エラーになる
}
c++のように1文字だけ普通に変えようとするとエラーになる仕様となっている。
strings.Replace()という関数を使うと文字列の文字を任意の文字に置き換えることができるが、fmt.Println()では、元の文字列をコピーしたうえで置き換えてプリントし表示させているので、もともとの文字列は変わっていないままとなる。
var s string = "Hello world"
fmt.Println(strings.Replace(s,"H","X",1))
// Xello World
fmt.Println(s)
// Hello World
もともとの文字列も置き換えたいときは、fmt.Println()じゃなく普通に変数sにstrings.Replace()の結果を代入する。
s = strings.Replace(s, "H", "X", 1)
fmt.Println(s)
// Xello World
文字列の中にある文字が入っているかを調べたいときは、strings.Contains()で調べることができる。
fmt.Println(strings.Contains(s, "world"))
// trueまたはfalseの出力結果となる
改行
fmt.Println("Hello\n" +
"World")
\nで改行できるけど、バッククオートでも改行できる。
fmt.Println(`hello
hello
world`)
// 実行結果
hello
hello
world
ダブルクオートの中にダブルクオートを書きたい場合
fmt.Println("\"")
// 出力結果
"
バックスラッシュでエスケープすることでダブルクオートを出力できる。
バッククオートで囲むことでも出力できる。
fmt.Println(`"`)
// 出力結果
"
論理値型(bool型)
true false
package main
import "fmt"
func main() {
//var t, f bool = true, false
t, f := true, false
fmt.Printf("%T %v %t\n", t, t, 1)
fmt.Printf("%T %v %t\n", f, f, 0)
}
//実行結果
bool true %!t(int=1)
bool false %!t(int=0)
fmt.Printf()関数に渡した変数が論理値型かどうかを厳密にしたいときは%tを使うと、
%tのところに渡した変数がもし論理値型じゃなかったらエラーを表示し、論理値型だったら論理値を表示してくれる。
コード例では%tに1と0を渡しているので、渡した変数の型は論理値型じゃないよ!と教えてくれている。
fmt.Println()関数を使って下記のように、&&、||、! で論理値を判定させて出力できる。
package main
import "fmt"
func main() {
fmt.Println(true && true) //true
fmt.Println(true && false) //false
fmt.Println(false && false) //false
fmt.Println(true || true) //true
fmt.Println(true || false) //true
fmt.Println(false || false) //false
fmt.Println(!true) //false
fmt.Println(!false) //true
}
型変換
int -> float64への型変換
package main
import "fmt"
func main() {
var x int = 1
xx := float64(x)
fmt.Printf("%T %v %f\n", xx, xx, xx)
}
// float64 1 1.000000
float64 -> intへの型変換
package main
import "fmt"
func main() {
var y float64 = 1.2
yy := int(y)
fmt.Printf("%T %v %d\n", yy, yy, yy)
}
// int 1 1
string型の文字列の数字をint型に変換する方法
下記だと型変換は不可。
var s string = "12"
z := int(s) //エラー
string型の数字をint型に型変換したい場合は下記のようにstrconv.Atoiメソッドを使用する。
var s string = "14"
i, _ := strconv.Atoi(s)
fmt.Printf("%T %v", i, i)
strconvのドキュメント↓
strconv.Atoiメソッドはintとerrorの有無の二つを返す。エラーがあったらエラーを返し、変換は実行されない。
エラーがなければ、変換した値と、エラーがなかったことを表すnilを返す。
通常は、i, err := strconv.Atoi(s)のように、iとerrを使うことを想定しているので、errなしでこの関数を使うとコンパイルエラーが起きる。
もしエラーハンドリングを使わないのであれば、errの代わりにアンダースコア "_"を使うとエラーハンドリングを使わずにstrconv.Atoiメソッドが使える。
package main
import (
"fmt"
"strconv"
)
func main() {
var s string = "12"
i, err := strconv.Atoi(s)
if err != nil {
fmt.Println("ERROR!")
}
fmt.Printf("%T %v", i, i)
}
文字列の1文字を下記のように表示させると、Hに該当するASCIIコードの72が表示される。Hとして表示させたいときは、stringでASCIIコードをキャストする。
f := "Hello World"
fmt.Println(f[0]) //72
f := "Hello World"
fmt.Println(string(f[0])) //H
配列
配列の宣言時に初期値を入れない場合
1 package main
2
3 import "fmt"
4
5 func main() {
6 var a [2]int
7 a[0] = 100
8 a[1] = 200
9 fmt.Println(a) //[100 200]
10 }
11
配列の宣言時に初期値を設定する場合は下記のように宣言する。
var b [2]int = [2]int{100, 200}
fmt.Println(b) //[100 200]
配列の宣言時に型のところで設定した配列のサイズは、後からサイズを変更できないので注意。
※スライスを利用するとサイズが可変の配列ができる。
スライス
配列とスライスの違いがどうちがうんだろうと思ったので軽く調べてみました。
・配列はサイズが固定長
・スライスは配列のサイズが可変長
上記の認識でよさそう。
スライスの例
1 package main
2
3 import "fmt"
4
5 func main() {
6 n := []int{1, 2, 3, 4, 5}
7 fmt.Println(n) //[1 2 3 4 5]
8 fmt.Println(n[2]) //3
9 fmt.Println(n[2:4]) //[3 4]
10 fmt.Println(n[:2]) //[1 2]
11 fmt.Println(n[2:]) //[3 4 5]
12 fmt.Println(n[:]) //[1 2 3 4 5]
13
14 }
15
スライスの要素を上書きしたい場合、
n[2] = 100 のようにして上書きできる。
スライスに要素を追加したい場合
append()関数を使用して要素をスライスの後ろに追加できる。
1 package main
2
3 import "fmt"
4
5 func main() {
6 n := []int{1, 2, 3, 4, 5}
7 fmt.Println(n)
8 fmt.Println(n[2])
9 fmt.Println(n[2:4])
10 fmt.Println(n[:2])
11 fmt.Println(n[2:])
12 fmt.Println(n[:])
13
14 n[2] = 100
15 fmt.Println(n)//[1 2 100 4 5]
16
17 n = append(n, 100, 200, 300, 400)
18 fmt.Println(n)
19 //[1 2 100 4 5 100 200 300 400]
20 }
スライスの中にスライスを作りたい場合、下記のようにする。
1 package main
2
3 import "fmt"
4
5 func main() {
6
7 var board = [][]int{
8 []int{0, 1, 2},
9 []int{3, 4, 5},
10 []int{6, 7, 8},
11 }
12 fmt.Println(board)
13 } //[[0 1 2] [3 4 5] [6 7 8]]
14
スライス|makeとcapについて
make() を使うことでスライスの初期設定をしてスライスを作れる。
package main
import "fmt"
func main() {
n := make([]int, 3, 5)
fmt.Printf("len=%d cap=%d value=%v\n", len(n), cap(n), n)
//len=3 cap=5 value=[0 0 0]
n = append(n, 0, 0)
fmt.Printf("len=%d cap=%d value=%v\n", len(n), cap(n), n)
//len=5 cap=5 value=[0 0 0 0 0]
n = append(n, 1, 2, 3, 4, 5)
fmt.Printf("len=%d cap=%d value=%v\n", len(n), cap(n), n)
//len=10 cap=10 value=[0 0 0 0 0 1 2 3 4 5]
}
初期設定のキャパシティ(スライスのサイズ、要素数)を超えた要素を追加すると都度キャパシティはその分自動的に追加される。
サイズ=キャパのときは、make()に渡す引数は下記のように1個でよい。
a := make([]int, 3)
fmt.Printf("len=%d cap=%d value=%v\n", len(a), cap(a), a)
//len=3 cap=3 value=[0 0 0]
makeで作ったスライスのメモリはサイズゼロとキャパ0でも確保されるが、varで作ったスライスの場合はnilとなり、スライスのメモリは確保されない。
b:= make([]int, 0)
fmt.Printf("len=%d cap=%d value=%v\n", len(b), cap(b), b)
//len=0 cap=0 value=[]
var c []int
fmt.Printf("len=%d cap=%d value=%v\n", len(c), cap(c), c)
//len=0 cap=0 value=[]
make()の初期設定での挙動の違い
サイズ5、キャパ5のときは、[0 0 0 0 0]で初期化される
サイズ0、キャパ5のときは、[ ]で初期化される
上記により、appendしたときの挙動が異なってくる。
//c = make([]int, 5)
c = make([]int, 0, 5)
for i :=0; i < 5; i++{
c = append(c, i)
fmt.Println(c)
}
fmt.Println(c)
mapについて
連想配列がmap関数で作れるみたい。
package main
import "fmt"
func main() {
m := map[string]int{"apple": 100, "banana": 200}
fmt.Println(m)
//map[apple:100 banana:200]
fmt.Println(m["apple"])
//100
}
キーを指定すると、値が取り出せる。
値の上書きと要素の追加は下記を参照。
package main
import "fmt"
func main() {
m := map[string]int{"apple": 100, "banana": 200}
fmt.Println(m)
fmt.Println(m["apple"])
m["banana"] = 300
fmt.Println(m)
//map[apple:100 banana:300]
m["new"] = 500
fmt.Println(m)
//map[apple:100 banana:300 new:500]
}
存在しない要素の値を取り出そうとすると、0と表示される。
mapはキーでアクセスすると、値と一緒に値が入っているかどうかの値も返してくれる。値が存在するときはtrue、存在しないときはfalseが返される。
下記コードでは、mapでアクセスしたキーに対しての返り値を2つの変数に格納しているので、キーに対する値と、値の有無を表すbool値が表示される。
package main
import "fmt"
func main() {
m := map[string]int{"apple": 100, "banana": 200}
fmt.Println(m)
fmt.Println(m["apple"])
m["banana"] = 300
fmt.Println(m)
m["new"] = 500
fmt.Println(m)
fmt.Println(m["nothing"])
//0
v, ok := m["apple"]
fmt.Println(v, ok)
//100 true
v2, ok2 := m["nothing"]
fmt.Println(v2, ok2)
//0 false
}
空のmapをmakeで作成し後からキーと値を追加する。
メモリが確保された状態なので、後からキーと値を入れていくことができる。
m2 := make(map[string]int)
m2["pc"] = 5000
fmt.Println(m2)
//map[pc:5000]
makeを使わず、普通の配列を作ってキーと値を入れようとしてもエラーが出る。普通の変数としての宣言では空の配列に対してメモリが確保されないため。
var m3 map[string]int
m3["pc"] = 5000
fmt.Println(m3)
//panic: assignment to entry in nil map
var宣言で空の配列を作成したら中身がnilになるのかを確認
var s []int
if s == nil{
fmt.Println("nil")
}
//nil
バイト型
alias for uint8と説明がドキュメントにあり、unit8の説明を見ると、
the set of all unsigned 8-bit integers (0 to 255)と書かれている。
0から255までの8ビットの符号なしの整数。
72は8ビットの2進数で01001000
このように、バイト型で生成した配列の要素は8ビットの値となる。
キャストせずに表示したらアスキーコードとしてそのまま表示される。文字列型にキャストすると、アスキーコードに該当する文字が表示される。
文字をバイト型にキャストしてアスキーコードで表示することもできる。
package main
import "fmt"
func main() {
b := []byte{72, 73}
fmt.Println(b)//[72 73]
fmt.Println(string(b))//HI
c := []byte("HI")
fmt.Println(c)//[72 73]
fmt.Println(string(c))//HI
}
関数
funcで宣言する|返り値なし
package main
import "fmt"
func add(){
fmt.Println("add function")
}
func main() {
add()
}
//add function
作成した関数に引数を渡したい場合、下記のように定義し、main関数の中で関数の引数に実行したい具体的な引数を渡してあげる。
package main
import "fmt"
func add(x int, y int){
fmt.Println("add function")
fmt.Println(x + y)
}
func main() {
add(10, 20)
}
//add function
//30
返り値ありの関数を作成
関数名の後ろに、関数の返り値の型を指定する。
main関数の中で作った関数を使うときは、返り値を入れる変数を準備する。
package main
import "fmt"
func add(x int, y int) int {
return x + y
}
func main() {
r := add(10, 20)
fmt.Println(r)
}
//30
func add(x, y int) int { }
のように、引数の型は1個に省略して書くこともできる。
返り値を2つにしたいとき
(int, int)のように、2つの返り値の型を指定してあげる。
返り値も2つリターンするように書く。
package main
import "fmt"
func add(x int, y int) (int, int) {
return x + y, x - y
}
func main() {
r1, r2 := add(10, 20)
fmt.Println(r1, r2)
}
//30 -10
返り値を変数として宣言もできる。
返り値を変数として宣言することによって、関数の1行目をみただけで、どんな返り値が返ってくるのかが推測しやすくなる。特に引数が何個もあるときは分かりやすくなる。
result intのように返り値の変数名と返り値の型を同時に宣言しておくことで、int型の返り値がresult変数に格納される。
このとき注意なのが、すでに返り値のところでresultを宣言しているので、関数の処理の中で
result := のように、:を書くとエラーになるので注意。
。また、返り値をresultに格納することを宣言しているので、
関数の最後のreturn文の引数は省略して、returnとだけ書くことができる。
package main
import "fmt"
func cal(price, item int) (result int){
result = price * item
return result
}
func main() {
r3 := cal(100, 2)
fmt.Println(r3)
}
//200
関数を変数の中に格納できる。
package main
import "fmt"
func cal(price, item int) (result int){
result = price * item
return result
}
func main() {
r3 := cal(100, 2)
fmt.Println(r3)
f := func(){
fmt.Println("inner func")
}
f()
}
//200
//inner func
引数を渡すときは下記のように定義して使う。
f := func(x int){
fmt.Println("inner func", x)
}
f(1)
}
//inner func 1
上記と同じ関数を下記のようにも宣言して使える。
上記では関数を変数名に格納して、f()で使うというやり方だけど、下記のように書いてそのまま実行できる書き方もある。
func(x int){
fmt.Println("inner func", x)
}(1)
クロージャ
package main
import "fmt"
func main() {
x := 0
increment := func() int{
x++
return x
}
fmt.Println(increment())
fmt.Println(increment())
fmt.Println(increment())
}
//1 2 3
メイン関数の中にかいてしまうとxの値が書き換えられてしまうかもしれないので、外側に定義する。
package main
import "fmt"
func incrementGenerator() (func() int) {
x := 0
return func() int {
x++
return x
}
}
func main() {
counter := incrementGenerator()
fmt.Println(counter())
fmt.Println(counter())
fmt.Println(counter())
fmt.Println(counter())
}
//1 2 3 4
返り値として関数を返すことができる。
その関数は、int型のクロージャーで、xをインクリメントしたものを返す。
メイン関数の中で、呼び出すたびに、x := 0より下の、incrementGeneratorの中のinner関数が呼ばれて、インクリメントされる。
同じコードで、引数に応じて結果を変えたいときにクロージャーを使うと便利。サンプルコードでは、piの値を保持して、クロージャー内の関数で保持した値を使っている。
1 package main
2
3 import "fmt"
4
5 func incrementGenerator() (func() int) {
6 x := 0
7 return func() int {
8 x++
9 return x
10 }
11 }
12
13 func circleArea(pi float64) func(radius float64) float64{
14 return func(radius float64) float64{
15 return pi * radius * radius
16 }
17 }
18 func main() {
19 counter := incrementGenerator()
20 fmt.Println(counter())
21 fmt.Println(counter())
22 fmt.Println(counter())
23 fmt.Println(counter())
24
25 c1 := circleArea(3.14)
26 fmt.Println(c1(2))
27
28 c2 := circleArea(3)
29 fmt.Println(c2(2))
30 }
//1 2 3 4
//12.56 12
可変長引数
関数の引数を可変にできる。
params ...<型>とすることで可変長引数になり、関数を使うときの引数の数が任意の個数にできる。
1 package main
2
3 import "fmt"
4
5 func foo(params ...int){
6 fmt.Println(len(params), params)
7 }
8 func main() {
9 foo(10, 20)
10 foo(10, 20, 30)
11}
//2 [10 20]
//3 [10 20 30]
下記ソースコードの16行目はスライスを宣言、19行目のように、このスライスを関数の引数に渡して、可変長引数の...を追加することで、スライスの中身を引数として展開してくれる。
1 package main
2
3 import "fmt"
4
5 func foo(params ...int){
6 fmt.Println(len(params), params)
7 for _, param := range params{
8 fmt.Println(param)
9 }
10 }
11 func main() {
12 foo() //引数をなにも渡さなくても実行できる。
13 foo(10, 20)
14 foo(10, 20, 30)
15 }
16 s := []int{1, 2, 3}
17 fmt.Println(s)
18
19 foo(s...)
20 }
//実行結果
0 []
2 [10 20]
10
20
3 [10 20 30]
10
20
30
[1 2 3]
3 [1 2 3]
1
2
3
ステートメント
ステ=トメントとは、if forなどの文のこと。
if文
ifとelse ifとelseの使い方
1 package main
2
3 import "fmt"
4
5 func main() {
6 num := 9
7 if num % 2 == 0 {
8 fmt.Println("by 2")
9 } else if num % 3 == 0 {
10 fmt.Println("by 3")
11 } else{
12 fmt.Println("else")
13 }
14 }
//by 3
&&の使い方
15 x, y := 10, 10
16 if x == 10 && y == 10{
17 fmt.Println("&&")
//&&
||の使い方
20 if x == 10 || y == 10{
21 fmt.Println("||")
22 }
//||
if文の使用例
1 package main
2
3 import "fmt"
4
5 func by2(num int) string{
6 if num % 2 == 0{
7 return "ok"
8 }else{
9 return "no"
10 }
11 }
12
13 func main() {
14 result := by2(10)
15
16 if result == "ok" {
17 fmt.Println("great")
18 }
//great
上記のif文を1行で書くこともできる。1文で書くことができるけど、result2変数はif文の中でしか使えない。if文の外側からはアクセスできない。
20 if result2 := by2(10); result2 == "ok"{
21 fmt.Println("great 2")
22 }
for文
continueを書くと、以降のプログラムを実行せずに、次のループへ行く。
1 package main
2
3 import "fmt"
4
5 func main(){
6 for i := 0; i < 10; i++ {
7 if i == 3{
8 fmt.Println("continue")
9 continue
10 }
11 fmt.Println(i)
12 }
13 }
//実行結果
0
1
2
continue
4
5
6
7
8
9
breakを書くと、break以降、ループ自体を抜ける。
1 package main
2
3 import "fmt"
4
5 func main(){
6 for i := 0; i < 10; i++ {
7 if i == 3{
8 fmt.Println("continue")
9 continue
10 }
11 if i > 5 {
12 fmt.Println("break")
13 break
14 }
15 fmt.Println(i)
16 }
17 }
//実行結果
0
1
2
continue
4
5
break
sumの初期値とカウンタは省略することができる。
18 sum := 1
19 for ; sum < 10; {
20 sum += sum
21 fmt.Println(sum)
22 }
23 fmt.Println(sum)
//実行結果
2
4
8
16
16
※さらに、for ; sum < 10 ; {}のセミコロンは両方とも省略もできる。
無限ループを意図してつくることもできる。
forだけ書くと無限ループになる。
for {
fmt.Println("hello")
}
//実行結果
hello
hello
hello
...
range
for文でrangeを使うこともできる。
range lと宣言することで、スライスlの添字と値を指定した変数に代入しループしてくれる。ここでは添字をiに、値をvに代入している。
1 package main
2
3 import "fmt"
4
5 func main(){
6 l := []string{"python", "go", "java"}
7
8 for i := 0; i < len(l); i++{
9 fmt.Println(i, l[i])
10 }
11
12 for i, v := range l{
13 fmt.Println(i, v)
14 }
15 }
// 実行結果
0 python
1 go
2 java
0 python
1 go
2 java
rangeで添字のプリントを省略したい場合、添字にあたるiの部分をアンダースコアにすると省略できる。
16 for _, v := range l {
17 fmt.Println(v)
18 }
//実行結果
python
go
java
mapの連想配列のキーとバリューもrangeで取り出せる。
20 m := map[string]int{"apple": 100, "banana": 200}
21
22 for k, v := range m{
23 fmt.Println(k, v)
24 }
mapからキーだけ取り出したいとき
キーに該当する変数だけ宣言すると、mapからキーが取り出させる。
26 for k := range m{
27 fmt.Println(k)
28 }
//実行結果
apple
banana
mapから値だけ取り出したいとき
mapから値だけ取り出したいときは、キーの部分をアンダースコアにする。
すると、mapから値だけ取り出せる。
30 for _, v := range m{
31 fmt.Println(v)
32 }
33 }
switch文
defaultはなくてもok. defaultを省略したらcaseに該当しない場合はswithを抜ける。
package main
import "fmt"
func main() {
os := "mac"
switch os {
case "mac":
fmt.Println("Mac!")
case "windows":
fmt.Println("windows!")
default:
fmt.Println("Default!") }
}
switch文で判定を行う対象の変数に関数を渡すこともできる。
package main
import "fmt"
func getOsName() string{
return "linux"
}
func main() {
os := getOsName()
switch os {
case "mac":
fmt.Println("Mac!")
case "windows":
fmt.Println("windows!")
default:
fmt.Println("Default!")
}
}
switch文で使う変数の定義と判定に使うことの宣言をswitch文の中で1行で書くこともできる。
package main
import "fmt"
func getOsName() string{
return "linux"
}
func main() {
switch os := getOsName(); os {
case "mac":
fmt.Println("Mac!")
case "windows":
fmt.Println("windows!")
default:
fmt.Println("Default!", os)
}
}
// Default! linux
switchだけ書いて、switch中でオブジェクトをcaseに渡して判定に使うこともできる。
import (
"fmt"
"time"
)
func getOsName() string{
return "linux"
}
func main() {
switch os := getOsName(); os {
case "mac":
fmt.Println("Mac!")
case "windows":
fmt.Println("windows!")
default:
fmt.Println("Default!", os)
}
t := time.Now()
fmt.Println(t.Hour())
switch {
case t.Hour() < 12:
fmt.Println("Morning")
case t.Hour() < 17:
fmt.Println("Afternoon")
case t.Hour() < 24:
fmt.Println("night")
}
}
defer
deferで処理を遅らせることができる。
1 package main
2 import "fmt"
3
4 func main() {
5 defer fmt.Println("world")
6
7 fmt.Println("hello")
8 }
// hello world
deferの実行される順番
1 package main
2 import "fmt"
3
4 func foo(){
5 defer fmt.Println("world 1")
6
7 fmt.Println("hello 1")
8 }
9 func main() {
10 foo()
11
12 defer fmt.Println("world")
13
14 fmt.Println("hello")
15 }
// hello 1 world 1
// hello world
deferの実行例
1 package main
2 import "fmt"
3
4 func main() {
5 fmt.Println("run")
6 defer fmt.Println("a")
7 defer fmt.Println("b")
8 defer fmt.Println("c")
9 fmt.Println("uooo")
10 }
//run
//uooo
//c
//b
//a
run, uooo, c, b, aの順にPrintlnが実行される。
deferは書き忘れの防止に使える。
下記の例では、file.Close()関数の書き忘れ防止としてdeferをつけて書いている。こうすることで、処理の最後にfile.Close()を実行してくれる。
1 package main
2
3 import (
4 ▸-"fmt"
5 ▸-"os"
6 )
7
8 func main() {
9 file, _ := os.Open("./go.mod")
10 defer file.Close()
11 data := make([]byte, 100)
12 file.Read(data)
13 fmt.Println(string(data))
14 }
osパッケージの実行結果を保存する変数は2つあり、一つ目に実行結果を入れる変数名、二つ目の変数名は、エラーを入れる変数名。今回のコード例の場合は、開いたファイルの結果を入れる変数名がfile,エラーがあった場合にエラーを入れる変数名はアンダースコアで省略している。
log
日付と時刻とともに文字列を出力させることができる。
1 package main
2
3 import (
4 ▸-"log"
5 )
6
7 func main() {
8 log.Println("logging!")
9 log.Printf("%T %v", "hello", "hello")
10
11 log.Fatalln("error")
12 }
//2021/07/30 12:20:36 logging!
//2021/07/30 12:20:36 string hello
//2021/07/30 12:20:36 error exit status 1 2021/07/30 12:20:36 string hello 2021/07/30 12:20:36 error exit status 1
log.Fatallnを実行すると、そこでプログラムが終了するので、以降に書いたプログラムは実行されない。
1 package main
2
3 import (
4 ▸-"log"
5 )
6
7 func main() {
8 log.Println("logging!")
9 log.Printf("%T %v", "hello", "hello")
10 log.Fatalf("%T %v", "uoo", "uoo")
11
12 log.Fatalln("error")
13 }
// log.Fatalf()を実行して終了
log.Fatallnをエラーハンドリングに使用する例
os.Open()の実行結果として、エラーがなければnilが返ってくる。もしエラーだと、nil以外の結果が返ってくるので、その動作を利用して、エラーである場合は、エラーメッセージを表示させたりすることができる。
1 package main
2
3 import (
4 ▸-"log"
5 ▸-"os"
6 )
7
8 func main() {
9 _, err := os.Open("amaenbo")
10 if err != nil{
11 log.Fatalln("Exit", err)
12 }
13 log.Println("logging!")
14 log.Printf("%T %v", "hello", "hello")
15 log.Fatalf("%T %v", "uoo", "uoo")
16
17 log.Fatalln("error")
18 }
//2021/07/30 12:33:52 Exit open amaenbo: no such file or directory
//exit status 1
11行目まででプログラムが終了し、以降のプログラムは実行されない。
logをファイルに保存する例。
プログラムの実行結果がログに記録される。
1 package main
2
3 import (
4 ▸-"io"
5 ▸-"log"
6 ▸-"os"
7 )
8
9 func LoggingSettings(logFile string){
10 logfile, _ := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
11 multiLogFile := io.MultiWriter(os.Stdout, logfile)
12 log.SetFlags(log.Ldate | log.Ltime | log.Llongfile)
13 log.SetOutput(multiLogFile)
14 }
15
16 func main() {
17 LoggingSettings("test.log")
18 _, err := os.Open("amaenbo")
19 if err != nil{
20 log.Fatalln("Exit", err)
21 }
22 log.Println("logging!")
23 log.Printf("%T %v", "hello", "hello")
24 log.Fatalf("%T %v", "uoo", "uoo")
25
26 log.Fatalln("error")
27 }
エラーハンドリングの方法
カレントディレクトリのファイルを開く。もしファイルが存在しなかったらファイルを開けないので、エラーハンドリングを行う。ファイルを開いたら、バイト列の配列を作成し、読み込んだファイルの内容をバイト列に保存する。file.Read()はバイト列に保存したファイルの内容のカウント数を返すので、カウント数とバイト列に保存したファイルの内容を文字列にキャストして出力する。ここでもエラーハンドリングを行う。
出力される文字列は、作成したバイト数分だけ入るので、そのバイト数分だけ出力される。
nilかそれ以外かを入れるerr変数を2回宣言しているが、2回目の宣言で新たに初期化されている(上書きされている)。
short宣言(:=)は、左側の一つでも初期化されていればエラーにならない。
2つ目のerrのところは、countは初めての宣言なので初期化されていて、errについては、一つ目のerrを上書きしている。
3つ目のerrでもしerr := os.Chdir()のようにshort宣言で初期化しようとすると、エラーになる。1つ目のshort宣言で初期化したものをまた初期化しようとするため、エラーとなる。
なので、3つ目のように単独でerrを宣言する場合は、shortによる初期化ではなく、=によって変数errをオーバーライドすればよい。
package main
import (
"fmt"
"log"
"os"
)
func main() {
file, err := os.Open("./gostudy.go")
if err != nil{
log.Fatalln("error")
}
defer file.Close()
data := make([]byte, 400)
count, err := file.Read(data)
if err != nil{
log.Fatalln("error")
}
fmt.Println(count, string(data))
err = os.Chdir("golang")
if err != nil{
log.Fatalln("error")
}
}
最後のerrの部分は、1行で書くこともできる。
if err = os.Chdir("golang"); err != nil {}
panicとrecover
panicとrecoverは自分で作ったコードの中で使うことは推奨していない。errorハンドリングをちゃんと使って処理するようにしたほうがいい。
package main
import (
"fmt"
)
func thirdPartyConnectDB(){
panic("Unable to connect database")
}
func save(){
thirdPartyConnectDB()
}
func main() {
save()
fmt.Println("ok?")
}
//実行結果(panicの実行によってプログラムが強制終了される)
panic: Unable to connect database
goroutine 1 [running]:
main.thirdPartyConnectDB(...)
C:/Users/user/golang/gostudy.go:8
main.save(...)
C:/Users/user/golang/gostudy.go:12
main.main()
C:/Users/user/golang/gostudy.go:16 +0x45
exit status 2
panicにrecover()を組み合わせると、panicで投げられた例外をrecoverでキャッチしてプログラムを正常に終わらせることができる。
deferを使うので、}の後ろに( )を書くのを忘れないようにする。
package main
import (
"fmt"
)
func thirdPartyConnectDB(){
panic("Unable to connect database")
}
func save(){
defer func(){
s := recover()
fmt.Println(s)
}()
thirdPartyConnectDB()
}
func main() {
save()
fmt.Println("ok?")
}
//実行結果
Unable to connect database
ok?
練習
スライスのデータから最小値を出力する。
最小値を出力する関数sort.Ints()を利用した例。意味ないですが関数定義の練習として独自の関数を定義しその中でintsを使うようにしてみました。
package main
import (
"fmt"
"sort"
)
func min(l []int){
sort.Ints(l)
}
func main() {
l := []int{100, 300, 23, 11, 23, 2, 4, 6, 4}
min(l)
fmt.Printf("最小値は%vです", l[0])
}
//最小値は2です
上記をsort.Intsを使わずに書くと。
package main
import (
"fmt"
)
func min(l []int){
N := len(l)
for i := 0; i < N-1; i++ {
for j := 0; j < N-1; j++ {
if l[j] > l[j+1] {
x := l[j]
l[j] = l[j+1]
l[j+1] = x
}
}
}
fmt.Printf("min is %v", l[0])
}
func main() {
l := []int{100, 300, 23, 11, 23, 2, 4, 6, 4}
min(l)
}
//min is 2
mapで作った連想配列の値の合計値を出力する
func main() {
m := map[string]int{
"a": 200,
"b": 300,
"c": 150,
"d": 80,
"e": 500,
"f": 90,
}
var sum int = 0
for _, v := range m{
sum = sum + v
}
fmt.Printf("sum is %v", sum)
}
//sum is 1320
ポインタについて
package main
import (
"fmt"
)
func main() {
var n int = 100
fmt.Println(n)
fmt.Println(&n)
var p *int = &n
fmt.Println(p)
fmt.Println(*p)
}
//出力結果
100
0xc000010078
0xc000010078
100
var p *int = &n
この操作によって、ポインタ型のintである変数の箱pにnのアドレスを保存している。pは変数の箱なので、pについてもnとは別にアドレスが確保されていることに注意。通常は、変数の箱には値を入れるけど、pはポインタ型の変数の箱なので、nのアドレスを格納している。
なので、
fmt.Println(p)でポインタ型変数のpの中身を出力すると、nのアドレスが入っているので16進数でnのアドレスが表示される。
fmt.Println(*p)
*pとアスタリスクをつけて表示することで、アスタリスクによってpに格納しているアドレスの値を参照し、pの値が出力される。
ポインタを使った例(dereference | デリファレンス)
package main
import (
"fmt"
)
func one(x *int){
*x = 1
}
func main() {
var n int = 100
one(&n)
fmt.Println(n)
}
//1が出力される
var n int = 100によって、変数nには100が格納されている。
one(&n)によって、100を格納したnのアドレスが関数oneのポインタ型変数xに渡される。
ここで、ポインタ型変数xはnのアドレスが入っている。
one関数の中身でポインタ型変数xに値1を代入している。
なので、nのアドレスの値は1に書き換わっている。
そのため、1が出力される。
ポインタ|newとmake
newでポインタ型のアドレスを格納するメモリ領域を確保できる。
package main
import (
"fmt"
)
func main() {
var p *int = new(int)
fmt.Println(p)
var p2 *int
fmt.Println(p2)
}
//0xc0000aa058
//<nil>
var p2 *intでは、ポインタ型の変数p2を宣言したけど、メモリに変数p2のためのメモリ領域が確保されていない状態。
この状態でp2を出力すると、メモリ確保されていないので、何もないことを表すnilが返ってくる。
var p *int = new(int)
fmt.Println(*p)によってpのアドレスではなくて値を出力すると、0と出力される。
package main
import (
"fmt"
)
func main() {
var p *int = new(int)
fmt.Println(*p)
*p++
fmt.Println(*p)
var p2 *int
fmt.Println(p2)
*p2++
fmt.Println(*p2)
}
//実行結果
0
1
<nil>
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x0 pc=0x94c5e7]
goroutine 1 [running]:
main.main()
C:/Users/user/golang/gostudy.go:15 +0xe7
exit status 2
メモリを確保していないp2に対して値をインクリメントしようとしているので実行時エラーとなりpanicが発生。
newとmakeの違い
ポインタ型が返ってくるものについてはnewで変数を作成し、それ以外はmakeでスライスやmapを作成する。
package main
import (
"fmt"
)
func main() {
s := make([]int, 0)
fmt.Printf("%T\n", s)
m := make(map[string]int)
fmt.Printf("%T\n", m)
var p *int = new(int)
fmt.Printf("%T", p)
}
//実行結果
[]int
map[string]int
*int
構造体struct
構造体の中身は大文字で宣言する。大文字がpublic、小文字だとprivateとなるので、外側からアクセスできない。
int x, int y, string sのデフォルトの値は、int型の場合は0、string型の場合は空文字となる。
package main
import "fmt"
type Vertex struct{
X int
Y int
S string
}
func main() {
v := Vertex{X:1, Y:2}
fmt.Println(v)
fmt.Println(v.X, v.Y)
v.X = 100
fmt.Println(v.X, v.Y)
v2 := Vertex{X: 1}
fmt.Println(v2)
v3 := Vertex{1, 2, "hello"}
fmt.Println(v3)
v4 := Vertex{}
fmt.Println(v4)
var v5 Vertex
fmt.Printf("%T %v\n", v5, v5)
v6 := new(Vertex)
fmt.Printf("%T %v\n", v6, v6)
v7 := &Vertex{}
fmt.Printf("%T %v\n", v7, v7)
}
//出力結果
{1 2 }
1 2
100 2
{1 0 }
{1 2 hello}
{0 0 }
main.Vertex {0 0 }
*main.Vertex &{0 0 }
*main.Vertex &{0 0 }
varで宣言について、スライスやマップは何も値を設定しない場合は値はnilになるけど、varで宣言した構造体のインスタンスの値はnilでなはく0や空文字になるので注意。
changeVertex(v)としても値渡しとなりvのコピーに対して値の変更がされるので、元のvの値は変化しない。
package main
import "fmt"
type Vertex struct{
X int
Y int
S string
}
func changeVertex(v Vertex){
v.X = 1000
}
func changeVertex2(v *Vertex){
v.X = 1000
}
func main() {
v := Vertex{1, 2, "test"}
changeVertex(v)
fmt.Println(v)
v2 := &Vertex{1, 2, "test"}
changeVertex2(v2)
fmt.Println(v2)
}
//出力結果
{1 2 test}
&{1000 2 test}
値渡し:
changeVertex(v)とすると、v の値のコピーが関数changeVertex(v)のvに渡されるので、関数内でX=1000としてvの値を変えても、変数のコピーに対して値を変えているだけなので、元の変数vの値は変わらない。
参照渡し:
changeVertex(v *Vertex)とすると、vの値がコピーされずそのままchangeVertx(v *Vertex)に渡される。
つまり、v.X=1, v.Y=2, v.S="test"という値が格納されたアドレスがそのままchangeVertex(v, *Vertex)に渡されるので、changeVertex(v *Vertex)の中でv.X=1000に書き換えると、ちゃんと変更が反映される。
Structオリエンテッド
19行目のfmt.Println(Area(v))のように引数に関数名Area(v)を渡す方法と、
20行目のfmt.Println(v.Area())のように引数にクラスのメソッドのようにv.Area()を渡す方法がある。こっちの方が可読性が高い。 下記のようにArea()関数をクラスのメソッドのように宣言できる。
func (v Vertex) Area() int{
return v.X * v.Y
}
1 package main
2
3 import "fmt"
4
5 type Vertex struct{
6 X, Y int
7 }
8
9 func (v Vertex) Area() int{
10 return v.X * v.Y
11 }
12
13 func Area(v Vertex) int {
14 return v.X * v.Y
15 }
16
17 func main() {
18 v := Vertex{3, 4}
19 fmt.Println(Area(v)) //12
20 fmt.Println(v.Area()) //12
21
22 }
ポインタレシーバ
structのVertexをポインタにすると、VertexのX,Yを書き換えることができる。ポインタにしない場合は値渡しとなるので値レシーバと呼ぶ。
1 package main
2
3 import "fmt"
4
5 type Vertex struct{
6 X, Y int
7 }
8
9 func (v Vertex) Area() int{
10 return v.X * v.Y
11 }
12
13 func (v *Vertex) Scale(i int) {
14 v.X = v.X * i
15 v.Y = v.Y * i
16 }
17
18 func Area(v Vertex) int {
19 return v.X * v.Y
20 }
21
22 func main() {
23 v := Vertex{3, 4}
24
25 v.Scale(10)
26 fmt.Println(v.Area()) //1200
27 }
jojooojijioojojojojo
この記事が気に入ったらサポートをしてみませんか?