Goの文法学習 ( 自分用Memoで適宜更新 )
先人たちのブログや技術書を活用して自分用の備忘録です。 参照した記事は参照に一覧で表示させていただきます。
( 適宜更新 )
Map
mapを作成
make
make(map[<キーの型>]<値の型>)
で mapを生成
- mapから値を取得
package main import ( "fmt" ) func main() { m := make(map[string]int) m["test1"] = 100 m["test2"] = 90 fmt.Println(m["test1"]) fmt.Println(m) } /* 結果 100 map[test1:100 test2:90] */
mapリテラルをつくる
※)リテラル
数値や文字列を直接に記述した定数のこと
package main import ( "fmt" ) func main() { m := map[string]int{ "test1": 100, "test2": 95, "test3": 50, } fmt.Println(m) } /* 結果 map[test1:100 test2:95 test3:50] */
値チェック
valueが存在するかを確認する。結果は true
, false
が返る
package main import ( "fmt" ) func main() { m := map[string]int{ "test1": 100, "test2": 95, "test3": 50, } val, chk := m["test1"] fmt.Println(val, chk) val, chk = m["test100"] fmt.Println(val, chk) } /* 結果 100 true 0 false */
key/valueを削除
package main import ( "fmt" ) func main() { m := map[string]int{ "test1": 100, "test2": 95, "test3": 50, } fmt.Println(m) delete(m, "test1") fmt.Println(m) } /* 結果 map[test1:100 test2:95 test3:50] map[test2:95 test3:50] */
key/valueの全取得と要素数の取得
package main import ( "fmt" ) func main() { m := map[string]int{ "test1": 100, "test2": 95, "test3": 50, } for key, val := range m { fmt.Println(key, val) } fmt.Println(len(m)) } /* 結果 test1 100 test2 95 test3 50 3 */
error
package main import ( "fmt" ) func main() { if err := doError();err != nil { fmt.Println(err) //return } fmt.Println("end of code") } func doError() error { message := "error occurred!!!" return fmt.Errorf(message) } /* 結果 ( return無し ) error occurred!!! end of code */ /* 結果 ( return有り ) error occurred!!! */
Slice (スライス)
- 特徴
- 可変長配列
- 配列全体のポインタ(ptr) / 配列の長さ(len) / 配列の容量(cap)からなるデータ構造
make()関数
make(型, 要素数, 容量)
で定義する。- スライスが初期化され、デフォルトでは0が格納される
- 容量は省略可能。省略すると
要素数
と容量
は同じになる - 組み込み関数
package main import ( "fmt" ) func main() { test1 := make([]int, 5, 10) fmt.Println(test1) fmt.Println(len(test1)) fmt.Println(cap(test1)) fmt.Println() test2 := make([]int, 10) fmt.Println(test2) fmt.Println(len(test2)) fmt.Println(cap(test2)) } /* 結果 [0 0 0 0 0] 5 10 [0 0 0 0 0 0 0 0 0 0] 10 10 */
append()関数
append(slice, val1, val2, val3, ...)
と使うslice
の末尾に val1, val2 ... と追加されていく- sliceの要素数は自動的に増える
- 容量に達すると、容量の2倍を確保してくれる
- 配列のメモリアドレスは容量が確保されると変わる
copy関数
copy(コピー先, コピー元)
として使う
package main import ( "fmt" ) func main() { test1 := []int{1, 2, 3, 4, 5} test2 := make([]int, 5) copy(test2, test1) fmt.Println(test2) } /* 結果 [1 2 3 4 5] */
構造体/JSON
構造体の初期化
package main import ( "fmt" ) type Person struct { Name string Age int } func main() { var p1 Person p1.Name = "yhidetoshi" p1.Age = 20 fmt.Println("p1: "+p1.Name, p1.Age) p2 := Person{"yhidetoshi", 21} fmt.Println("p2: "+p2.Name, p2.Age) p3 := Person{Name: "yhidetoshi", Age: 22} fmt.Println("p3: "+p3.Name, p3.Age) p4 := &Person{Name: "yhidetoshi", Age: 23} fmt.Println("p4: "+p4.Name, p4.Age) p5 := new(Person) p5.Name = "yhidetoshi" p5.Age = 24 fmt.Println("p5: "+p5.Name, p5.Age) p6 := &Person{} p6.Name = "yhidetoshi" p6.Age = 25 fmt.Println("p6: "+p6.Name, p6.Age) } /* 結果 p1: yhidetoshi 20 p2: yhidetoshi 21 p3: yhidetoshi 22 p4: yhidetoshi 23 p5: yhidetoshi 24 p6: yhidetoshi 25 */
構造体配列
フェレットを飼っている人には見やすいかもしれません。 フェレットの情報を保持した構造体を配列にして、3匹のフェレット情報を格納した例です。
package main import "fmt" type Ferret struct { Age int Color string Farm string } type Ferrets []Ferret func main() { maru := Ferret{ Age: 4, Color: "blackself", Farm: "furFarm", } hana := Ferret{ Age: 6, Color: "sable", Farm: "ruby", } natu := Ferret{ Age: 3, Color: "butterscotch", Farm: "mountainDew", } var ferrets Ferrets ferrets = append(ferrets, maru) ferrets = append(ferrets, hana) ferrets = append(ferrets, natu) fmt.Println(ferrets) } /* [{4 blackself furFarm} {6 sable ruby} {3 butterscotch mountainDew}] */
ネスト構造体
package main import ( "fmt" ) type Ferret struct { Name string Food Food } type Food struct { Name string Volume int } func main() { ferret := Ferret{ Name: "maru", Food: Food{ Name: "zupurimeGrainFreeDiet", Volume: 5, }, } fmt.Println(ferret.Name) fmt.Println(ferret.Food.Name) fmt.Println(ferret.Food.Volume) } /* 実行結果 maru zupurimeGrainFreeDiet 5 */
ネスト構造の実用例
- 別ブログでこのネスト構造体を利用しました。
GuardDutyのjson形式が以下で、 Resource
がネストし、さらに InstanceDetails
がネストしています。
{ "schemaVersion": "2.0", "accountId": "XXX", "region": "ap-northeast-1", "type": "UnauthorizedAccess:EC2/TorRelay", "resource": { "resourceType": "Instance", "instanceDetails": { "instanceId": "i-99999999", "instanceType": "m3.xlarge" } }, "title": "EC2 instance i-99999999 is communicating with Tor Exit node.", "description": "EC2 instance i-99999999 is communicating with IP address 198.51.100.0 on the Tor Anonymizing Proxy network." }
なので、構造体の宣言は以下のようにしました。
- go( 一部抜粋 )
// GuardDutyFindings set guardduty GuardDutyFindingsValue type GuardDutyFindings struct { AccountID string `json:"accountId"` Region string `json:"region"` Type string `json:"type"` Severity json.Number `json:"severity"` Title string `json:"title"` Description string `json:"description"` Resource Resource `json:"resource"` } // Resource set guardduty ResourceValue type Resource struct { ResourceType string `json:"resourceType,omitempty"` UserName string `json:"userName,omitempty"` InstanceDetails InstanceDetails `json:"instanceDetails,,omitempty"` AccessKeyDetails AccessKeyDetails `json:"accessKeyDetails,,omitempty"` } // InstanceDetails set guardduty value type InstanceDetails struct { InstanceID string `json:"instanceId,omitempty"` InstanceType string `json:"instanceType,"` }
Marshalを使う
b, _ := json.Marshal(&ferret) fmt.Println(string(b)) fmt.Println() bmi, _ := json.MarshalIndent(&ferret, "", " ") fmt.Println(string(bmi))
- 実行結果
{"Name":"maru","Food":{"Name":"zupurimeGrainFreeDiet","Volume":5}} { "Name": "maru", "Food": { "Name": "zupurimeGrainFreeDiet", "Volume": 5 } }
Unmarshalを使う
[ { "Name": "maru", "Food": { "Name": "zupurimeGrainFreeDiet", "Volume": 5 } }, { "Name": "hana", "Food": { "Name": "ferretSelectionSenior", "Volume": 3 } }, { "Name": "natu", "Food": { "Name": "totalyComplete", "Volume": 7 } } ]
- main.go
package main import ( "encoding/json" "fmt" "io/ioutil" ) type Ferret struct { Name string Food Food } type Food struct { Name string Volume int } func main() { var ferrets []Ferret bytes, err := ioutil.ReadFile("ferret.json") if err != nil { fmt.Println(err) } errJson := json.Unmarshal(bytes, &ferrets) if errJson != nil { fmt.Println(errJson) } for _, value := range ferrets { fmt.Printf("%s\t%s\t%d\n", value.Name, value.Food.Name, value.Food.Volume) } } /* 実行結果 maru zupurimeGrainFreeDiet 5 hana ferretSelectionSenior 3 natu totalyComplete 7 */
NewDecoderを使う
- ストリームから情報を読み込んだ時はjson.NewDecoderを使う
- http.Getで返ったresp.Bodyなどの io.Reader型に対して使う
req, err := http.NewRequest("GET", "https://api.nature.global/1/devices", nil) if err != nil { fmt.Println("Error") } req.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, err := client.Do(req) if err != nil { fmt.Println("Error request") } err = json.NewDecoder(resp.Body).Decode(&devices) if err != nil { fmt.Println("Error decode") }
詳細はこちら。 yhidetoshi.hatenablog.com
JSON-to-Go ( Convert JSON to Go struct )
JSON --> 構造体 してくれるサービスがる。
JSON-to-Go: Convert JSON to Go instantly
ポインタ
参考・引用について
このポインタのところは、以下の2つのブログで大変わかりやすく解説されています。自分が理解するために必要なところを引用・参照して記載しています。(多謝)
ポインタとは
説明
- int型変数のポインタの場合は
*int
と記述する - 変数のアドレス取得するときは
&
をつけ、アドレスから変数の中身へアクセスする時は*
を使う 変数
とは、メモリのアドレスに付けられた名前
のこと変数c
があるのではなくて、0x7fffffffe494
という場所アドレス
に変数c
という名前を付けている- コンパイラが
変数cという場所
が必要なようだから、そのぶんメモリを用意しておこう。0x7fffffffe494
を変数c
と名付けよう」と決めている - コンパイラがこういったことを考えてくれるおかげで、われわれプログラマーは「変数c」というものの正体を深く考えずにプログラミングできる
- ポインタは
メモリ上の他のアドレスを指す変数
- int型変数のポインタの場合は
Pointerを使う理由
- サイズの大きな変数を効率よく扱う
- 関数の引数として値を渡す際に、「引数用のアドレス」に変数をコピーします。int型やchar型のような小さい変数であれば問題ないですが、大きなサイズの構造体を引数に取るような関数の場合、メモリコピーにCPUを使ってしまって性能が落ちてしまう。どれだけ大きな構造体でも、先頭のアドレスだけ渡してしまえば効率よくやり取りできる
- サイズの大きな変数を効率よく扱う
void warizan(int warareru, int waru, int* syou, int* amari) { *syou = warareru / waru; *amari = warareru % waru; } void main () { int a; int b; int c; int d; a = 17; b = 5; warizan(a, b, &c, &d); // c = 3, d = 2になる }
- 値渡し
- ある変数を関数の引数として渡す場合、値のコピーが渡されます。そのため呼び出された関数内で変数の値を変更しても、元の値には影響がありません。これを「値渡し」と呼びます。
- ポインタ渡し(参照渡し)
- ポインタ変数を関数に渡した場合は、ポインタが指し示す値が、呼び出し元も、呼び出された方も同じものであるため、呼び出された関数内で元の値を変更することができます。これを「ポインタ渡し(または参照渡し)」と呼びます。
値渡しとポインタ渡しの簡単な例
package main import "fmt" func call(a int, b *int){ a = a + 1 *b = *b + 1 } func main(){ a,b := 5,5 call(a, &b) fmt.Println("値渡し", a) // ---> 値渡し 5 fmt.Println("ポインタ渡し", b) // ---> ポインタ渡し 6 }
- ポインタ配列
- Go 言語の配列名は配列の先頭アドレスを表していません。また、配列にアドレス演算子を適用すると、配列へのポインタが生成されますが、それは配列の先頭アドレスを表しているわけではありません。Go 言語の配列はひとつの「値」なので、配列へのポインタは配列そのものを指し示すことになります。
メソッドの ポインタレシーバ
と 変数レシーバ
type Vertex struct { X, Y float64 } func (v *Vertex) Scale(f float64) { v.X = v.X * f v.Y = v.Y * f } func ScaleFunc(v *Vertex, f float64) { v.X = v.X * f v.Y = v.Y * f }
- ポイントレシーバ
- 呼び出し時に、変数、または、ポインタのいずれかのレシーバとして取得できる
var v Vertex ScaleFunc(v) // Compile error! ScaleFunc(&v) // OK var v Vertex v.Scale(5) // OK p := &v p.Scale(10) // OK
- 変数レシーバ
- メソッドが変数レシーバである場合、呼び出し時に、変数、または、ポインタのいずれかのレシーバとして取ることができる
var v Vertex fmt.Println(AbsFunc(v)) // OK fmt.Println(AbsFunc(&v)) // Compile error! var v Vertex fmt.Println(v.Abs()) // OK p := &v fmt.Println(p.Abs()) // OK
- ポインタレシーバを使う理由
- メソッドがレシーバが指す先の変数を変更するため
- メソッドの呼び出し毎に変数のコピーを避けるため。 例えば、レシーバが大きな構造体である場合に効率的
reflect
-
- interface{}型から具体的な型に落とし込む必要がある
- アサーションする型が実体と合わない場合はpanicが発生する
reflectパッケージを利用する
package main import ( "fmt" "reflect" ) func p(v ...interface{}) { fmt.Println(v...) } func main() { // 型チェック p(reflect.TypeOf(0)) // int //型比較 p(reflect.TypeOf(0).Kind() == reflect.Int) // true p(reflect.TypeOf(0) == reflect.TypeOf("hoge")) // false p(reflect.TypeOf(0) == reflect.TypeOf(1)) // true //reflectで値をセット /* Elem https://golang.org/pkg/reflect/#Value.Elem Elem returns the value that the interface v contains or that the pointer v points to. It panics if v's Kind is not Interface or Ptr. It returns the zero Value if v is nil. */ var n int v := reflect.ValueOf(&n).Elem() v.SetInt(99) p(n) // 99 v.Set(reflect.ValueOf(999)) p(n) // 999 /* Slice */ // 型チェック p(reflect.TypeOf([]int{})) // []int // 型比較 p(reflect.TypeOf([]int{}).Kind() == reflect.Slice) // true p(reflect.TypeOf([]int{}) == reflect.TypeOf([]int{1, 2})) //true }
テスト
書き方
- 書き方のルール
- テストコードは同一のディレクトリに配置する
- テストコードのファイル名は
_test.go
とする -->calc_test.go
- Testから始まる名前にする -->
func TestSum(t *testing.T)
- 引数は *testing.Tを渡す -->
func TestSum(t *testing.T)
シンプルなコードで確認する。
- ディレクトリ構成
. ├── calc │ ├── calc.go │ └── calc_test.go └── main.go
- main.go
package main func main() {}
- calc.go
package calc func Sum(a, b int) int { return a + b }
- calc_test.go
package calc import "testing" func TestSum(t *testing.T) { if Sum(1 ,2) != 3{ t.Fatal("Sum vaule should be 3, but not match" ) } }
実行方法
実行のルール
$ go test -v ./calc
=== RUN TestSum --- PASS: TestSum (0.00s) PASS ok _/Users/hidetoshi/TestGoStudy/calc (cached)
$ go test -run TestSum ./calc
ok _/Users/hidetoshi/TestGoStudy/calc (cached)
$ go clean -testcache
(キャッシュをクリア)$ go test -run TestSum ./calc
ok _/Users/hidetoshi/TestGoStudy/calc 0.008s
Tips
Goで開発するときに利用すべきツール
gofmt
- コードをフォマットしてくれる
goimports
- パッケージを読み込むimport文の挿入ど削除を自動でおこなってくれる
govet
- バグの原因になりそうなコードを検出してくれる
golint
- Goらいしくない書き方を検出してくれる
-set_exit_status
- 警告が発生したらエラーで終了させる場合に使う
-min_confidence
- 厳しさを変更できる
- デフォルト値は0.8
-min_confidence=0.5
のように指定する
- 厳しさを変更できる
Go Module
- Go1.13から正式導入予定
- 環境変数
export GO111MODULE=on
go mod init
- go.modファイルが生成される
- 依存関係の定義と管理のためのファイル
go: cannot determine module path for source directory... ( outside GOPATH, no import comments )
- gitのリポジトリ設定がされていなければエラーになる ? と思う。
go get
を実行するとgo.modが自動更新される。go.sumファイルが生成される- go.sumファイル
- バージョンロックのためのファイル
- go.sumファイル
- go.modファイルが生成される
表示形式
- go-humanize
- Size
- ファイルサイズ等
humanize.Bytes(12345678)
- ファイルサイズ等
- Time
- 現在時刻からの相対時刻
humanize.Time(t)
- 現在時刻からの相対時刻
- Comma
- 3桁区切り
humanize.Comma(123456)
- 3桁区切り
- SI単位
humanize.SI()
- Ftoa
- 末尾の0を除去
humanize.Ftoa(1.23)
- 末尾の0を除去