2016年 07月 25日

go言語入門

インストール

【Mac】
Golang Macへのインストール〜Homebrewを使って〜


【Windows】
はじめての Go 言語 (on Windows)


インストールとパス通しができたら


この記事のソースコードをダウンロードして、フォルダに移動後
go run 1_hello_world.go で実行してみましょう!

基本的なデータ型

go
 string    "hello"
 int       1234
 float64   3.141592
 bool      false true
 nil       不定

デフォルト値

 var s string  // ""
 var a int     // 0
 var b float64 // 0
 var f bool    // false

こんにちは世界

プログラムのmainパッケージ内のmain関数が、まず最初(初期化処理後)に実行されるので、main()の中に処理を書いていきます。
入出力の標準パッケージ fmtをimportして文字列を表示

go
package main

import (
    "fmt"
)

func main() {

    // 通常の代入
    var msg = "hello world"

    // 短縮代入
    m := "hello world!!"

    fmt.Println(msg)
    fmt.Println(m)

}
実行結果
hello world
hello world!!

goの特徴として「使っていない変数」や「パッケージ」があった場合
コンパイルエラーが発生します。

fmt.Println(msg)をコメントアウトすると
# command-line-arguments
./1_hello_world.go:24: msg declared and not used

と怒られるので注意 ʕ◔ϖ◔ʔ

変数と定数

go
package main

import (
    "fmt"
)

// 基本的な変数
func main() {

    a := 5           // int
    b := 1.35        // float
    c := "hoge"      // 文字列
    var d bool       // bool
    const e = "定数"  // 定数

    fmt.Printf("a:%d  b:%f  c:%s  d:%t  e:%s", a,b,c,d,e)

    // 定数を列挙
    // 識別子 iota を使用することで0から始まる連番にできる
    // 識別子 iota+1 を使用すること1から始まる連番にできる
    const (
        sun = iota+1
        mon
        tue
    )
    fmt.Println(sun, mon, tue)

}
実行結果
a:5  b:1.350000  c:hoge  d:false  e:定数 

1 2 3

iotaは「アイオーティーエー」ではなく「イオタ」と読むらしい

ポインタ操作

go
package main

import (
    "fmt"
)

// ポインタ操作
func main (){

    a := 5

    // int 型のアドレスを宣言
    var pa * int

    pa = &a

    // 出力
    fmt.Println(*pa)
    fmt.Println(pa)

}
実行結果
5
0x208178170

関数

関数の構文は func 関数名(引数,型)(戻り値の型){ 処理 }

go
// int型のaを受け取って、int型のaをリターン
func test(a int) (int) {
    return a
}

Go言語は 複数戻り値を設定できます

go
package main

import (
    "fmt"
)

// 関数、関数リテラル
func main() {

    // hello world
    a := hello("wolrd")
    fmt.Println(a)


    // 複数戻り値
    b , c := swap(111, 222)
    fmt.Println(b, c)


    // 無名関数を作成し、変数に代入(関数リテラル)
    tmp := func(a, b int) (int, int) {
        return b , a
    }
    fmt.Println( tmp(44, 33) )


    // 即時関数
    func (msg string) {
        fmt.Println(msg)
    }("関数を定義して実行")

}

/**
 * 引数で受け取った文字列に連結させて返す(戻り値の変数を指定)
 */
//func hello(変数名 型) (戻り値 型)
func hello(msg string) (ret string) {

    ret = "hello " + msg
    return
}

/**
 * 引数を逆にしてまとめて返す
 */
func swap(a, b int) (int, int) {
    return b , a
}
実行結果
hello wolrd
222 111
33 44
関数を定義して実行

Go言語の特徴の一つでもある 複数戻り値がでてきました
swap関数に値を渡し、複数の戻り値がbとcに代入されていることがわかります。


次の行では関数を変数に代入しています

go
// 無名関数を作成し、変数に代入
tmp := func(a, b int) (int, int) {
    return b , a
}

上記のような例を 関数リテラルといいます。

Tips①【リテラルとは?】

リテラルとは

ソースコード内に値となる、文字列、数字、式を直接表記したもの。
一例として、変数が箱とたとえられるのであれば、その変数の中に入る値をリテラルと呼ぶ。

  例)
  var string = "Hello World";
  var num = 10;

ここでいう代入された値の「Hello World」や「10」のことをリテラルという。


関数リテラルとは
変数に関数を代入して記述することを関数リテラルと言う。

  例)
  var func01 = function(){処理};

関数リテラル、無名関数、匿名関数この三つは同義語。

配列

var 変数名 [要素数]型で配列を宣言でき、0で初期化されます
また、要素数に ...を指定することで要素数を省略することができます

go
package main

import (
    "fmt"
)

func main() {

    // 配列を宣言(0初期化)
    var a [5]int
    fmt.Println(a)

    // 配列を宣言して代入
    b := [3]int{1,3,5}
    fmt.Println(b[2])

    // [...]で要素の大きさを省略可能
    c := [...]int{2,4,6}
    fmt.Println(len(c)) // 要素数を取得
}
実行結果
[0 0 0 0 0]
5
3

スライス①

Goの特徴のひとつ
スライスは配列の部分列を簡単に取り出すことができるデータ構造
Go の配列は固定長、スライスは可変長配列のようなもの

// 公式の説明
スライスはある配列内の連続した領域への参照であり、スライスの内容はその配列の要素の並びです。スライス型は、その要素型と同じ要素型を持つ配列のすべてのスライスの集合を表します。初期化されていないスライス型の値はnilです。
go
a := [3]int{1,2,3}   // 配列
a := [...]int{1,2,3} // 配列
a := []int{1,2,3}    // スライス
go
package main

import (
    "fmt"
)

func main() {

    // 要素数をしていせず、配列を作成(コンパイラが数える)
    c := [...]int{1,3,5,7,9}

    // 2番目から(4-1)番目をスライス
    d := c[2:4]

    // [5,7]
    fmt.Println(d)
    fmt.Println(c)

    // スライスし生成した配列に値を代入 ※元の配列の参照なのでc[4]も12となる
    d[1] = 12
    fmt.Println(c)

    // len() 配列の長さ
    // cap() 配列の先頭からきりだせる最大数
    fmt.Println(d, len(d), cap(d))
}

実行結果
[5 7]
[1 3 5 7 9]
[1 3 5 12 9]
[5 12] 2 3

参考リンク
http://golang.jp/go_spec#Slice_types
http://jxck.hatenablog.com/entry/golang-slice-internals
http://www.slideshare.net/yasi_life/go-14075425

スライス操作

make
スライスのもう一つの宣言方法。第一引数に型を、第二引数に長さ(len)を指定

append
要素の追加

copy
 スライスをコピーし、コピーした要素数を返す

go
package main

import (
    "fmt"
)

func main() {

    // eスライスを作成
    e := []int{1,3,5}

    // スライスした配列に要素を追加(スライスのみ可能)
    e = append(e, 7, 9)
    fmt.Println(e)

    // eの要素数の配列を作成
    f := make([]int, len(e))

    // eスライスをコピーし、コピーした要素数をgに代入
    g := copy(f,e)
    fmt.Println(f)
    fmt.Println(g)
}

eと同じ大きさの fスライスを作成し、eをfにコピーしています
gにはコピーした要素数がはいるので、5が出力されます

実行結果
[1 3 5 7 9]
[1 3 5 7 9]
5

参考リンク
http://jxck.hatenablog.com/entry/golang-slice-internals
http://www.slideshare.net/yasi_life/go-14075425

MAP

PHPの連想配列のような感じ。

go
package main

import (
    "fmt"
)

// MAP (連想配列のようなもの)
func main() {

    // map[キーの型]値の型
    m := make(map[string]string)
    m["first_name"] = "yamada"
    m["last_name"]  = "taro"

    fmt.Println(m);

    // 宣言と代入
    i := map[string]string{"first_name":"yamada", "last_name":"taro"}
    fmt.Println(i);

    // キーを指定し、要素を削除
    delete(i, "first_name")
    fmt.Println(i);

    // 値が存在するか
    value, flg := i["yamada"]
    fmt.Println(value, flg);
}
実行結果
map[first_name:yamada last_name:taro]
map[first_name:yamada last_name:taro]
map[last_name:taro]
 false

分岐処理 ifとswitch

go
package main

import(
    "fmt"
)

// ifとswitch
func main() {

    // ifの中でしか生存しない変数の場合、この書き方ができる
    if _score := 60; _score > 80 {
        fmt.Println("Great")
    } else if _score >  60 {
        fmt.Println("Nice")
    } else {
        fmt.Println("Oh...")
    }

    // switch
    signal := "red"
    switch signal {
    case "red":
        fmt.Println("Stop")
    case "yellow":
        fmt.Println("Caution")
    case "green", "blue":
        fmt.Println("Go!!")
    default:
        fmt.Println("Accident")
    }

    // switchにifを使う場合
    score := 80
    switch {
    case score > 80:
        fmt.Println("Great")
    case score > 60:
        fmt.Println("Nice")
    default:
        fmt.Println("Oh...")
    }

}
実行結果
Oh...
Stop
Nice

変数の生存範囲が確定してるのは、わかりやすいですね!

ループ処理 

goにはwhileがないので、ループは基本的にforで行う

go
package main

import(
    "fmt"
)

func main() {

    // 基本構文
    for i := 0; i < 3; i++ {
        if i == 0 {
            continue
        }
        fmt.Println(i)
    }

    // whileっぽくする
    i := 0
    for i < 3 {
        fmt.Println(i)
        i++
    }

    // 無限ループ
    n := 0
    for {
        fmt.Println(n)
        if n == 5 {
            break
        }
        n++
    }
}
実行結果
12
012
012345

range

予約語のrangeは
「Array」「Slice」「map」などをイテレートする

go
package main

import(
    "fmt"
)

func main() {

    // スライスを作成
    slice := []int{2, 3, 8}

    // sliceの要素文ループ  i:インデックス  v:値
    for i , v := range slice {

        // rangeは二つの値を返す
        fmt.Println(i ,v)
    }


    // 値を破棄したければ、ブランク修飾子「 _ 」を使用
    for _ , v := range slice {
        fmt.Println(v)
    }


    // mapの場合は key value が返る
    m := map[string]string{"first_name":"Tachi", "last_name":"Hiroshi"}
    for k ,v := range m {
        fmt.Println(k ,v)
    }
}
実行結果
0 2
1 3
2 8
2
3
8
first_name Tachi

※mapをrangeでイテレーションすると、実行ごとに異なるので注意が必要。

固定されたオーダーで実行していたことで、使用者がそれを期待してコードを書いてしまうことが問題になりました。なぜかというと、この"固定されたオーダー"そのものがターゲットのCPUアーキテクチャなどによって変化する場合があったからです。これではgoの良さである移植性が殺されてしまいます。
そういうわけで、mapのイテレーションの順序を期待したコードが書かれないように、変化するように実装が変えられました。

これまた強烈な仕様w
知らないでコード書いてると爆死します!

構造体

なぜクラスではなく構造体なのかというと
goにクラスは存在しないからです

go
package main

import(
    "fmt"
)

// 構造体の宣言
type user struct {
    first_name string
    last_name string
    score int
}

/**
 * 構造体
 */
func main() {

    u := new(user) // アドレスが返る

    // 代入方法1
    u.first_name = "Tachi"

    // 代入方法2
    (*u).last_name = "Hiroshi"

    // 代入方法3 フィールド順
    uu  := user {"Tachi", "Hiroshi", 100}

    // 代入方法4 キー指定っぽく
    uuu  := user {first_name:"Tachi", last_name:"Hiroshi", score:100}

    fmt.Println(u)
    fmt.Println(uu)
    fmt.Println(uuu)
}
実行結果
&{Tachi Hiroshi 0}
{Tachi Hiroshi 100}
{Tachi Hiroshi 100}

メソッド

構造体へのアクセサとして使用するのがほとんどのよう。

user構造体を定義しsocoreをカウントアップするclearメソッドと
表示するshowメソッドを作成します

go
package main

import (
    "fmt"
)

// 構造体の宣言
type user struct {
    first_name string
    last_name string
    score int
}

func main() {

    u := user{first_name:"Tachi", last_name:"Hiroshi", score:79}
    u.clear()
    u.show()
    fmt.Println(u)
}


// メソッド
func (u *user)clear(){
    u.score++
}

func (u *user)show(){
    fmt.Printf("Name:%s %s Score:%d", u.first_name, u.last_name, u.score)
}
実行結果
Name:Tachi Hiroshi Score:80 
{Tachi Hiroshi 80}

Tips② 関数とメソッドの違いって?

調べてみた結果

・オブジェクト自身を操作する場合に使う手続きをメソッド。
・それ以外を関数(非オブジェクト指向言語)

という結論に。

インターフェイス

Go の開発者が 「Interface を制すものは Go を制す」と言い切るくらい重要らしい。


「型アサーション」「型switch」で型を判定して別の文字を表示してみます

go
package main

import (
    "fmt"
)

// インターフェース
type greeter interface {
    greet()
}

// JPN構造体とメソッド
type jpn struct {}
func (j jpn) greet() {
    fmt.Println("こんにちは!")
}

// USA構造体とメソッド
type usa struct {}
func (u usa) greet() {
    fmt.Println("Hello!")
}

func main() {

    // greeter型でスライスを作成
    greeters := []greeter{ jpn{}, usa{} }

    for _, v := range greeters {
        v.greet()

        // 型チェックし、文字列を出力
        checkInterface(v)
    }


}

/*
 * 型チェック
 * t interface{} は空のインターフェースであり、全ての型を受け取ることができる
 */
func checkInterface(t interface{} ) {

    /**
     * 型アサーション
     * 値, フラグ := t.(type) // t が type を満たすかを調べる
     */
    _, flg := t.(jpn)
    if flg {
        fmt.Printf("I am ")
    } else {
        fmt.Printf("I'm ")
    }

    // 型switch
    switch t.(type) {
    case jpn:
        fmt.Println("japanese")
    case usa:
        fmt.Println("american")
    default:
        fmt.Println("human")
    }
}
実行結果
こんにちは!
I am japanese

Hello!
I'm american

インターフェイスを使えばダックタイピングができます。

ゴルーチン

Go最大の特徴であるゴルーチンです。
関数を並行処理します。


使い方は超簡単で go 関数名() だけです。
main()で task1 → task2 の順で処理を実行させます。

go
package main

import (
    "fmt"
    "time"
)

func task1() {

    // 2秒間停止
    time.Sleep(time.Second * 2)
    fmt.Println("task1 finish")
}

func task2() {

    fmt.Println("task2 finish")
}

// ゴルーチン
func main(){

    // 並行処理開始
    go task1()
    go task2()

    // task1が終了する前にmain関数が終了するため3秒まつ
    time.Sleep(time.Second * 3)
}

実行結果
task2 finish
task1 finish

先にtask1が実行されますが、関数内で2秒停止しているため
task2の方が先に完了しています!

Tips③ 並行処理と並列処理の違い

【並行処理】
・時分割でスレッドを処理
・複数の動作が、順不同もしくは同時に起こりうる
・実行状態を複数保てる


【並列処理】
・マルチコアで処理
・複数の動作が、同時に起こること(非同期処理)
・複数の動作を同時に出来る


単純に「行」と「列」で考えるとわかりやすいかも
参考リンク

チャネル

Channel を用いたメッセージング
Channel は参照型なので make() でインスタンスを生成


送信が channel<-value
受信が <-channel


ゴルーチンの終了判定をしたり、ゴルーチン間で値を送受信できます

go
package main

import (
"fmt"
"time"
)

func task1(result chan string) {

    // 2秒間停止
    time.Sleep(time.Second * 2)

    // チャネルに値を送信
    result<- "task1 finish"
}

func task2() {

    fmt.Println("task2 finish")
}

 func main() {

    // チャネルのインスタンスを生成
    result := make(chan string)

    go task1(result)
    go task2()

    /**
     * <-result チャネルの値を取り出す
     * resultに値が入るまで処理がブロックされる
     */
     fmt.Println(<-result)


    // 即時関数を使い、ローディングっぽい表示
     complete := make(chan bool)
     go func() {

        fmt.Printf("Now Loading")
        time.Sleep(time.Second * 1)
        fmt.Printf(".")
        time.Sleep(time.Second * 1)
        fmt.Printf(".")
        time.Sleep(time.Second * 1)
        fmt.Printf(".")

        complete<- true
    }()

    <-complete // 受信した値自体は必要ないため、捨てる

    fmt.Println("end")
}

実行結果
chanel.gif

実行結果をみると、 fmt.Println(<-result) の行で
チャネルの受信待ちが発生しているため
task1終了後に Nowloading... が行われているのがわかります。


これはおもしろい!

Webサーバーをたてる

パッケージの net/http をインポートしてwebサーバーをたて、URLの「/」以下を表示します

go
package main

import(
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

func handler(w http.ResponseWriter, r *http.Request) {

    // [1:] ←1文字目から最後まで表示(0文字目は/がはいる)
    fmt.Fprintf(w, "Hi %s!", r.URL.Path[1:])
}

実行結果
go run 17_web_server.goで実行後、
ブラウザでローカル( http://localhost:8080/太刀ひろし)にアクセス
(※yosemite環境で実行する場合はここを参照してくさだい)

スクリーンショット 2015-06-15 13.53.58.png

超簡単にwebサーバーがたちました!Goすげぇ!

最後に

学生時代はCとC++しか書いたことがなかったんですが、言語っておもしろいですね!
コツコツと基礎を学ぶしかないかな〜っと考えてた今日この頃


以上、お疲れさまでした!

Yoshida e070c695df5d4bbe8e6b800136356dbfb59e78836e5658e2b5f4e4e33df4a66d
Ryo

グルメ旅とお酒が大好きなプログラマー
大阪界隈の勉強会運営もやってます。
趣味はボルダリング

follow us in feedly このエントリーをはてなブックマークに追加