【Go言語】【book】「WEB+DB PRESS Vol.82」を使った学習記録

下記の「WEB+DB PRESS Vol.82」を使った学習記録のページです。
 
以下、私が勝手に章に分けています。
 
書籍の Go のバージョンは 1.4 であり、現在は 1.11 であることから、本ページには古い記述形式があるかも知れませんが、目的は Go言語の勉強なので気にせずに書いていきます。
 
また、私自身が Go言語の習得を開始したということもあって、本書の内容は3章以降は駆け足気味で説明不足に感じ、あまり学習していません。

なお、ブログ表記の都合でタブを半角スペース2個に置き換えています。

WEB+DB PRESS Vol.82

WEB+DB PRESS Vol.82

  • 作者: 山口徹,Jxck,佐々木大輔,横路隆,加来純一,山本伶,大平武志,米川健一,坂本登史文,若原祥正,和久田龍,平栗遵宜,伊藤直也,佐藤太一,高橋俊幸,海野弘成,五嶋壮晃,佐藤歩,吉村総一郎,橋本翔,舘野祐一,中島聡,渡邊恵太,はまちや2,竹原,河合宜文,WEB+DB PRESS編集部
  • 出版社/メーカー: 技術評論社
  • 発売日: 2014/08/23
  • メディア: 大型本
  • この商品を含むブログ (1件) を見る


1章 Go言語の特徴と環境構築
 1.1 特徴
 1.2 作成・コンパイル・実行
   hello.go
 1.3 プロジェクトの構成
   gosample.go
   main.go

2章 基本文法
 2.1 インポート
   main.go
 2.2 変数
   var.go
   var_2.go
 2.3 定数
   const.go
 2.4 if, for, break, continue, swith, fallthrough
   break.go
   continue.go
   fallthrough.go
   for1.go
   for2.go
   for3.go
   if.go
   switch1.go
   switch2.go
 2.5 関数
   func1.go
   func2.go
   func3.go
   func_vargs.go
 2.6 関数リテラル
   funcliteral1.go
   funcliteral2.go
 2.7 配列
   array1.go
   array2.go
   array3.go
   array4.go
   array_range.go
 2.8 スライス
   slice1.go
   slice2.go
   slice4.go
   slice_range.go
 2.9 マップ
   map1.go
   map2.go
 2.10 ポインタ
   pointer1.go
   pointer2.go
 2.11 defer
   defer1.go
 2.12 パニック
   panic1.go
   recover1.go
   recover2.go
 
3章 型システム
 3.1 type
   type.go
 3.2 struct
   struct1.go
   struct2.go
   struct3.go
 3.3 ポインタ
   new.go
   struct_pointer1.go
 3.4 コンストラクタ
   constructor.go
 3.5 メソッド
   method1.go
   method2.go
 3.6 インタフェース
   interface1.go
 3.7 空インタフェース
   empty_interface1.go
   empty_interface2.go
 3.8 型の埋め込み - 構造体
   embStruct1.go
 3.9 型の埋め込み - インタフェース
 3.10 キャスト
   cast1.go
 3.11 型の検査
   empty_interface2.go

 
4章 標準パッケージ - JSON、ファイル、HTTP、HTML を扱う


5章 並行プログラミング - ゴルーチンとチャネルを使いこなす
 5.1 ゴルーチン
   httpGetP1.go
   httpGetP2.go
   httpGetS.go
 5.2 チャネル
   channel1.go
   channel2.go
 5.3 イベント制御
 5.4 チャネルバッファ

 
 

1.1

 
Go言語を書くうえでの注意点

  • ループは for のみである
    • for の条件文は () を使ってはいけない
  • 未使用変数が存在しているとコンパイルエラーとなる
  • 未使用変数パッケージが存在しているとコンパイルエラーとなる
  • 3項演算子は非サポートである
  • func, if などの { の位置は改行してはいけない
  • else, else if を書く際は、} else といったように } と同一行に else を書く
  • 書式は gofmt ツールで整形した形が標準となる。タブインデントが嫌だが従う...

最少構成のコード
毎回、以下の型枠コードが必要になる。

package main

import (
)

func main() {
}

 
Go のコマンドラインツール

コマンド 用途
go build プログラムのビルド
go fmt Go の規約に合わせてプログラムを整形
go get 外部パッケージの取得
go install プログラムのビルドとインストール
go run プログラムのビルドと実行
go test テストやベンチマークの実行
go tool yacc パーサを Go で出力する Go 実装の yacc(パーサジェネレータ)
go doc ソースからドキュメントの生成

 
 

1.2

 
コンパイルと実行を同時に行う

% go run hello.go 
hello world

 
コンパイルのみ
実行ファイルである hello が作成される。ややファイルサイズが大きい。

% go build hello.go

% ls -lh hello
-rwxr-xr-x 1 neko neko 1.9M  91 12:18 hello*

 
ファイル種別は静的リンクされたバイナリなので、同じアーキテクチャであれば異なるマシンでも実行可能である。
なお、nm でシンボルを見ることはできるが、いまひとつ馴染みがない名称が表示される。

% file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped


 

package main

import (
  "fmt"
)

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

 
 

1.3

 
ディレクトリ構成を作る

myproject/
├── bin# 実行ファイル置き場
├── pkg# 依存パッケージのオブジェクトファイル置き場
└── src# ソースコード置き場

 
環境変数 GOPATH を設定する

fish shell の場合

% cd myproject/
% set -x GOPATH (pwd)

bash shell の場合

% cd myproject/
% export GOPATH=`pwd`

 
パッケージの作成をする

src/gosample/gosample.go を作る。パッケージ名は gosample とする。

package gosample

var Message string = "hello world"

src/main/main.go を作る。パッケージ名は main とする。

package main

import (
    "fmt"
    "gosample"
)

func main() {
  fmt.println( gosample.Message )
}

 
ビルドを行い実行ファイルを作成する

以下のように go install をすると $GOPATH/bin/. に実行ファイルを配置してくれる。
cd $GOPATH/src/main ; go build ; mv main $GOPATH/bin/. と同じことである。

% cd $GOPATH/src/main
% go install

以下のように実行ファイル main が配置された。

% tree
$GOPATH/myproject
├── bin
│   └── main
├── pkg
└── src
    ├── gosample
    │   └── gosample.go
    └── main
        └── main.go

5 directories, 3 files

 

gosample.go
package gosample

var Message string = "hello world"

 

main.go
package main

// $GOPATH 以下をサーチして gosample パッケージを参照する
import (
  "fmt"
  "gosample"
)

func main() {
  fmt.Println(gosample.Message)
}

 
 

2.1

 

main.go
package main

// $GOPATH 以下をサーチして gosample パッケージを参照する
import (
  f "fmt"      // fmt と書かずに f で済ませる
  _ "gosample" // gosample パッケージは使用しない
  . "strings"  // strings の記入なしでメソッドを使用する
)

func main() {

  // 省略なしならば次のように書く必要がある
  // fmt.Println( strings.ToUpper("Hello World") )

  f.Println(ToUpper("Hello World"))
}

 
 

2.10

 

pointer1.go
package main

import (
  "fmt"
)

func callByValue(i int) {
  i = 20 // 値を上書きする
}

func callByRef(i *int) {
  *i = 20 // 参照先を上書きする
}

func main() {
  var i int = 10

  callByValue(i) // 値コピー
  fmt.Println(i)

  callByRef(&i) // ポインタ渡し
  fmt.Println(i)
}

 

pointer2.go
package main

import (
  "fmt"
)

// スライスの場合はポインタ渡しになるので、本関数でスライスを変更すると
// 呼び出し元の値を変えることになる
func testRef(slc []string) {
  slc[0] = "0000"
  slc[1] = "1111"
  slc[2] = "2222"
  slc[3] = "3333"
}

func main() {

  // 文字列(string)を 4個持つ固定配列を定義する
  s := []string{"a", "bb", "ccc", "dddd"}

  for idx, val := range s {
    fmt.Println(idx, val)
  }

  testRef(s)

  for idx, val := range s {
    fmt.Println(idx, val)
  }
}

 
 

2.11

 

defer1.go
package main

import (
  _ "fmt"
  "os"
)

// 下記の「エラー処理」に進んだ場合に main関数を抜けてしまい、
// file.Close() が実行されないこと起こりうる。
// そこで main() を抜ける際に必ず実行したい処理は defer を添えて記述する。

func main() {
  file, err := os.Open("./xxxyyyzzz.txt")
  if err != nil {
    // エラー処理
  }
  defer file.Close()

  // 以降、正常処理を書く
}

 
 

2.12

 
配列の範囲外アクセスや 0除算が起きるとパニックが発生する。
いわゆる "例外" であり、Go言語では recover() を使って try-catch 相当を行う。
 

panic1.go
package main

import (
  "errors"
  "fmt"
  "log"
)

func div(i, j int) (result int, err error) {
  if i == 0 {
    err = errors.New("divided by Zero")
    panic(err) // 意図的に panic を発生させる
  } else {
    result = i / j
  }
  return
}

func main() {

  defer func() {
    // main() を終了する際に本関数が呼び出される.
    err := recover()

    if err != nil { // panic が発生したので、ここで err が存在することになる
      log.Fatal(err) // エラーがあればエラーログを出す
    } else {
      fmt.Println("[DEBUG] OK") // 問題が起きなかった場合
    }
  }()

  n, _ := div(10, 0) // div の中で意図的に panic を発生させる
  fmt.Println(n)
}

 

recover1.go
package main

import (
  "fmt"
  "log"
)

func div(i, j int) (result int) {
  result = i / j
  return
}

func main() {

  defer func() {
    // main() を終了する際に本関数が呼び出される.
    err := recover()

    if err != nil {
      log.Fatal(err) // エラーがあればエラーログを出す
    } else {
      fmt.Println("[DEBUG] OK") // 問題が起きなかった場合
    }
  }()

  n := div(10, 0)
  fmt.Println(n)
}

 

recover2.go
package main

import (
  "fmt"
  "log"
)

func div(i, j int) (result int) {
  result = i / j
  return
}

func main() {

  defer func() {
    // main() を終了する際に本関数が呼び出される.
    err := recover()

    if err != nil {
      log.Fatal(err) // エラーがあればエラーログを出す
    } else {
      fmt.Println("[DEBUG] OK") // 問題が起きなかった場合
    }
  }()

  // 範囲外アクセスを発生させる
  a := []int{1, 2, 3}
  fmt.Println(a[10])
}

 
 

2.2

 
Go の組み込み型

説明
int8 8 ビット符号なし整数
uint16 16 ビット符号なし整数
uint32 32 ビット符号なし整数
uint64 64 ビット符号なし整数
int8 8 ビット符号あり整数
int16 16 ビット符号あり整数
int32 32 ビット符号あり整数
int64 64 ビット符号あり整数
float32 32 ビット浮動小数
float64 64 ビット浮動小数
complex64 64 ビット複素数
complex128 128 ビット複素数
byte uint8 のエイリアス
rune Unicode のコードポイント
uint 32 か 64 ビットの符号なし整数
int 32 か 64 ビットの符号なし整数
uintptr ポインタ値用符号なし整数
error エラーを表わすインタフェース

 

var.go
package main

import (
  f "fmt"
  _ "gosample"
  . "strings"
)

func main() {

  // 変数の宣言は var で始める
  var message string = "hello world"
  f.Println(ToUpper(message))
}

 

var_2.go
package main

import (
  f "fmt"
  _ "gosample"
  . "strings"
)

func main() {

  // 変数の宣言は次の方法でも良い。
  // これは「var message string = "hello world"」と書くことと同じことである
  message := "hello world"

  f.Println(ToUpper(message))
}

 
 

2.3

 

const.go
package main

import (
  f "fmt"
  _ "gosample"
  . "strings"
)

func main() {

  // 定数の宣言は var の代わりに const で始める
  const message string = "hello world"
  f.Println(ToUpper(message))
}

 
 

2.4

 

  • if 文の () は不要である。(書いても良い)
  • else, else if は } と同じ行に書くこと
  • for 文の () は書いてはいけない

 

break.go
package main

// break を使うことで for ループを脱出することができる
// (下記は 5 で割り切れる場合に break する)
import (
  "fmt"
)

func main() {
  n := 1
  for n < 10 {
    fmt.Printf("n = %d\n", n)
    if (n % 5) == 0 {
      break
    }
    n++
  }
}

 

continue.go
package main

// continue もある
// (下記は 5 で割り切れる場合に continue に進み、ログ表示しない)
import (
  "fmt"
)

func main() {
  for n := 0; n < 10; n++ {
    if (n % 5) == 0 {
      continue
    }
    fmt.Printf("n = %d\n", n)
  }
}

 

fallthrough.go
package main

// switch - case
// break を書かなくても break するので、
// fallthrough を使うことで C言語の switch - case のようになる
import (
  "fmt"
)

func main() {
  for n := 0; n <= 20; n++ {
    switch n {
    case 20:
      fmt.Printf("[%d] %% 20 == 0\n", n)
      fallthrough
    case 5, 10:
      fmt.Printf("[%d] %%  5 == 0\n", n)
      fallthrough
    case 2:
      fmt.Printf("[%d] %%  2 == 0\n", n)
    default:
    }
  }
}

 

for1.go
package main

import (
  "fmt"
)

func main() {
  a, b := 10, 100
  for i := a; i < b; i++ {
    fmt.Printf("i = %d\n", i)
  }
}

 

for2.go
package main

// C言語の while のように継続条件のみを書くことも可能である
import (
  "fmt"
)

func main() {
  n := 0
  for n < 10 {
    fmt.Printf("n = %d\n", n)
    n++
  }
}

 

for3.go
package main

// 無限ループを書くときは, for の引数を空っぽにすれば良い
import (
  "fmt"
)

func main() {
  n := 0
  for {
    fmt.Println(n)
    n++
  }
}

 

if.go
package main

import (
  "fmt"
)

func main() {
  a, b := 10, 100
  if a > b {
    fmt.Println(" a is larger than b")
  } else if a < b {
    fmt.Println(" a is smaller than b")
  } else {
    fmt.Println("a is Equals b")
  }
}

 

switch1.go
package main

// switch - case
// break を書かなくても break する
import (
  "fmt"
)

func main() {
  for n := 0; n <= 20; n++ {
    switch n {
    case 20:
      fmt.Printf("[%d] %% 20 == 0\n", n)
    case 5, 10:
      fmt.Printf("[%d] %%  5 == 0\n", n)
    case 2:
      fmt.Printf("[%d] %%  2 == 0\n", n)
    default:
    }
  }
}

 

switch2.go
package main

// switch - case
// case で条件分岐をさせることも可能である
import (
  "fmt"
)

func main() {
  for n := 0; n <= 20; n++ {
    switch { // switch には何も書かない
    case (n % 20) == 0: // case に条件を書く.
      fmt.Printf("[%d] %% 20 == 0\n", n)
    case ((n % 10) == 0): //((n%10)==0) というように括弧でくくっても良い
      fmt.Printf("[%d] %% 10 == 0\n", n)
    case (n % 5) == 0:
      fmt.Printf("[%d] %%  5 == 0\n", n)
    case (n % 2) == 0:
      fmt.Printf("[%d] %%  2 == 0\n", n)
    default:
    }
  }
}

 
 

2.5

 

func1.go
package main

import (
  "fmt"
)

// 引数には int が2つ続くので swap( i, j int, s strings) という書き方でも OK.
// 戻り値は引数の右側で定義している
func swap(i, j int) (int, int, string) {
  return j, i, "returned by swap()"
}

func main() {
  x, y, s := 3, 4, ""
  x, y, s = swap(x, y)
  fmt.Printf("(x,y)=(%d,%d) %s\n", x, y, s)
}

 

func2.go
package main

import (
  "errors"
  "fmt"
  "log"
)

func div(i, j int) (int, error) {
  if j == 0 {
    return 0, errors.New("divided by Zero")
  }
  return i / j, nil
}

func main() {
  n, err := div(10, 0)
  if err != nil {
    // エラーを出力して終了する
    log.Fatal(err)
  }
  fmt.Println(n)
}

 

func3.go
package main

import (
  "errors"
  "fmt"
  "log"
)

// 戻り値に変数名を割り当てることで戻り値が分かり易くなる。
// また、初期化漏れも減らせる。
func div(i, j int) (result int, err error) {
  if j == 0 {
    err = errors.New("divided by Zero")
    return // return 0, err と同じ
  }
  result = i / j
  return // return result, nil と同じ
}

func main() {
  i := 10
  for i >= 0 {
    n, err := div(10, i)
    if err != nil {
      // エラーを出力して終了する
      log.Fatal(err)
    }
    fmt.Println(n)
    i--
  }
}

 

func_vargs.go
package main

import (
  "fmt"
)

// 可変長引数を受け取る
func sum(nums ...int) (result int, err error) {
  for _, n := range nums {
    // 添字は受け取らずに値のみ取り出す
    result += n
  }
  return // return result, nil と同じ
}

func main() {
  fmt.Println(sum(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
}

 
 

2.6

 
Go言語では関数もオブジェクトのため、

  • 関数を変数に代入すること
  • 関数を引数として代入すること

も可能である。
 

funcliteral1.go
package main

import (
  "fmt"
)

func main() {
  func(i, j int) {
    fmt.Println(i + j)
  }(2, 4)
}

 

funcliteral2.go
package main

import (
  "fmt"
)

func main() {
  // 変数 sum を (i int,j int) を引数に持つ関数型として宣言する
  var sum func(i, j int) = func(i, j int) {
    fmt.Println(i + j)
  }

  sum(2, 4)
}

 
 

2.7

 

array1.go
package main

import (
  "fmt"
)

func main() {

  // 文字列(string)を 4個持つ固定配列を定義する
  var arr [4]string

  arr[0] = "a"
  arr[1] = "bb"
  arr[2] = "ccc"
  arr[3] = "dddd"
  for i := 0; i < len(arr); i++ {
    fmt.Printf("arr[%d] = %s\n", i, arr[i])
  }

  arr[0] = "AAAA"
  arr[1] = "BBB"
  arr[2] = "CC"
  arr[3] = "D"
  for i := 0; i < len(arr); i++ {
    fmt.Printf("arr[%d] = %s\n", i, arr[i])
  }
}

 

array2.go
package main

import (
  "fmt"
)

func main() {

  // 次のように文字列(string)を 4個持つ固定配列を定義することも可能である
  arr := [4]string{"a", "bb", "ccc", "dddd"}
  for i := 0; i < len(arr); i++ {
    fmt.Printf("arr[%d] = %s\n", i, arr[i])
  }

  // 以下のように書くこともできる
  arr2 := [...]string{"A", "BB", "CCC", "DDDD"}
  for i := 0; i < len(arr2); i++ {
    fmt.Printf("arr2[%d] = %s\n", i, arr2[i])
  }
}

 

array3.go
package main

import (
  "fmt"
)

func printArray(array [4]string) {
  fmt.Println(array)    // => [a bb ccc dddd]
  array[1] = "--------" // ここで代入しても呼び出し元の配列は書き換えしない
}

func main() {

  // 次のように文字列(string)を 4個持つ固定配列を定義することも可能である
  arr := [4]string{"a", "bb", "ccc", "dddd"}
  for i := 0; i < len(arr); i++ {
    fmt.Printf("arr[%d] = %s\n", i, arr[i])
  }

  // 以下のように書くこともできる
  arr2 := [...]string{"A", "BB", "CCC", "DDDD", "EEEEE"}
  for i := 0; i < len(arr2); i++ {
    fmt.Printf("arr2[%d] = %s\n", i, arr2[i])
  }

  printArray(arr) // 引数は「値渡し = コピー」となる
  //  printArray( arr2 )  // printArray関数の引数と配列の要素数が異なるためコンパイルエラーとなる
}

 

array4.go
package main

import (
  "fmt"
)

func main() {

  // 文字列(string)を 4個持つ固定配列を定義する
  var arr [4]string

  arr[0] = "a"
  arr[1] = "bb"
  arr[2] = "ccc"
  arr[3] = "dddd"
  for i := 0; i < len(arr); i++ {
    fmt.Printf("arr[%d] = %s\n", i, arr[i])
  }

  // 添字を : で区切ることで範囲を指定して取り出すことができる
  fmt.Println(arr[1:3]) //=> [bb ccc]
  fmt.Println(arr[:1])  //=> [a]
  fmt.Println(arr[1:])  //=> [bb ccc dddd]
}

 

array_range.go
package main

import (
  "fmt"
)

func main() {

  // 次のように文字列(string)を 4個持つ固定配列を定義することも可能である
  arr := [4]string{"a", "bb", "ccc", "dddd"}
  for i := 0; i < len(arr); i++ {
    fmt.Printf("arr[%d] = %s\n", i, arr[i])
  }

  // range を使うことで添字と要素を同時に取り出すことができる
  for idx, val := range arr {
    fmt.Printf("arr[%d] = %s\n", idx, val)
  }
}

 
 

2.8

 
スライス=可変長配列のことである。
変数の宣言方法は、配列とほぼ同じであり要素数を定義しない点が異なるのみである。

 

slice1.go
package main

import (
  "fmt"
)

func main() {
  var s []string

  // スライスに要素を追加する場合は append() を使う.
  // 以下のように新規インデックスに値を設定することはできない.
  // s[0] = "BB"
  s = append(s, "a")
  s = append(s, "bb")
  s = append(s, "ccc")
  s = append(s, "dddd")
  fmt.Println(s) //=> [a bb ccc dddd]

  // この時点で領域を確保した [0]〜[3] には代入ができる。
  s[0] = "AAAA"
  s[1] = "BBB"
  fmt.Println(s) //=> [AAAA BBB ccc dddd]
}

 

slice2.go
package main

import (
  "fmt"
)

func main() {
  // スライスについて、初期化しつつ領域を確保する場合は次のようにする
  s := []string{"a", "bb", "ccc", "dddd"}

  fmt.Println(s) //=> [a bb ccc dddd]

  // この時点で領域を確保した [0]〜[3] には代入ができる。
  s[0] = "AAAA"
  s[1] = "BBB"
  fmt.Println(s) //=> [AAAA BBB ccc dddd]
}

 

slice4.go
package main

import (
  "fmt"
)

func main() {

  var s []string

  // スライスに要素を追加する場合は append() を使う.
  // 以下のように新規インデックスに値を設定することはできない.
  // s[0] = "BB"
  s = append(s, "a")
  s = append(s, "bb")
  s = append(s, "ccc")
  s = append(s, "dddd")
  fmt.Println(s) //=> [a bb ccc dddd]

  // 添字を : で区切ることで範囲を指定して取り出すことができる
  fmt.Println(s[1:3]) //=> [bb ccc]
  fmt.Println(s[:1])  //=> [a]
  fmt.Println(s[1:])  //=> [bb ccc dddd]
}

 

slice_range.go
package main

import (
  "fmt"
)

func main() {
  var s []string

  s = append(s, "a")
  s = append(s, "bb")
  s = append(s, "ccc")
  s = append(s, "dddd")

  fmt.Println(s) //=> [a bb ccc dddd]

  // range を使うことで, 添字と要素を同時に取り出すことができる
  for idx, val := range s {
    fmt.Printf("s[%d] = %s\n", idx, val)
  }
}

 
 

2.9

 

map1.go
package main

import (
  "fmt"
)

func main() {
  // 添字に int, 値に文字列を持つ map を作成する
  // 「map[int]string」の部分が型を表している
  var month map[int]string = map[int]string{}

  month[1] = "Jan"
  month[2] = "Feb"
  month[3] = "Mar"
  month[4] = "Apr"
  month[5] = "May"
  month[6] = "Jun"
  month[7] = "Jul"
  month[8] = "Aug"
  month[9] = "Sep"
  month[10] = "Oct"
  month[11] = "Nov"
  month[12] = "Dec"

  fmt.Println(month)
  //=> map[1:Jan 2:Feb 3:Mar 4:Apr 8:Aug 12:Dec 5:May 6:Jun 7:Jul 9:Sep 10:Oct 11:Nov]

  name := month[1] //=> Jan
  fmt.Println(name)

  // 2つ目の引数には値の存在有無を示す値が格納されている
  var ret bool
  name, ret = month[1] //=> Jan true
  fmt.Println(name, ret)

  // マップからデータを消す
  delete(month, 1)

  // 2つ目の引数には値の存在有無を示す値が格納されている
  name, ret = month[1] //=>  false
  fmt.Println(name, ret)

  name, ret = month[13] //=>  false
  fmt.Println(name, ret)
}

 

map2.go
package main

import (
  "fmt"
)

func main() {
  // 添字に int, 値に文字列を持つ map を作成する
  // 「map[int]string」の部分が型を表している
  month := map[int]string{
    1:  "Jan",
    2:  "Feb",
    3:  "Mar",
    4:  "Apr",
    5:  "May",
    6:  "Jun",
    7:  "Jul",
    8:  "Aug",
    9:  "Sep",
    10: "Oct",
    11: "Nov",
    12: "Dec", // カンマが必要!!
  }
  fmt.Println(month)
  //=> map[1:Jan 2:Feb 3:Mar 4:Apr 8:Aug 12:Dec 5:May 6:Jun 7:Jul 9:Sep 10:Oct 11:Nov]

  // 2つ目の引数には値の存在有無を示す値が格納されている
  var ret bool
  var name string
  name, ret = month[1] //=> Jan true
  fmt.Println(name, ret)

  // マップからデータを消す
  delete(month, 1)

  // 2つ目の引数には値の存在有無を示す値が格納されている
  name, ret = month[1] //=>  false
  fmt.Println(name, ret)

  name, ret = month[13] //=>  false
  fmt.Println(name, ret)
}

 
 

3.1

 
type とは、C言語の typedef を流用して型を厳密にするテクニックの模様。

下記の場合だと ProcessTask( id, priority int ) 関数の引数は共に int なので、
呼び出し元で誤って ProcessTask( priority, id ) という順で関数呼び出しをしてしまうかも知れない。

そこで、このような誤使用をコンパイル段階で見付け出す方法として type がある。

type を使うことで id と priority をそれぞれ別の型として扱い、ProcessTask() の引数指定を間違えないようにしている。
 

type.go
package main

import (
  "fmt"
)

// ID型を定義する
type ID int

// Priority型を定義する
type Priority int

func ProcessTask(id ID, priority Priority) {
  fmt.Printf("id: %d\n", id)
  fmt.Printf("priority: %d\n", priority)
}

func main() {
  var id ID = 3
  var priority Priority = 5
  ProcessTask(id, priority)
}

 
 

3.10

 

cast1.go
package main

import (
  "fmt"
)

func main() {

  var i uint8 = 3
  var j uint32 = uint32(i) // uint8 -> uint32
  fmt.Println(j)           //=> 3

  var s string = "abc"
  var b []byte = []byte(s) // string -> []byte
  fmt.Println(b)           //=> 97 98 99

  // cannot convert "a" (type string) to type int
  // (パニックが発生する)
  // a := int("a")
}

 
 

3.11

 
型の検出方法として「Type Assertion」「Type Switch」の2通りの方法がある。

とりあえず、「Type Switch」の方法のみで十分かと思うので、この方法を載せておく。(コードは 3.7 と同じコードである)

もしも「Type Assertion」による検査が必要になった場合は下記ページを見るなどすれば良い。
急いで学ぶGo lang#6 インターフェイス | DevelopersIO

 

empty_interface2.go
package main

import (
  "fmt"
  _ "strconv"
)

// voidポインタ変数に相当する VP を定義する
var VP interface{}

// voidポインタを関数の引数にする
func typeChecker(vpArg interface{}) {
  switch value := vpArg.(type) {
  case int:
    fmt.Printf("int value:%d\n", value)
  case string:
    fmt.Printf("string value:%s\n", value)
  default:
    fmt.Println("other")
  }
}

func main() {

  s := "aaaaa"
  VP = s
  typeChecker(VP)

  i := 1
  VP = i
  typeChecker(i)

}

 
 

3.2

 
構造体であるが、以下の点が C言語とは異なる。

構造体の各フィールドのうち、スコープは次のようになる。

  • 大文字で始まる名称は public である
  • 小文字で始まる名称は パッケージ内限定公開 である

従って本プログラムの場合は、done メンバはパッケージ内限定公開となる。
(その他 ID, Detail は公開メンバとなる)


 

struct1.go
package main

import (
  "fmt"
)

// Task構造体を定義する
type Task struct {
  ID     int
  Detail string
  done   bool
}

func main() {
  var task Task = Task{
    Detail: "buy the milk",
    done:   true,
    ID:     1,
  }

  fmt.Println(task.ID)
  fmt.Println(task.Detail)
  fmt.Println(task.done)
}

 

struct2.go
package main

import (
  "fmt"
)

// Task構造体を定義する
type Task struct {
  ID     int
  Detail string
  done   bool
}

func main() {
  // 構造体のメンバ名を指定しない場合は順番を決め打ちにすることで設定が可能である
  var task Task = Task{
    1,              // ID
    "buy the milk", // Detail
    true,           // done
  }

  fmt.Println(task.ID)
  fmt.Println(task.Detail)
  fmt.Println(task.done)
}

 

struct3.go
package main

import (
  "fmt"
)

// Task構造体を定義する
type Task struct {
  ID     int
  Detail string
  done   bool
}

func main() {
  // 構造体データのうち、初期値を設定しなかったメンバ変数は 0 となる
  var task Task = Task{
    ID: 1,
  }

  fmt.Println(task.ID)     //=> 1
  fmt.Println(task.Detail) //=>
  fmt.Println(task.done)   //=> false
}

 
 

3.3

 
構造体の名前の前に & を付けることでポインタとして扱われる。

task := &Task{ done: false }

 
あるいは、new() を使うことでポインタを返すことも可能になる。

var task * Task = new(Task)

 

new.go
package main

import (
  "fmt"
)

// Task構造体を定義する
type Task struct {
  ID     int
  Detail string
  done   bool
}

func Finish(task *Task) {
  task.done = true
}

func main() {
  // new によりアドレスが返される
  var task *Task = new(Task)

  task.done = false

  fmt.Println(task.done) //=> false

  Finish(task)

  fmt.Println(task.done) //=> true
}

 

struct_pointer1.go
package main

import (
  "fmt"
)

// Task構造体を定義する
type Task struct {
  ID     int
  Detail string
  done   bool
}

func Finish(task *Task) {
  task.done = true
}

func main() {
  // 構造体のメンバ名を指定しない場合は順番を決め打ちにすることで設定が可能である
  task := &Task{done: false}

  fmt.Println(task.done) //=> false

  Finish(task)

  fmt.Println(task.done) //=> true
}

 
 

3.4

 
Go には構造体のコンストラクタにあたる構文が無い。
代わりに 'New' で始まる関数を定義し、その内部で構造体を生成するのが通例となっている。
 

constructor.go
package main

import (
  "fmt"
)

// Task構造体を定義する
type Task struct {
  ID     int
  Detail string
  done   bool
}

// 引数「int, string」, 戻り値「Task へのポインタ」
func NewTask(id int, detail string) *Task {

  // Task 型インスタンスへのポインタを返す
  task := &Task{
    ID:     id,
    Detail: detail,
    done:   false,
  }
  return task
}

func main() {
  task := NewTask(1, "buy the milk")
  fmt.Println(task) //=> &{1 buy the milk false}
}

 
 

3.5

 
書籍の内容はこの辺りから説明が乱暴になっており、言及がない箇所が多々ある*1

method1.go について。
下記 Task構造体の「Detail string」に Stringメソッドを紐付けする

type Task struct {
    ID      int
    Detail  string
    done    bool
}

 
method2.go について。
Task 構造体に Finishメソッドを定義する。
 

method1.go
package main

import (
  "fmt"
)

// Task構造体を定義する
type Task struct {
  ID     int
  Detail string // この string 型メンバにメソッドを紐付ける
  done   bool
}

// 引数「int, string」, 戻り値「Task へのポインタ」
func NewTask(id int, detail string) *Task {

  // Task 型インスタンスへのポインタを返す
  task := &Task{
    ID:     id,
    Detail: detail,
    done:   false,
  }
  return task
}

// メソッドの書式は以下の通り。
// -----------------------------------------
// func (レシーバ  型) 関数名 (引数) 戻り値
// -----------------------------------------
func (task Task) String() string {
  str := fmt.Sprintf("%d) %s", task.ID, task.Detail)
  return str
}

func main() {
  task := NewTask(1, "buy the milk")
  fmt.Println(task) //=> &{1 buy the milk false}
}

 

method2.go
package main

import (
  "fmt"
)

// Task構造体を定義する
type Task struct {
  ID     int
  Detail string
  done   bool
}

// 引数「int, string」, 戻り値「Task へのポインタ」
func NewTask(id int, detail string) *Task {

  // Task 型インスタンスへのポインタを返す
  task := &Task{
    ID:     id,
    Detail: detail,
    done:   false,
  }
  return task
}

// メソッドの書式は以下の通り。
// -----------------------------------------
// func (レシーバ  型) 関数名 (引数) 戻り値
// -----------------------------------------
func (task *Task) Finish() {
  task.done = true
}

func main() {
  task := NewTask(1, "buy the milk")
  task.Finish()
  fmt.Println(task) //=> &{1 buy the milk true}
}

 
 

3.6

 
「WEB+DB PRESS Vol.82」の記載内容では "インタフェース" を理解することは到底できないので、
急いで学ぶGo lang#6 インターフェイス | DevelopersIO に切り替えて学習した.

type 型名 interface {
    メソッド名 (引数) 戻り値の型
    .
    .
    .
}

 
まず Car インタフェースを定義する

type Car interface {
    run(int) string
    stop()
}

 
(全く Car インタフェースとは無関係な) MyCar 構造体を定義する
この時点では、Car インタフェースとは紐付いてはいない。

type MyCar struct {
    name  string
    speed int
}

 
MyCar 構造体のメソッドとして「run(int) string」「stop()」を定義する
「run(int) string」「stop()」という2つの関数は Car インタフェースで定義した関数型と同じであり、
この 2つの関数を MyCar 構造体のメソッドとして実装することで Car インタフェースを使ったとみなされる。

func (u * MyCar) run(speed int) string {
    u.speed = speed
    return strconv.Itoa(speed) + "kmで走ります"
}

func (u * MyCar) stop() {
    fmt.Println("停止します")
    u.speed = 0
}

以下のように MyCar構造体を Car 型として扱うことができる。

myCar := &MyCar{ name: "マイカー", speed: 0 }

var i Car = myCar
fmt.Println(i.run(50))  //=> 50kmで走ります
i.stop()                //=> 停止します

 

interface1.go
package main

import (
  "fmt"
  "strconv"
)

// Carインタフェースを定義する
type Car interface {
  run(int) string // 関数名(引数) 戻り値
  stop()          // 関数名()
}

// MyCar構造体を定義する
type MyCar struct {
  name  string
  speed int
}

// MyCar構造体にメソッドを持たせる。
// このメソッドが、Carインタフェースで定義したメソッドを満していれば、
// Car 型として使えるということである.
func (u *MyCar) run(speed int) string {
  u.speed = speed
  return strconv.Itoa(speed) + "kmで走ります"
}

func (u *MyCar) stop() {
  fmt.Println("停止します")
  u.speed = 0
}

func main() {
  myCar := &MyCar{name: "マイカー", speed: 0}

  var i Car = myCar
  fmt.Println(i.run(50)) //=> 50kmで走ります
  i.stop()               //=> 停止します
}

 
 

3.7

 
「WEB+DB PRESS Vol.82」の記載内容では "インタフェース" を理解することは到底できないので、
急いで学ぶGo lang#6 インターフェイス | DevelopersIO
Go言語 - 空インターフェースと型アサーション - 覚えたら書く
に切り替えて学習した.

空インタフェースを使うことで、 C言語の void ポインタと同じことを実現することができる。

voidポインタ変数に相当する VP を定義する

var VP interface{}

 
voidポインタを関数の引数にする

func typeChecker( vp interface{} ) {
}

 

empty_interface1.go
package main

import (
  _ "fmt"
  _ "strconv"
)

// voidポインタ変数に相当する VP を定義する
var VP interface{}

func main() {
  s := retString
  VP = s
}

 

empty_interface2.go
package main

import (
  "fmt"
  _ "strconv"
)

// voidポインタ変数に相当する VP を定義する
var VP interface{}

// voidポインタを関数の引数にする
func typeChecker(vpArg interface{}) {
  switch value := vpArg.(type) {
  case int:
    fmt.Printf("int value:%d\n", value)
  case string:
    fmt.Printf("string value:%s\n", value)
  default:
    fmt.Println("other")
  }
}

func main() {

  s := "aaaaa"
  VP = s
  typeChecker(VP)

  i := 1
  VP = i
  typeChecker(i)

}

 
 

3.8

 
前述の Task 構造体に User構造体を組み込む。
というか、単に C言語で書くと以下のようなことをしているだけである。

typedef struct Task {
    int     ID;
    string  Detail;
    bool    done;
    User  * pUser;
};

typedef struct User {
    string  FirstName;
    string  LastName;
};

 

embStruct1.go
package main

import (
  "fmt"
)

// Task構造体を定義する
type Task struct {
  ID     int
  Detail string
  done   bool
  *User  // 型のみを定義する
}

// User構造体を定義する
type User struct {
  FirstName string
  LastName  string
}

// User構造体に FullNameメソッドを追加する
func (u *User) FullName() string {
  fullname := fmt.Sprintf("%s %s", u.FirstName, u.LastName)
  return fullname
}

// User構造体のインスタンスを確保する
func NewUser(firstName, lastName string) *User {
  return &User{
    FirstName: firstName,
    LastName:  lastName,
  }
}

// 引数「int, string」, 戻り値「Task へのポインタ」
func NewTask(id int, detail string, firstName, lastName string) *Task {

  // Task 型インスタンスへのポインタを返す
  task := &Task{
    ID:     id,
    Detail: detail,
    done:   false,
    User:   NewUser(firstName, lastName),
  }
  return task
}

// メソッドの書式は以下の通り。
// -----------------------------------------
// func (レシーバ  型) 関数名 (引数) 戻り値
// -----------------------------------------
func (task *Task) Finish() {
  task.done = true
}

func main() {

  task := NewTask(1, "buy the milk", "Jxck", "Daniel")

  // TaskにUserのフィールドが埋め込まれている
  fmt.Println(task.FirstName) //=> Jxck
  fmt.Println(task.LastName)  //=> Daniel

  // TaskにUserのメソッドが埋め込まれている
  fmt.Println(task.FullName()) //=> Jxck Daniel

  // Taskから埋め込まれたUser自体にもアクセス可能
  fmt.Println(task.User) //=> &{Jxck Daniel}
}

 
 

5.1

 
ゴルーチン(Goroutine) という軽量スレッドで並列処理が可能になる。
具体的には、go構文を使って任意の関数を別のゴルーチンとして動かす。

以下、次のように実装した。

  • httpGetS.go は並列処理をしない場合
  • httpGetP*.go は並列処理をした場合
    • httpGetP1.go は並列処理終了後に 1秒間だけ待機して全スレッドの終了を待つ
    • httpGetP2.go は並列処理終了後に Wait により全スレッドの終了を待つ

 

httpGetP1.go
// 並列処理により 3つのサイトにアクセスして応答を得る
package main

import (
  "fmt"
  "log"
  "net/http"
  "time"
)

func main() {
  urls := []string{
    "https://www.yahoo.co.jp/",
    "https://www.msn.com/ja-jp",
    "https://github.com/",
  }

  for _, url := range urls {
    go func(url string) {
      res, err := http.Get(url)
      if err != nil {
        log.Fatal(err)
      }
      defer res.Body.Close()
      fmt.Println(url, res.Status)
    }(url)
  }
  // main()が終らないように, 1秒間だけ待機する
  time.Sleep(time.Second)
}

 

httpGetP2.go
// 並列処理により 3つのサイトにアクセスして応答を得る
package main

import (
  "fmt"
  "log"
  "net/http"
  "sync"
)

func main() {
  wait := new(sync.WaitGroup)

  urls := []string{
    "https://www.yahoo.co.jp/",
    "https://www.msn.com/ja-jp",
    "https://github.com/",
  }

  for _, url := range urls {
    wait.Add(1)
    go func(url string) {
      res, err := http.Get(url)
      if err != nil {
        log.Fatal(err)
      }
      defer res.Body.Close()
      fmt.Println(url, res.Status)
      wait.Done()
    }(url)
  }
  // main()が終らないように, 1秒間だけ待機する
  wait.Wait()
}

 

httpGetS.go
// 並列処理をせずに 3つのサイトにアクセスして応答を得る
package main

import (
  "fmt"
  "log"
  "net/http"
)

func main() {
  urls := []string{
    "https://www.yahoo.co.jp/",
    "https://www.msn.com/ja-jp",
    "https://github.com/",
  }

  for _, url := range urls {
    res, err := http.Get(url)
    if err != nil {
      log.Fatal(err)
    }
    defer res.Body.Close()
    fmt.Println(url, res.Status)
  }
}

 
 

5.2

 
チャネル(channel)というメッージ通信の機構を使って,スレッド間のデータ送受信ができる。
今回は mainスレッドと各 http get ゴルーチンの間でチャネルを使うパターンである。
f:id:dnkrnka:20180902144531p:plain:w500

channel1.go
main() 内部の匿名関数でチャネルを使うパターン。

channel2.go
固有関数からチャネルを使うパターン。
 

channel1.go
// 並列処理により 3つのサイトにアクセスして応答を得る
package main

import (
  "fmt"
  "log"
  "net/http"
)

func main() {
  urls := []string{
    "https://www.yahoo.co.jp/",
    "https://www.msn.com/ja-jp",
    "https://github.com/",
  }

  // stringを扱うチャネルを作成する
  statusChan := make(chan string)

  for _, url := range urls {
    go func(url string) {
      res, err := http.Get(url)
      if err != nil {
        log.Fatal(err)
      }
      defer res.Body.Close()
      statusChan <- res.Status // チャネルに res.Status を書き込む
    }(url)
  }

  for i := 0; i < len(urls); i++ {
    fmt.Println(<-statusChan)
  }
}

 

channel2.go
// 並列処理により 3つのサイトにアクセスして応答を得る
package main

import (
  "fmt"
  "log"
  "net/http"
)

func getStatus(urls []string) <-chan string {
  // 関数でチャネルを作成する
  statusChan := make(chan string)

  for _, url := range urls {
    go func(url string) {
      res, err := http.Get(url)
      if err != nil {
        log.Fatal(err)
      }
      defer res.Body.Close()
      statusChan <- res.Status // チャネルに res.Status を書き込む
    }(url)
  }
  return statusChan // チャネルを返す
}

func main() {
  urls := []string{
    "https://www.yahoo.co.jp/",
    "https://www.msn.com/ja-jp",
    "https://github.com/",
  }

  statusChan := getStatus(urls)

  for i := 0; i < len(urls); i++ {
    fmt.Println(<-statusChan)
  }
}

 

*1:メソッドの書式にすら触れていないので、別途 Web で調べた