🌞

Go言語 文法まとめ

2022/08/31に公開

Package

  • Goのプログラムは、パッケージ(package)で構成されます
  • プログラムはmainパッケージから開始されます
  • 規約で、パッケージ名はインポートパスの最後の要素と同じ名前になります
    • ex) math/randrandパッケージ
package main

import (
    "fmt"
    "math/rand"
)

func main() {
    fmt.Println("My favorite number is", rand.Intn(10))
}

imports

// factoredインポートステートメント
import (
    "fmt",
    "math",
    "パッケージ名"
)

// 複数のインポートステートメント
import "fmt"
import "math"
import "パッケージ名"

Exported names

package main

import (
    "fmt"
    "math"
)

func main() {
    // 最初の文字が大文字で始まる名前は、外部のパッケージから参照できるエクスポート(公開)された名前( exported name )
    fmt.Println(math.Pi)

    // 小文字ではじまる pi や hoge などはエクスポートされていない名前です。
    // fmt.Println(math.pi)
}

関数

// 変数名の後ろに型名を書く
func 関数名(変数名1 型名, ...) 戻り値の型名 {

    // ...

    return 戻り値
}

// 関数の2つ以上の引数が同じ型である場合には、最後の型を残して省略して記述できます。
func 関数名(変数名1, 変数名2 型名) {

    // ...

    return 戻り値
}

// 複数の戻り値を返す
func 関数名(変数名 型名, ...) (型名, 型名) {

    // ...

    return 戻り値1, 戻り値2
}

ex) 関数

package main

import "fmt"

func add1(x int, y int) int {
    return x + y
}

func add2(x, y int) int {
    return x + y
}

func main() {
    fmt.Println(add(42, 13))
}

ex) 複数の戻り値を返す

package main

import "fmt"

func swap(x, y string) (string, string) {
    return y, x
}

func main() {
    a, b := swap("hello", "world")
    fmt.Println(a, b)
}

Named return values

Goでの戻り値となる変数に名前をつける(named return value)ことができます。戻り値に名前をつけると、関数の最初で定義した変数名として扱われます。

func 関数名(引数) (x 型名, y 型名) {
    x = ...
    y = ...
    return
}

ex)

package main

import "fmt"


// xとyがnamed return valueになる
func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return
}

func main() {
    fmt.Println(split(17))
}

変数

varステートメントは変数(variable)を宣言します。 関数の引数リストと同様に、複数の変数の最後に型を書くことで、変数のリストを宣言できます。

  • varステートメントはパッケージ、または、関数で利用できます
// 変数宣言
// var 変数名(, 変数名2, 変数名3, ...) 型名
var i int
var c, java, java bool


// 初期化宣言. 初期化子が与えられている場合、型を省略できます
// var 変数名(, 変数名2, 変数名3, ...) = 値(, 値2, 値3, ...)
var i, j int = 1, 2
var c, python, java = true, false, "no!"

// 関数の中では、`var`宣言の代わりに、短い`:=`の代入文を使い、暗黙的な型宣言ができます。
// なお、関数の外では、キーワードではじまる宣言(`var`, `func`, など)が必要で、`:=`での暗黙的な宣言は利用できません。
func main() {
    k := 3
}

Go言語の基本型

Go言語の基本型 デフォルト値
真偽 bool false
文字列 string ""
整数 int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr 0
バイト byte (uint8の別名) ?
? rune (int32の別名. Unicode のコードポイントを表す.) ?
浮動小数 float32, float64 0
? complex64, complex128 ?

型変換

変数v、型Tがあった場合、T(v)は、変数vT型へ変換します。

var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

// シンプルな記述
i := 42
f := float64(i)
u := uint(f)

明示的な型を指定せずに変数を宣言する場合(:=var =のいずれか)、変数の型は右側の変数から型推論されます

var i int
j := i // j is an int

右側に型を指定しない数値である場合、左側の新しい変数は右側の定数の精度に基いてint, float64, complex128の型になります

i := 42           // int
f := 3.142        // float64
g := 0.867 + 0.5i // complex128

定数

定数は、constキーワードを使って変数と同じように宣言します。
定数は、文字(character)、文字列(string)、boolean、数値(numeric)のみで使えます。
なお、定数は:=を使って宣言できません。

package main

import "fmt"

const Pi = 3.14

func main() {
    const World = "世界"
    fmt.Println("Hello", World)
    fmt.Println("Happy", Pi, "Day")

    const Truth = true
    fmt.Println("Go rules?", Truth)
}

For文

for 初期化; 条件式; 後処理 {
    // ループは、条件式の評価がfalseとなった場合にイテレーションを停止します。
}

for ; 条件式; {
    // 初期化と後処理ステートメントの記述は任意
}

for 条件式 {
    // ;を省略することもできる
}

for {
    // 無限ループ
}
sum := 0
for i := 0; i < 10; i++ {
    sum += i
}


// 初期化と後処理ステートメントの記述は任意
sum := 1
for ; sum < 1000; {
    sum += sum
}

sum := 1
for sum < 1000 {
    sum += sum
}

If文

if 条件式 {

} else {

}

// ifステートメントは、 forのように、条件の前に、評価するための簡単なステートメントを書くことができます。
// ここで宣言された変数は、 if のスコープ内だけで有効です。
if ステートメント; 条件式 {

} else {}

func pow(x, n, lim float64) float64 {
    if v := math.Pow(x, n); v < lim {
        return v
    }
    return lim
}

Switch文

  • 他言語のSwitchと似ている
  • 上から下へcaseを評価する
  • しかし、Goでは選択されたcaseだけを実行してそれに続く全てのcaseは実行されない(自動的にbreakされる)
  • もう一つの重要な違いは Goのswitchcaseは定数である必要はなく、 関係する値は整数である必要はないということです
package main

import (
    "fmt"
    "runtime"
)

func main() {
    fmt.Print("Go runs on ")
    switch os := runtime.GOOS; os {
    case "darwin":
        fmt.Println("OS X.")
    case "linux":
        fmt.Println("Linux.")
    default:
        // freebsd, openbsd,
        // plan9, windows...
        fmt.Printf("%s.", os)
    }
}

条件のないswitchは、switch trueと書くことと同じです。
このswitchの構造は、長くなりがちな "if-then-else" のつながりをシンプルに表現できます。

package main

import (
    "fmt"
    "time"
)

func main() {
    t := time.Now()
    switch {
    case t.Hour() < 12:
        fmt.Println("Good morning!")
    case t.Hour() < 17:
        fmt.Println("Good afternoon.")
    default:
        fmt.Println("Good evening.")
    }
}

Defer

deferステートメントは、deferへ渡した関数の実行を、呼び出し元の関数の終わり(returnする)まで遅延させるものです。
deferへ渡した関数の引数は、すぐに評価されますが、その関数自体は呼び出し元の関数がreturnするまで実行されません。

package main

import "fmt"

func main() {
    defer fmt.Println("world")  // main関数が終了してから実行される
    defer fmt.Println("!!!")

    fmt.Println("hello")
}

// hello
// world
// !!!

ポインタ

Goはポインタを扱います。ポインタは値のメモリアドレスを指します。

// 変数Tのポインタは、*T型で、デフォルト値(ゼロ値)はnilです。
var p *int

// & オペレータは、そのオペランドへのポインタを引き出します。
i := 42
p = &i

// * オペレータは、ポインタの指す先の変数を示します。
fmt.Println(*p) // ポインタpを通してiから値を読みだす
*p = 21         // ポインタpを通してiへ値を代入する

Structs(構造体)

Goにはオブジェクト指向言語におけるclassというものは存在しません。
似た役割として関連する変数をひとまとめにする struct(構造体) が使用されます。

構造体の定義

type 構造体名 struct {
    フィールド名1 型名
    フィールド名2 型名
    // ...
}

ex)

type Vertex struct {
    X, Y int
    name string
}

初期化方法

// ①初期化後にフィールドに値を代入
var v1 Vertex  // もしくは var v1 Vertex{}
v1.X = 1
v1.Y = 2
v1.name = "ほげ"

// ②順番にフィールドの値を渡す方法
v1 := Vertex{1, 2, "ほげ"}

// ③フィールド名を : で指定する方法
v1 := Vertex{name: "ほげ"}

フィールドへのアクセス

// structのフィールドは、ドット( . )を用いてアクセスします。
v := Vertex{1, 2}
v.X = 4

// ポインタを通してアクセス
v := Vertex{1, 2}
p := &v
p.X = 1e9
fmt.Println(v)

メソッドの定義

Goにはクラスと言う概念は存在しませんが、構造体内にメソッドmethodを定義できます。

func (変数名 構造体名) メソッド名(引数) 戻り値の型名 {
    // 処理...
}

例)

// 構造体
type Person struct {
   first_name string
   age int
}

// メソッド
func (p Person) intro(greetings string) string {
    return greetings + " I am " + p.first_name
}

func main() {
  bob := Person{"Bob", 30}
  fmt.Println(bob.intro("Hello")) //=> Hello I am Bob
}

任意の型(例えば、int, float, ...)にもメソッドを宣言できる。
ex) Absメソッドを持つ、数値型のMyFloat型です。

type MyFloat float64

func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

func main() {
    f := MyFloat(-math.Pi)
    fmt.Println(f.Abs())
    // => 3.141592653589793
}

更新系メソッドの注意点

フィールドの値を更新する際は、ポイントレシーバを使うように

  1. メソッドがレシーバが指す先の変数を変更するため
  2. メソッドの呼び出し毎に変数のコピーを避けるため
type Person struct {
   first_name string
   age int
}

func (p *Person) changeAge(newAge int) {
    p.age = newAge
}


func main() {
    person := Person{"taiyo", 24}
    person.changeAge(25)
    fmt.Println(person)  // => {taiyo 25}
}

構造体の埋め込み

Goはオブジェクト指向言語の様なクラスを持ちません。ですのでクラスの継承も存在しません。そこで継承に似た機能として構造体の埋め込みがあります。

package main

import "fmt"

type Person struct {
   first_name string
}

func (a Person) name() string{ //Personのメソッド
    return a.first_name
}

type User struct {
     Person
}

func (a User) name() string { //Userのメソッド
    return a.first_name
}

func main(){
  bob := Person{"Bob"}
  mike := User{}
  mike.first_name = "Mike"

  fmt.Println(bob.name()) //=> Bob
  fmt.Println(mike.name()) //=> Mike
}

Arrays

  • 固定長の配列
  • 宣言した後に、配列のサイズを変えることはできません

宣言例

[2]string

[3]bool{true, true, false}

[]bool{true, true, false}
// [n]T 型は、型 T の n 個の変数の配列( array )を表します。
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)

primes := [6]int{2, 3, 5, 7, 11, 13}
fmt.Println(primes)

Slices

  • 配列への参照のようなもの
  • スライスはどんなデータも格納しておらず、単に元の配列の部分列を指し示しています
  • スライスの要素を変更すると、その元となる配列の対応する要素が変更されます
  • 同じ元となる配列を共有している他のスライスは、それらの変更が反映されます
primes := [6]int{2, 3, 5, 7, 11, 13}

var s []int = primes[1:4]
fmt.Println(s)
names := [4]string{"John", "Paul", "George", "Ringo",}
fmt.Println(names)

a := names[0:2]
b := names[1:3]
fmt.Println(a, b)

b[0] = "XXX"
fmt.Println(a, b)
fmt.Println(names)

// => [John Paul George Ringo]
// => [John Paul] [Paul George]
// => [John XXX] [XXX George]
// => [John XXX George Ringo]

スライスするときは、それらの既定値を代わりに使用することで上限または下限を省略することができます。

// 以下の配列において
var a [10]int

// これらのスライス式は等価です
a[0:10]
a[:10]
a[0:]
a[:]

スライスの長さと元の配列の長さ

s := []int{2, 3, 5, 7, 11, 13}
// len(s) => 6
// cap(s) => 6

スライスのデフォルト値(ゼロ値)はnilです。

var s []int
s == nil  // => true

make関数

make([]型名, len, cap)
func main() {
     a := make([]int,5, 5)
     fmt.Println(a) //=> [0 0 0 0 0]
}

append関数

append(スライス, 追加する値(, 追加する値2, ...))

ex)

var s []int
s = append(s, 0, 1, 2)

// s => [0 1 2]

range

forループに利用するrangeは、スライスや、マップ(map)をひとつずつ反復処理するために使います。

for i, v := range スライスorマップ {
    i  // => インデックス番号
    v  // => 要素
}


// インデックスや値は、 "_"へ代入することで捨てることができます。
for _, v := range スライスorマップ {
    // ...
}

// もしインデックスだけが必要なら、", value"を省略する
for i := range スライスorマップ {
    i  // => インデックス番号
}

Maps(連想配列, 辞書)

Mapsの宣言

// 組み込み関数make()を利用して宣言. (デフォルト値(ゼロ値)はnill)
make(map[キーの型]値の型, キャパシティの初期値)
make(map[キーの型]値の型)// キャパシティの初期値は、省略も可能

// 初期値を指定して宣言
var 変数名 map[キーの型]値の型 = map[キーの型]値の型{key1: value1, key2: value2, ..., keyN: valueN}

Mapsの操作

挿入や更新

m[key] = value

要素の取得

elem = m[key]

要素の削除

delete(m, キー)

キーに対する要素が存在するかの確認

elem, ok := m[key]

// もし、 m に key があれば、変数 ok は true となり、存在しなければ、 ok は false となります。
// なお、mapに key が存在しない場合、 elem はmapの要素の型のゼロ値となります。

Function values(lambda?)

関数も変数です。他の変数のように関数を渡すことができます。

  • 関数値( function value )は、関数の引数に取ることもできますし、戻り値としても利用できます。
func main() {
    hypot := func(x, y float64) float64 {
        return math.Sqrt(x*x + y*y)
    }
    fmt.Println(hypot(5, 12))  // => 13
}

Interfaces

  • Interface(インターフェース)とは、メソッドの型だけを定義した型のことです。
  • Interface(インターフェース)を利用することで、オブジェクト指向言語でいうところのポリモーフィズムと同様の機能を実現できます。

定義

type インタフェース名 interface {
    インタフェースのメソッド名1(引数名 型名, ...) 返り値の型
    インタフェースのメソッド名1(引数名 型名, ...) (戻り値の型名, 戻り値の型名, ...)
    // ...
}

type 構造体 struct {
    // 定義...
}

func (変数 構造体) インタフェースのメソッド名1(引数名 型名, ...) 返り値の型 {
    // 処理...
}

func (変数 構造体) インタフェースのメソッド名1(引数名 型名, ...) (戻り値の型名, 戻り値の型名, ...) {
    // 処理...
}

空インタフェース

  • 全ての型と互換性を持っているinterface{}型(空インターフェース)というものが存在しています

定義

interface{}

以下の例の様に interface{}(空インターフェース) で宣言した変数にはどんな型の値でも代入可能です

var obj interface{}

obj = 0123                                                            // int
obj = "String"                                                       // string
obj = []string{"Python", "Golang", "Ruby"}                          // slice
obj = func greetings(_ string) string { return "Hello World" }     // function

型アサーション

全ての型と互換性を持っているinterface{}ですが、interface{}型の引数で受け渡された値は、元の型の情報が欠落しています。

var i interface{} =// インターフェースの値 i が具体的な型 T を保持し、基になる T の値を変数 t に代入することを主張します。
t := i.(T)

// i が T を保持していれば、 t は基になる値になり、 ok は真(true)になります。
t, ok := i.(T)
var i interface{} = "hello"
s := i.(string)

// i が T を保持していれば、 t は基になる値になり、 ok は真(true)になります。
s, ok := i.(string)

f, ok := i.(float64)

型switch

データの型判定はswitch文でも行うことができます。Go言語では、これを型switchといいます。
以下の様に型switchを書く事ができます。

switch v := x.(type) {
case 型名1: ...  // v は型名1の値になる
case 型名2: ...  // v は型名2の値になる
    ...
default: ...
}

ex

func do(i interface{}) {
    switch variable := i.(type) {
    case int:
        fmt.Println(variable)
    case string:
        fmt.Println(variable)
    default:
        fmt.Println("Default")
    }
}

func main() {
    do(23) //=> 23
    do("hello") //=> hello
    do(true) //=> Default
}

Stringers

もっともよく使われているinterfaceの一つにfmtパッケージに定義されているStringerがあります

type Stringer interface {
    String() string
}
  • Stringerインタフェースは、stringとして表現することができる型です
  • fmtパッケージ(と、多くのパッケージ)では、変数を文字列で出力するためにこのインタフェースがあることを確認します

Error

Goのプログラムは、エラーの状態をerror値で表現します。

error型はfmt.Stringerに似た組み込みのインタフェースです

type error interface {
    Error() string
}

よく、関数はerror変数を返します。そして、呼び出し元はエラーがnilかどうかを確認することでエラーをハンドル(取り扱い)します。

i, err := strconv.Atoi("42")
if err != nil {
    fmt.Printf("couldn't convert number: %v\n", err)
    return
}
fmt.Println("Converted integer:", i)

Discussion