Help us understand the problem. What is going on with this article?

Go言語入門 ~概要と基本構文~

はじめに

TechCommit Advent Calendar 2019
22日目担当のSatoshi です。

筆者について

エンジニア歴2年
22歳
業務ではJavaScriptを使ってなにやらやっています。
Goを使ってバックエンド開発がやりたいので、Goを学習中

本記事の内容

本記事では、Go言語入門編として、基礎的な内容を書いていきます。
筆者は、業務ではJavaScript(動的言語)を触っているので、それと比較してGo言語(静的言語)の良いところにもちょくちょく触れていけたらなと思います。

本記事のゴール

  • Go言語について、概要レベルで理解できる
  • 基礎的な部分をさらっと学習、復習できる

Go言語について

Go言語は、2009年にGoogleによって開発された言語です。
特徴としては、一部ですが以下のようなものがあります。

  • シンプルな言語
  • 静的型付け
  • クロスプラットフォーム対応
  • コンパイル・実行速度が早い
  • 並列処理が得意
  • フォーマットの統一性 etc...

Go言語では、標準でフォーマットを整えてくれる機能(go fmt)や、使ってない変数があるとコンパイルできないように設計されているので、属人性は出にくいのかなと思いました。

本記事では、クロスプラットフォーム対応コンパイル・実行速度が早い並列処理が得意などには触れません。

導入

サクッと簡単なプログラムだけ組んで遊んでみたいということであれば、Go公式ページTry GoもしくはOpen in Playgroundリンクを踏むと、環境構築無しにWeb上でプログラムが実行可能です。

Go公式 - Downloadsにアクセスし、ご自身の環境に合ったファイルをダウンロードして下さい。
各環境の細かいインストール方法を記述すると大変なので、各自で調べてください。

公式の導入方法

以下ページ(英語)に行くとシステム要件やGoツールのインストール方法、アンインストール方法などが載っています。
Getting Started

チュートリアル

Goには、公式が提供しているチュートリアル、A Tour of Goというものがあります。
上記リンクを踏んで、ページを1つ進めると日本語や英語、中国語など様々な言語が選択できます。
ただ、説明がとても短い章があるので、別のサイトで調べつつ取り組むことをオススメします。

世界に挨拶

以下のソース内で、import "fmt"と記述していますが、fmtとはGoが標準で提供しているパッケージです。
Goが提供しているパッケージの一覧は、Go公式 - Packagesで確認できます。

Goでは、以下のように挨拶します。

HelloWorld.go
//ソースファイルがどこに所属しているか必ず宣言
package main
//入出力フォーマットを実装した標準パッケージ"フォーマット"をインポート
import "fmt"
//関数はfuncキーワードで定義
func main(){
    fmt.Println("Hello, World!") // Hello, World!
}

Goでは、決まりとしてプログラムは何らかのpackageに属している必要があり、そのうちの 1 つは必ず main でなければならない。というものがあります。
また、mainパッケージmain関数があれば、それが最初(初期化処理後)に実行されます。
別の関数を定義して呼び出したいときは、main関数の中で呼び出します。

HelloNeighbor.go
package main
import "fmt"
//stringの部分は、戻り値の型を示す
func Say() string{
    return "Neighbor!"
}
func main(){
    fmt.Println("Hello,", Say()) //Hello, Neighbor!
}

特別な関数init

世界に挨拶では、Goでは、mainパッケージのmain関数が最初(初期化処理後)に実行されますと記述しました。
しかし、mainパッケージinitという関数を定義することで、main関数よりも先に関数を実行することができます。
このinit関数を利用することで、変数の初期化外部リソースの読み込みなどを一番最初に実行できます。

Initialize.go
package main
import "fmt"

func init(){
    fmt.Println("Initializing...")
}
func main(){
    fmt.Println("Hello, World!")
}
//Initializing...
//Hello, World!

Import

Goでは、様々なパッケージをインポートしつつプログラムを書いていきます。
以下は、現在の時刻を出力するプログラムです。

PrintTime.go
package main
//import()と記述することで、複数のパッケージをインポートできる
import(
    "fmt"
    "time"
)
func main(){
    fmt.Println("Now: ", time.Now()) // Now:  2009-11-10 23:00:00 +0000 UTC m=+0.000000001
}

変数宣言

Goでは、変数の型を変数名の後ろに定義する決まりとなっています。
また、様々な型が提供されています。Go - Packages - Variables
varでの型宣言は、関数外でも宣言できますが、ショートデクラレーション(:=)は関数内でしか宣言できません。
JavaScriptだと型宣言はしないので、ソースの型宣言を見るだけで変数の型が分かるのは良いですね!
また、予期せぬ型が入ってくることも少ないので、安全です。

var_type.go
package main
import "fmt"
func main(){
    //varで宣言
    var i int = 1
    //var()で複数宣言
    var (
        f64 float64 = 1.5
        str string = "foo"
    )
    //カンマ区切りで複数宣言、かつショートデクラレーションで型推論
    t, f := true, false

    fmt.Println(i, f64, str, t, f) // 1 1.5 foo true false
    //ちなみに、初期化だけして値を代入しなかった場合、それぞれの型の初期値が出力される
}

関数

Goでは、関数の戻り値の型を指定します。
基本構文は
func <関数名>([引数]) [戻り値の型] {
[関数の本体]
}

です。
JavaScriptと違い、関数定義時に戻り値の型を指定できるので、実行せずともある程度どの型が戻り値かが分かります。

func.go
package main
import "fmt"
//int型の戻り値
func add(x int, y int) int{
    return x + y
}
//複数の戻り値の型を指定
func sub(a, b int) (int, string){
    return a - b, "subtraction!"
}
//戻り値"result"は予約語ではない。この関数内で戻り値としてresultを使用するということ
func calc(price, item int) (result int){
    //resultをショートデクラレーション(:=)で再定義はできない
    result = price * item
    return result
}
func main(){
    radd := add(10, 20)
    fmt.Println(radd) // 30

    rsub, rsubStr := sub(10 , 5)
    fmt.Println(rsub)   // 5
    fmt.Println(rsubStr)// subtraction!

    //以下のように書くことで、即時に関数を実行できる
    func(a int){
        fmt.Println(a) //9999
    }(9999)
}

型変換

以下に、いくつかの型変換を行うプログラムを記述します。

cast.go
package main
//string->intへキャストする際に、strconvを使う
import (
    "fmt"
    "strconv"
)
func main(){

    //int -> float
    var x int = 1
    f64x := float64(x)
    //%Tは、型を出力、%vは値を出力、%fは指数なしの小数を出力する
    fmt.Printf("%T %v %f\n", i, i, i)      //int 1 %!f(int=1)
    fmt.Printf("%T %v %f\n", f64x, f64x, f64x)// float64 1 1.000000

    //float64 -> int
    var y float64 = 1.5
    inty := int(y)
    fmt.Printf("%T %v %f\n", y, y, y)         //float64 1.5 1.500000
    fmt.Printf("%T %v %f\n", inty, inty, inty)// int 1 %!f(int=1)
}

    //string -> int
    var str string = "72"
    //strconvの "Atoi(ASCII to integer)" は "int" と "error" の2つを返すため、 "_" でerrorを捨てる。
    //今回はエラーハンドリングを実装しませんが、ハンドリングするときは"_"ではなく"err"などの変数を定義し、ハンドリングしてください。
    i, _ := strconv.Atoi(str)
    //stringで72を文字列型にキャストすることで、ASCIIコードに沿って文字が出力される
    fmt.Printf("%T %v %v\n", i, i, string(i)) // int 72 H

配列とスライス

配列は固定長、スライスは可変長の配列のようなものです。

配列

array.go
package main
import "fmt"
func main(){
    //配列
    //int型の配列で、要素数2
    var x[2] int
    x[0] = 1
    x[1] = 2
    fmt.Println(x) // [1 2]

    //初期化時に値を入れたい場合、ブラケットを使って代入する
    var y[3]int = [3]int{1, 2, 3}
    fmt.Println(y) // [1 2 3]

    fmt.Println(y[0:2]) // [1 2]
    fmt.Println(y[1:2]) // [2]
    fmt.Println(y[:2])  // [1 2]
    fmt.Println(y[1:])  // [2 3]
}

スライス

slice.go
package main
import "fmt"
func main(){
    //型宣言時に、要素数を指定しない
    var z []int = []int{1, 2, 3} //z := []int {1, 2, 3} でもOK
    fmt.Println(z)// [1 2 3]
    //配列と違い、後から値を追加(append)できる
    z = append(z, 4)
    fmt.Println(z)// [1 2 3 4]

    fmt.Println(z[0:2]) // [1 2]
    fmt.Println(z[1:2]) // [2]
    fmt.Println(z[:2])  // [1 2]
    fmt.Println(z[1:])  // [2 3 4]
}

makeとcap

makeの詳細は、実践Go言語 - makeによる割り当てを参照。

make_cap.go
package main
import "fmt"
func main(){
    //integerのスライスで、長さが3つ、キャパシティは5
    a := make([]int, 3, 5)
    fmt.Printf("len=%d cap=%d val=%v\n", len(a), cap(a), a) // len=3 cap=5 val=[0 0 0]
    //長さが3なので、valは0が3つ並んでおり、キャパシティは5なので、メモリ上にはあと2つ確保してある

    a = append(a, 0, 1)
    fmt.Printf("len=%d cap=%d val=%v\n", len(a), cap(a), a) // len=5 cap=5 val=[0 0 0 0 1]
    //appendで "0" と "1" を追加したので、長さが5になった。

    //キャパシティ5以上追加してみる
    a = append(a, 6, 7, 8)
    fmt.Printf("len=%d cap=%d val=%v\n", len(a), cap(a), a) //len=8 cap=12 val=[0 0 0 0 1 6 7 8]
}

上記プログラムの最終行で実行しているfmt.Printfの出力結果に注目してください。
キャパシティも長さも5のスライスに、appendで3つ追加しただけのはずが、追加後のスライスのキャパシティが12になっています。
こんな挙動をする原因を、以下のプログラムで暴きます。

detective_p.go
package main
import "fmt"
func main(){
    //引数を省略することで、以下だと長さもキャパシティも3ということになる
    a := make([]int, 3)
    fmt.Printf("len=%d cap=%d val=%v\n", len(a), cap(a), a)
    fmt.Printf("*p= %p\n", a) // *p= 0x40e020

    a = append(a, 0)
    fmt.Printf("len=%d cap=%d val=%v\n", len(a), cap(a), a) // len=5 cap=5 val=[0 0 0 0 1]
    fmt.Printf("*p= %p\n", a) // *p= 0x456020

    //キャパシティ5以上追加してみる
    a = append(a, 6, 7, 8)
    fmt.Printf("len=%d cap=%d val=%v\n", len(a), cap(a), a)
    fmt.Printf("*p= %p\n", a) // *p= 0x456020
}

上記の通り、キャパシティ以上に要素を追加しようとすると、append関数内部でメモリ領域の再確保が行われ、新しい領域のアドレスが参照されるようになります。
元の値は、新しい領域へコピーされることになるので、メモリ領域の再確保にも、コピー処理にも計算コストがかかります。

map

Goではハッシュ(連想配列)のことをmapと呼びます。

map.go
package main
import "fmt"
func main(){
    m := map[string]int{"foo": 1, "bar": 2}
    fmt.Println(m) //map[bar:2 foo:1]
    fmt.Println(m["foo"]) // 1
    m["bar"] = 999
    fmt.Println(m) //map[bar:999 foo:1]
    //存在しないオプション名にアクセスすると、0が返ってくる
    fmt.Println(m["nothing"]) //0
    //mapに存在しているかチェックする方法は以下。戻り値は "値"と"bool"の2値
    val, ok := m["foo"]
    fmt.Println(val, ok)//1 true
}

可変長引数

関数やメソッドやマクロの引数固定ではなく任意の個数となっている引数のことです。

var_length_arguments.go
package main
import "fmt"
func foo(params ...int){
    fmt.Println("len=",len(params), "param=",params)
    //for rangeを使うことで、paramsをループできる
    for _, param := range params{
        fmt.Println(param)
    }
}
func main(){
    foo(10, 20) //len= 2 param= [10 20]
    foo(10, 20, 30, 40, 50) //len= 5 param= [10 20 30 40 50]
}

if文

特に根本的な動きや書き方で他言語と異なる部分はありません。
GOでは以下のように書きます。
また、Goではif文と同時に変数を宣言し、宣言した変数のスコープをそのif文だけに限定できる書き方があります。
この書き方にすることで変数のスコープが限定でき、コードを読む時も理解しやすくなります。

statement_if.go
package main
import "fmt"
func main(){
    age := 400
    //普通にif文。 if(xxx){} ではく、if xxx {}。
    if age == 400 {
        fmt.Println("Vampire")
    }else if age < 400 {
        fmt.Println("Dragon")
    }else{
        fmt.Println("Cat")
    }

    //if文定義と同時に変数sayを宣言し、";"で区切った後に条件として使用
    if say := "Hello,"; say == "Hello," {
        fmt.Println(say, "World!") // Hello, World!
    }
    //以下で変数sayを出力しようとするが、sayが使えるスコープは上記のif文だけなので、コンパイルエラーとなる
    fmt.Println(say)
}

switch文

こちらもif文同様、他言語と比べて異なる書き方はしません。
また、switch文にもif文同様、switch文の定義と同時に変数を宣言し、宣言した変数のスコープをそのswitch文だけに限定できる書き方があります。

statement_switch.go
package main
import "fmt"
func main(){
    weather := "red"
    //普通にswitch文
    switch weather {
        case "blue":
            fmt.Println("blue.")
        case "yellow":
            fmt.Println("yellow.")
        case "red":
            fmt.Println("yeahhhhh")
        default:
            fmt.Println("never mind.")
    }

    //同時に変数宣言。スコープをこのswitch文のみに限定
    switch animal := "cat"; animal {
        case "dog":
            fmt.Println("dog")
        case "cat":
            fmt.Println("meow!meow!meow!meow!")
        default:
            fmt.Println("never mind.")
    }
    //switch文のスコープ外なのでコンパイルエラー
    fmt.Println(animal)

}

for文とrange

こちらもif文switch文同様で、特別なことはありません。
continuebreakも、使い方は変わらないと思いますので、調べてみてください。
拡張for文(foreach)を実現したい場合は、rangeというものを使う必要があります。

forとcontinue, break

statement_for.go
package main
import "fmt"
func main(){
    max := 10
    //普通のfor文
    for count := 0; count <= 10; count++ {
        fmt.Println(count)

        if count == 2 {
            fmt.Println("now: 2")
            continue
        }

        if count > 5 {
            fmt.Println("Done!")
            break
        }

        //残念ながら、このプログラムでは以下の分岐に入りません...
        if count == max {
            fmt.Println("Max!!")
        }
    }    
}

rangeを使った拡張for文

statement_for_range.go
package main
import "fmt"
func main(){
    strArray := []string{"foo", "bar", "bizz"}
    //rangeはindexと値の2値を返すので、変数も2つ用意。
    for i, v := range strArray {
        fmt.Println(i, v)
        /*
        0 foo
        1 bar
        2 bizz
        */
    }

    //"_"を使うことで、値だけ取り出せる
    for _, v := range strArray {
        fmt.Println(v)
        /*
        foo
        bar
        bizz
        */
    }

    //mapでも使える
    strMap := map[string]int{"Taro": 21, "Hanako": 19, "John": 22}
    for k, v := range strMap {
        fmt.Println(k, v)
        /*
        John 22
        Taro 21
        Hanako 19
        */
    }

    //keyだけ取得することも可能
    for k := range strMap {
        fmt.Println(k)
        /*
        Taro
        Hanako
        John
        */
    }
}

while文

GoではWhileを以下のように書きます。

statement_while.go
package main
import "fmt"
func main(){
    //無限ループ
    for{
        fmt.Println("GoにWhile文はありません...")
        fmt.Println("騙してごめんなさい。")
    }

    //10回ループ
    count := 10
    for count > 0 {
        fmt.Println(count)
        count--
    }
}

最後に

唐突ですが、以上で入門編を終わりにしたいと思います。(続編を書くかは分かりませんが...)
本記事では細かい部分を結構削っています。
理由としては、話が脱線しそうだということと、さらっと学習、復習できるということを目標にしていたたためです。
ただ、入門として知っておくべきことは他にもあります
ここまで学習できれば、あとはトライ&エラーで学習を進めていき、細かい部分のキャッチアップは可能かなと思っているので、一緒に頑張って行きましょう!

Qiita初投稿の記事で時間は結構かかりましたが、書くのは面白いですね。
これは絶対に入門として必要だというものがあれば、コメントで教えて下さい!
筆者は最近Goを触り始めたので、何か間違いなどあった場合には、遠慮なくご指摘いただけると大変嬉しいです...!

あと、Gopherくん可愛いですよね。
可愛すぎてLINEスタンプ買っちゃいました。
Gopher

以上です。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした