読者です 読者をやめる 読者になる 読者になる

ぎじろぐ

まとまらないんです

Golang ことはじめ typeとstruct

type

独自の型を定義することが可能。
例えば以下の構文は引数の順番が 間違っていても コンパイルが通ってしまう。

func main(){
    id := 1
    priority := 5

    // 両方ともint型なため、コンパイルが通る
    ProcessTask(priority, id)
}

func ProcessTask(id, priority int) {
...
}

そこで、typeを使い、独自の型宣言をすることで解決することが可能

type ID int
type Priority int

func main(){
    var id ID = 1
    var priority Priority = 5

    // 引数の型が違うためコンパイルエラー
    ProcessTask(priority, id)

    // これは型が一致しているため通る
    ProcessTask(id, priority)
}

func ProcessTask(id ID, priority Priority){
...
}

構造体(struct)

データをひとまとめにした型だと思えば良い。
一般的なクラス構文的役割も果たす。

// 構造体
type Task struct {
    ID     int
    Detail string
    done   bool
}

func main() {

    // 構造体初期化
    var task Task = Task{
        ID:     1,
        Detail: "buy the milk",
        done:   true,
    }

    // 構造体順に定義すると、フィールド名を省略できる
    // var task Task = Task{1, "buy the milk", true,}

    // または下記のように型推論可能
    // task := Task{1, "buy the milk", true,}
    
    fmt.Println(task.ID)     //1
    fmt.Println(task.Detail) //buy the milk
    fmt.Println(task.done)   //true

    // 構造体のポインタ利用
    StructPointer()

    // コンストラクタ
    task2 := NewTask(1, "buy the milk")
    fmt.Printf("%+v", task2)

    // メソッド
    task3 := NewTask(1, "buy the popcorn")
    fmt.Printf("%s", task3)
}

// 構造体に明示的な値を指定しない場合、すべてゼロ値となる
func ZeroTask() {
    task := Task{}
    fmt.Println(task.ID)     //0
    fmt.Println(task.Detail) // ""
    fmt.Println(task.done)   //false
}

// 構造体のポインタ利用
func StructPointer() {
    task := &Task{done: false}
    Finish(task)
    fmt.Println(task.done) //true
}

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

/**
 * コンストラクタ例
 * Goには構造体のコンストラクタに当たる構文がないため、
 * 代わりにNewで始まる関数を定義するのが通例
 * 内部でTask構造体を生成し、そのポインタを返す
 */
func NewTask(id int, detail string) *Task {
    task := &Task{
        ID:     id,
        Detail: detail,
        done:   false,
    }
    return task
}

/**
 * メソッド(レシーバ)
 * メソッドを「実行した」対象の型をレシーバとして受け取り、内部で使用できる
 */
func (task3 Task) String() string {
    str := fmt.Sprintf("%d) %s", task3.ID, task3.Detail)
    return str
}

一般的なクラス構文のオブジェクト指向よりわかりやすい気がする。
ただ、クラス構文に慣れすぎてると戸惑いがあった。

次回はインターフェースについて。

Golang ことはじめ 目的~基礎構文まで

目的

  • 要件的にLL系の処理速度できつい部分が増えてきたため、別言語の模索。
  • Golangでチームに新しい風をもたらす。

目標

ブログアプリを作成し、Herokuへデプロイ

学習ステップ

  1. インストール方法
  2. hello wolrd
  3. 基礎構文
  4. DB接続
  5. フレームワーク(Gin)
  6. View
  7. Model
  8. Controller
  9. マイグレーション
  10. シーダー
  11. ルーティング
  12. ログイン / セッション
  13. パーミッション
  14. Heroku on Go lang
  15. ブログアプリ作成

Golangとは?

google謹製の新しい言語、と言っても2009年からプロジェクトはあったらしい。解決する課題として、

  • シンプル
  • 高速
  • 並列性 + タイプセーフ

を重視している。

インストール

brewからサクッと入る。

brew install go

hello world

テキトーにディレクトリを作成し、そこからプログラムを書いてみる

mkdir ~/dev/go-sample
cd ~/dev/go-sample
touch hello.go
package main
import "fmt"

func main () {
    fmt.Printf("Hello, World")
}

実行

go run hello.go
> Hello, World

出力完了。

フォーマット

Goでは開発者間の無駄な争いを避けるため、最初からコーディング規約が整備されている。一つの例を言うと、ソフトタブ(半角スペース)ではなく、ハードタブ(タブ文字)でインデントを行うなど。

atomでは上記のgo fmtを含む便利なプラグインがあったので、インストールしておくと良い。

https://atom.io/packages/go-plus

追加でgithubからパッケ−ジを直接ダウンロードするように指示されるかもしれないが、下記のようにパスを通した上でインストールを行えば良い。

export GOROOT=/usr/local/opt/go/libexec
export GOPATH=$HOME
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin

例えば、fmtを利用したい場合、⌘+shift+pからgo fmtと入力することで対象ファイルがフォーマットされる。

基礎構文

参考:

mainパッケージとimport

package main

import "fmt" //パッケージの追加

func main() {
    fmt.Println("HEELOO")
}

変数

// 宣言
var message string = "hello world"

// 一度に複数宣言をする
var foo, bar, baz string = "foo", "bar", "baz"

func main() {
    fmt.Println(message) //hello world
    fmt.Println(foo, bar, baz) //foo bar baz    
    
    // 型推論と初期化シンタックス
    message2 := "hello2"
    fmt.Printf(message2) //hello2

    // 定数
    const message3 string = "HEEEELOOOOO"
    fmt.Printf(message3)

    var i int //ゼロ値で初期化
    fmt.Println(i)
}

条件式 /ループ式

func main() {
    a, b := 10, 100
    // if後の()は必要なし、波かっこ省略パターンは存在しない
    // 三項演算子も存在しない、if elseを利用すること
    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 equals b")
    }
    
    // 繰り返しはfor意外存在しない
    for i := 0; i < 10; i++ {
        fmt.Println(i)
    }

    // continue, break
    n := 0
    for {
        n++
        if n > 10 {
            break //ループを抜ける
        }

        if n%2 == 0 {
            continue //偶数の場合、一つ飛ばす
        }

        fmt.Println(n) //奇数のみ表示
    }

    // switchでfizzbuzz
    s := 10
    switch s {
    case 15:
        fmt.Println("FizzBazz")
        // fallthrough <-- 明示的に次の処理へ移りたい場合
    case 5, 10:
        fmt.Println("Buzz")
    case 3, 6, 9:
        fmt.Println("Fizz")
    default:
        fmt.Println(s)
    }
    // 式のfizzbuzz
    switch {
    case s % 15 == 0:
        fmt.Println("FizzBazz")
    case s % 5 == 0:
        fmt.Println("Buzz")
    case s% 3 == 0:
        fmt.Println("Fizz")
    default:
        fmt.Println(s)
    }
}

関数

func main() {
    // 関数実行
    hello() //hello
    sum(2,3) //5
    fmt.Println(sum_return(5,4)) //9

    x, y := 3, 4

    // この時点でx=4, y=3と逆になっている
    x, y = sum_return_multiple(x, y)
    fmt.Println(x,y)

    // コンパイルエラー、2つある戻り値を一つしか受け取らないため
    x = sum_return_multiple(x, y)

    // _を利用することで第二引数を無視することができる
    x, _ = sum_return_multiple(x, y)
    fmt.Println(x) //3

    res, err := div(10,2)
    fmt.Println(res, err) //5, nill

    fmt.Println(literal())
}

// 関数引数なし
func hello() {
    fmt.Println("hello")
}

// 引数あり、複数の引数が同一型の場合は、最後の一つにまとめることができる
func sum(i, j int) {
    fmt.Println(i+j)
}

// 戻り値
func sum_return (i, j int) int {
    return i+j
}

// 複数の値を返す
// カンマ区切りをすることで、returnした型を指定し、値を返すことが可能
func sum_return_multiple (i, j int) (int, int) {
    return j, i
}

// 名前付き戻り値
func div(i, j int)(result int, err error) {
  if j == 0 {
    err = errors.New("divied by zero")
    return // return 0, err
  }
  result = i / j
  return // 5
}

// 無名関数
func literal () {
  func (i, j int) {
    fmt.Println(i+j)
  }(2,4)
}

配列 / スライス

func main() {
    arr_func1()
    arr_func2()

    // エラー
    // arr1 := [4]string{"a","b","c","d"}
    // fn(arr1)

    // 値渡しなのでarr2内は [a b c d]のまま
    arr2 := [4]string{"a", "b", "c", "d"}
    call_by_value(arr2)
    fmt.Println(arr2)

    slice()

    // 可変長引数
    fmt.Println(sum(1, 3, 5, 7, 9))
}

// 配列は固定長
func arr_func1() {
    var arr [4]string

    arr[0] = "a"
    arr[1] = "b"
    arr[2] = "c"
    arr[3] = "d"

    fmt.Println(arr[0]) //a
}

// 初期化、暗黙的配列長
func arr_func2() {
    arr1 := [4]string{"a", "b", "c", "d"}
    arr2 := [...]string{"a", "b", "c", "d"}

    fmt.Println(arr1, arr2)
}

// 配列の引数は配列長まで見るため、下記に[4]を渡すとエラー
func fn(arr [5]string) {
    fmt.Println(arr)
}

// 配列は値渡し
func call_by_value(arr [4]string) {
    arr[0] = "x"
    fmt.Println(arr) //[x b c d]
}

// 可変長配列の場合はスライスを利用する
// 固定のarrayを使う機会はあまりない
func slice() {

    // []内に値を入れなければslice
    // eg. var s[]string
    a := []string{"a", "b", "c", "d"}
    fmt.Println(a[0]) //a

    // appendでsliceの後に値を追加
    a = append(a, "e", "f")
    fmt.Println(a) //[a b c d e f]

    // rangeを利用し、先頭から順に処理
    for i, s := range a {
        // i = 添字 s = 値
        fmt.Println(i, s)
    }

    // 値の部分切り出し
    //コロンで挟むとその範囲内の値
    fmt.Println(a[2:4]) //c d

    //始点・終点を省略するとそれぞれ先頭 / 末尾となる
    fmt.Println(a[1:]) //b c d e f
    fmt.Println(a[:4]) //a b c d
}

// 可変長引数
func sum(nums ...int) (result int) {
    // numは[]int型

    // keyは不要
    for _, n := range nums {
        result += n
    }
    return
}

map

func main() {
    map1()
}

func map1() {
    // key-value タイプ 初期化宣言
    // intのkeyにstringのvalue
    // 最後の,も必須な模様
    month := map[int]string{
        1: "January",
        2: "Febrary",
    }

    fmt.Println(month)

    // valueの取り出し
    jan := month[1]
    fmt.Println(jan) //January

    // 2つ目の引数も受け取ると、指定したキーがマップにあるかどうかをboolで受け取る
    _, ok := month[1]
    if ok {
        fmt.Println("data exists!!")
    }

    // mapからデータを削除する場合はdelete関数
    delete(month, 1)
    fmt.Println(month) // map[2:Febrary]

  // rangeでforを回すことも可能
  // ただしmapの場合は、順序が保障されない
  for key, value := range month {
    fmt.Printf("%d, %s\n", key, value)
  }
}

ポインタ

Goではポインタを利用することが可能。

func main () {
  var i int = 10
  callByValue(i) //値渡し
  fmt.Println(i) //10

  callByRef(&i) //参照渡し
  fmt.Println(i) //20
}

// 値渡し
func callByValue(i int) {
  i = 20
}

// 参照渡し
func callByRef(i *int) {
  *i = 20
}

次回は型についての学習をする。