ごまなつ Blog

楽しく働ける世界を目指して

go言語の基本の基本

go言語の基本を学んだので、まとめてみようと思います。C言語をやったことがあるので、それとの比較になります。

基本構文

package パッケージ名

import(インポートするプラグイン名(複数書ける))

init(){}//main関数よりも先に呼ばれる

main(){}

変数宣言

var{
  i int =1
  f64 float64
  s string
  t, f bool
}

変数の型は、変数名の後に書く。初期値を宣言していない場合は自動的に初期値が入る。 この宣言は他の関数外に書いてすべての関数で使えるようにできる。関数内でしか使えない変数宣言は

xi := 1
xf54 :=1.2
xs := "test"
xt, xf := true, false

のように:=を使って宣言する。型は、自動的に設定される。 printfはCと同じような構文。printlnもある

fmt.Printf("%v", 変数名)

fmtはライブラリ名。ライブラリの中の関数を呼ぶ形になるため、ライブラリ名.関数名という呼び出しになる。 %vの部分はCのように%dだったり%fのように用途によって使い分ける。 %Tは、変数の型を調べられる。%vは変数の値、%tは論理値を表示する。 float32型はvarの宣言を使う必要がある。改行文字は"\n" const宣言はある。constは明示的に型を宣言しない。そのため、その変数を使うときにオーバーフローしていなければエラーにならない。 しかし、型を宣言していると使う前でもオーバーフローさせた時点でエラーになる。 型にはcomplex64、complex32という複素数の型もある。 goが推奨するインデントは一番長い変数にそろえる。

var{
  u8  uint8     =255
  i8  int8      =127
  f32 float32   = 0.2
  c64 complex64 = -5 + 12i
}

変数シフトは、 変数名 << シフト回数 文字列のワイルドカードは、``(シングルクォーテーション)で文字列全体を囲むか、ワイルドカードしたい文字の前に\をつける。 型変換は

var x int = 1
xx := float64(x)

のようにするとxxにfloat64型のxが入る。intをstringに変換するときは

i, _ := strconv.Atoi(s)

とする。変数の_は、この返り値を使わないという宣言。Atoi(s)は、intとerrorの2つの返り値を返しているが、iしか使わないときはこのような書き方をする。

配列、スライス、マップ

配列の宣言は

var b [2]int = [2]int{100, 200}

のように宣言する。[2]intの部分が型になっている。 スライスの宣言は

n := []int{1, 2, 3, 4, 5, 6}

スライスはスライス特有の取り出し方がある。 n[2:4]は[3 4]を取り出す。前の値は最初のインデックスは0だが、後ろの値は最初のインデックスは1になっている。2つの値の差の個数分取り出せると覚えておけばよい? n[:2]は[1 2]。最初から2個目までを取り出すという意味。 n[2:]は[3 4 5 6]。n[2]から最後まで取り出すという意味。 スライスの中にスライスを宣言することもできる。

var board = [][]int{
  []int{0, 1, 2}
  []int{3, 4, 5}
  []int{6, 7, 8}
}

とすると[ [0 1 2] [3 4 5] [6 7 8] ]が宣言できる。 スライスはappend(n, 入れたい値)で要素を末尾に追加できるが、配列はリサイズ不可能なので要素追加できない。 makeでスライスを宣言する方法もある。

n := make([]int, 3)

[]int型の長さ3、キャパシティ5のスライスを作れる。 len(n)で長さ、cap(n)でキャパシティが表示できる。キャパシティは柔軟なので、超える量の値が来たら要素をすべて追加して超えた量だけキャパシティが増える。

b := make([]int, 3)

では長さ、キャパシティともに3になる。makeはキャパシティ分をメモリ確保する。

関数

func add(x, y int)(int, int) {
  z := x+y
  return z
}

関数名(この関数内で使う変数)(返り値の型)という形。x, y intでどちらもintになる。返り値を複数返すことができ、複数返すなら(int, int)のようにする。 返り値の部分で(result int)とするとint型のresultを返す。関数の返り値を代入する変数には型を宣言しなくてもよい。

クロージャ

f := func()

とすると,fという名前のfuncの処理をする関数ができる。 func (x int){ fmt.Println("inner func", x) }(1) とすると、一回変数に入れなくてもこの関数が動く。

func incremaentGenerator()(func() int){
  x := 0
  return func() int{
    x++
    return x
  }
}
func main(){
  counter := incrementGenerator()
  counter()
}

counter()でincrementGeneratorが動く。関数が返り値として帰ってきているため。counterを呼ぶと1ずつ増えていく。 どう使うかという例

func circleArea(pi float64) func(radius float64) float64{
  return func(radius float64) float64{
    return pi*radius*radius
  }
}
c1 := circleArea(3.14)
fmt.Println(c1(2))

でpiが3.14、radiusが2の状態で上の関数が動く。

c2 := circleArea(3)
fmt.Println(c2(2))

でpiが3、radiusが2の状態で上の関数が動く。 この2つの状態をmain関数の中で記述できる。具体的には、関数の中の値をmain関数の中で設定して呼ぶことができる。

可変長引数

func foo(params ...int){}

で可変長引数が宣言できる。これは、引数の数が変わっても柔軟に対応できるもの。

s := []int{1, 2, 3}
foo(s...)

とすると、[1 2 3]を渡せる。展開して引数として渡している。

if文、for文

if 条件文 {} 条件文に()はいらない。

if result2 := by2(10); result2 =="ok"{}

でby2の返り値がokかどうかのif文が書ける。

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

が基本。()はいらない

l := []string{"python", "go", "java"}
for i,v := range l{
  fmt.Println(i,v)
}

とすると、0 python 1 go 2 javaが出力される

m := map[string]int{"apple":100, "banana": 200}

for k, v := range m{
  fmt.Println(k, v)
}

とすると、apple 100 banana 200が出力される

switch文

defaultはなくてもよい。C言語と基本的には同じ。だが、条件式に()がいらない

defer

遅延実行を宣言する。

func main(){
  defer fmt.Println("world")
  fmtPrintln("hello")
}

とすると、hello worldが出力される。つまり、deferの行はその関数の最後に実行される。 注意点としては、

func main(){
  defer fmt.Println("1")
  defer fmt.Println("2")
  defer fmt.Println("3")
  defer fmt.Println("4")
}

とすると、出力は4321になる。deferの処理はスタック順になるため後入れ先出しになるため。 deferをファイルクローズに使うと、openした次の行に書けるため、クローズし忘れることがなくなる。

log

log.Println("logging!")とすると、日時を表示した後logging!が表示される log.Printf("%T %v, "test","test")とすると、日時を表示した後string testが出力される log.Fatalln("error!!")がよく使われる。これは、Fatallnの行でプログラムが終了する log.Fatalf("%T %v, "test","test")でもこの行でプログラムが終了する

エラーハンドリング

ログの内容をファイルに書き込む

func LoggingSettings(logFile string){
  logfile, _ := os.OpenFile(logfile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
  multiLogRFile := io.MultiWriter(os.Stdout,logfile)
  log.SetFlags(log.Ldate|log.Ltime|log.Llongfile)
  log.SetOutput(multiLogFile)
}
LoggingSettings("test.log")

func main(){
  file, err := os.Open("./lesson.go")
  if err !=nil{
    log.Fatalln("Error!")
  }
  defer file.Close()
  data := make([]byte, 100)
  count, err := file.Read(data)
  if err != nil{
    log.Fatalln("Error")
  }
  fmt.Println(count, string(data))
}

とすると、100 lesson.goの内容が表示される。os.Openは、エラーでなかったらnilを返す。file.Readもエラーでないならnilを返す。よって、nilでないならエラーである。 ほかの言語ならtrycatchを使うが、goはこのようにする :=は、最低一つ代入があれば使える。同じ変数名だったら上書きされる。

os.Chdirは、errorという1つの数を返すので、 err := os.Chdir()とするとエラーになる err = os.Chdir()とするとエラーにならない if err = os.Chdir("test); err != nil{ log.Faralln("Error") }

panic, recover

panicは、自分で例外を投げられる。

func thirdPartyConnectDB(){
  panic("Unable to connect database!")
}
func save(){
  thirdPartyConnectDB()
}
func main(){
  save()
  fmt.Println("OK?")
}

として実行すると、panicでメッセージを表示した後エラーを表示して強制終了する。

func save(){
  defer func(){
    s := recover()
    fmt.Println(s)
  }()
  thirdPartyConnectDB()
}

に変更するとpanicのスタックトレースを表示しない。recoverはpanicで強制終了しないようにできる。 deferの部分は先に書くこと goはpanicを自分で書くことを推奨していない。panicを書くコードはなるべく書かないようにする。

終わりに

goの基本を学びました。学ぶだけでなく、実際に使ってみないと身につかないのでこれから何か作っていこうと思います。