sagantaf

IT関連の技術記事を書くブログ。

go言語の文法基礎をサンプルとともに(part2)

はじめに

A Tour of GoでGo言語の基本を学んでみましたが、言葉や具体例がいまいち分かりづらい部分もあったため、自分なりにまとめました。 (今の自分のレベルに対する備忘録の側面が強いため、逆に分かりにくい部分もあるかもしれません…)

これはpart2です。

part1は go言語の文法基礎をサンプルとともに(part1) - sagantaf です。


型について

型変換

float64(10)uint(2)のようにすることで、型変換できます。

i := 42
f := float64(i)
u := uint(f)

型宣言

型宣言、として型に別名をつけて新しい型を作成できます。type 新しい型 元の型の形式で作成します。

type SelfInt int // 型宣言
var num1 SelfInt = 1
var num2 SelfInt = 2
var num3 int = 3

func main() {
    fmt.Println(num1 + num2) //3
    //fmt.Println(num2 + num3) //型が異なるためエラーになる

構造体の基本

go言語にはclassは存在しません。その代わり、複数の変数をまとめて1つの型として扱うことができる構造体を利用できます。 型宣言と同様typeを使って宣言します。

type UserStruct struct {
    name string
    age int
    over20 bool
}
    
func main(){
    var taro UserStruct
    taro.name = "taro"
    taro.age = 25
    taro.over20 = true
    fmt.Println(taro.name, "は", taro.age, "歳です。")
}

構造体に構造体を埋め込むこともできます。その場合は型名のみ記載します。

type DevMember struct {
    UserStruct // 別の構造体を利用している
    lang string
    office string
}

func main(){
    var taro DevMember
    taro.name = "taro" // UserStructのフィールドをそのまま利用できる
    taro.lang = "Japanese"
    taro.office = "Tokyo"
    fmt.Println(taro.name, "は", taro.office, "で働いています。")
}


ポインタと構造体

Goにおけるポインタの扱い

Goはメモリアドレスを指すポインタを使えます。 var p *intのように宣言し、ゼロ値はnilになります。 変数(オペランド)に&をつけることで、そのポインタを得ることができます。

func main() {
        i := 42
        p := &i // pにはiのメモリアドレスが格納される
        fmt.Println(p)
        fmt.Println(*p) // *をつけることで中身を取り出せる
}

出力は下記。

0x414020
42

C言語とは異なり、ポインタ演算はありません。

構造体のフィールドにポインタを使ってアクセス

ポインタpを使ってフィールドにアクセスには、(*p).Xp.Xという書き方が可能です。

type Vertex struct {
        X int
        Y int
}

func main() {
        v := Vertex{1, 2}
        p := &v // vのメモリアドレスを取得
        p.X = 100000 //ポインタにドットを使ってフィールドにアクセス
        fmt.Println(v)
}

出力は下記になります。

{100000 2}

また、Structsに&をつけて、ポインタ型の構造体にすることもできます。

ポインタ型の構造体の場合は、*をつけることで中身を得られ、&をつけることでメモリアドレスを得られます。何もつけずにprintするとと&が付いた表記になります。

var (
        p  = &Vertex{1, 2} // pは*Vertexとしてポインタ型の変数になる
)

func main() {
        fmt.Println(p, p.Y) //&が付いた表記になる。フィールドアクセスも可能
        fmt.Println(*p, &p) // *で中身を、&でメモリアドレスを得られる
}

出力は下記になります。

&{1 2} 2
{1 2} 0x1cd044


配列とスライス(ArrayとSlice)

配列

同じ型を持つ要素をまとめたもので、固定長なため、一度作成したら要素数を変えられません。

下記のように配列名、サイズ、型を宣言して利用します。それぞれの要素には、a[x]のようにアクセスできます。

var a[3] int
a[0] = 1

var a[3] int = [3]int{2,4,6} // 初期値を指定して宣言できる
a := [...]int{2,4,6} //初期値を指定するのであれば、要素数を省略することも可能


スライス

スライスの基本

スライスは配列から一部を切り取って扱える可変な配列のようなものです。

func main() {
        nums := [...]int{1, 3, 5, 7, 9}
        var s []int = nums[1:4]
        fmt.Println(s) // [3 5 7]
}

上記のようにa[low : high]と指定し、highの値は含まれません。

スライスには実際に値が格納されるわけではなく、配列へのポインタが格納されるだけになります。そのため、スライスの要素を変更すると、その元となる配列の対応する要素も変更されます。

下記のコードでその挙動が確認できます。

func main() {
    countries := [...]string{"Japan", "Korea", "China"}
    fmt.Println("元の配列:", countries)
    a := countries[:2] // 先頭の0は省略可能
    b := countries[1:] // 末尾の値も省略可能([1:3]にしてもOK)
    fmt.Println("変更前のスライス:",a, b)

    a[0] = "Tokyo"
    fmt.Println("変更後のスライス:", a, b)
    fmt.Println("変更後の配列:", countries)
}

スライスaを編集しただけですが、bもcountriesも変わってしまいます。

元の配列: [Japan Korea China ]
変更前のスライス: [Japan Korea] [Korea China]
変更後のスライス: [Tokyo Korea] [Korea China]
変更後の配列: [Tokyo Korea China ]

Structを使ってSliceを作成することもできます。

func main() {
    s := []struct {
            i int
            b bool
    }{
            {2, true},
            {3, false},
            {5, true},
            {7, true},
            {11, false},
            {13, true},
    }
    fmt.Println(s[1]) // {3 false}
}

また、何も要素を書かずにスライスだけ定義すると、nilスライスになります。nilスライスは 長さ0と容量0で、何の元となる配列も持っていない状態です。

func main() {
    var s []int
    fmt.Println(s, len(s), cap(s)) // [] 0 0
    if s == nil {
            fmt.Println("nil!")
    }
}

s == nilで判定できます。

SliceのLengthとCapacity

スライスには長さ( length )と容量( capacity )という属性を持っています。

lengthは、スライスに含まれる要素の数を表し、len(s)で算出できます。

capacityは、スライスの最初の要素から数えた、元となる配列の要素数です。cap(s)で算出できます。

下記のようなイメージです。

f:id:sagantaf:20200120000341p:plain

スライスはこのcapacityを持ったまま定義されているので、再スライスして長さを変えることができます。下記がその例です。

func main() {
        s := []int{1, 2, 3, 4, 5} // len=5, cap=5
        s = s[:0] // len=0, cap=5
        s = s[:4] //上記でスライスを一度0個にしているがcapは6のままなので再スライスできる
        printSlice(s) // len=4, cap=5
        s = s[2:6] //先頭を変えるとcapは減る
        printSlice(s) // len=4, cap=4
}
func printSlice(s []int) {
        fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

Sliceへの新しい要素の追加

スライスへ新しい要素を追加するには append 関数を使います。ただし、既存のスライスに追加するのではなく、新しいスライスがreturnされます。

基本的には

new_slice = append(old_slice, 追加する値, 追加する値, ...)

として使います。

追加していくことでcapは自動的に増えていきます。

func main() {
        var s []int
        printSlice(s) // len=0 cap=0 []
        s = append(s, 0, 1, 2)
        printSlice(s) // len=3 cap=4 [0 1 2]
}
func printSlice(s []int) {
        fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

make関数でSliceを作成できる

make 関数を利用してスライスを作成することもできます。

下記のように宣言することで、0で初期化されたスライスが生成されます。なお、capacity部分は省略可能です。

a := make([]type, length, capacity)

例えば以下のように使います。

a := make([]int, 5) // len=5 cap=5 [0 0 0 0 0]
b := make([]int, 0, 5) // len=0 cap=5 []
// -> とりあえずcapだけ決めておいてlenは必要な時に決めることができる
c := b[:2] // len=2 cap=5 [0 0]


RangeとMaps

Range

反復処理を実装するためにrangeが利用できます。

例えばスライスの要素を1つずつ取り出して処理をさせたい時には、rangeが使えます。イテレーションごとにindexとvalue(要素値のコピー)を返します。

var num_slice = []int{10, 100, 1000, 10000}
func main() {
        for i, v := range num_slice {
                fmt.Println(i, v)
        }
}

下記のようにprintされます。

0 10
1 100
2 1000
3 10000

indexかvalueどちらかだけ利用することも可能です。

for i := range num_slice { fmt.Println(i) } // vを省略
for _, v := range num_slice { fmt.Println(v) } // iを省略

Maps

mapの基本

mapはkeyとvalueをセットで保存する辞書のようなものです。

var 変数名 = map[keyの型]valueの型{key1:value1, key2:value2, …}

例えば、下記のように作成します。

var m = map[string]int{"first":1, "second":2, "third": 3}

また、makeを使っても作成できます。

make(map[keyの型]valueの型, capacityの初期値)
make(map[keyの型]valueの型)// capacityの初期値は省略可

var m = make(map[string]int, 3) // map[]となる
m["first"] = 1
m["second"] = 2
fmt.Println(m) // map[first:1 second:2]

mapの操作

追加や削除は以下のように実行します。

m["Answer"] = 42 //要素の追加
m["Answer"] = 48 //要素の変更
delete(m, "Answer") //要素の削除

キーの存在チェックもできるため、キーがあれば処理を実行する、なければしない、などの操作が可能になります。

v, ok := m["Answer"] 

上記の例だと、keyが存在すればokにtrueが入り、なければfalseが入ります。falseの場合はvには型のゼロ値が入ります。(intだと0, stringだと空白、など)

参考