型とポインターを分かりやすく解説。値型と参照型についても解説しています

created byTakuya Ueda. Licensed under the Creative Commons 3.0 Attributions license.

こんにちはnasustです。 PHPやRubyからGo言語 / golangを学ぶと型とポインターについて戸惑うと思います。 今回は型とポインターを分かりやすく解説したいと思います。

型の一覧

Go言語 / golangには以下の型があります。

  • 論理型
  • 数値型
  • 文字列型
  • 関数型
  • 構造体型
  • ポインター型
  • インターフェイス型
  • マップ型
  • 配列型
  • スライス型
  • チャンネル型

論理型

これは分かりやすいと思います。

  • true ⋯ 真を表す値
  • false ⋯ 偽を表す値

数値型

これはスクリプトから学んできた方には分かりにくいと思います。 スクリプト言語でもさまざまな数値型がありますが、暗黙で変換されるので、 意識することなく使用できます。

しかしGo言語では数値型は暗黙で変換しない為に注意が必要です。

実行環境に依存する数値型

以下の型は実行環境に依存します。32bit cpuである場合は32bitの大きさで、 64bitのcpuである場合は64bitの大きさの数値型になります。

この中で、とくに使用するのがint型です。

int符号あり32bit又は64bit整数-2147483648 〜 2147483647 又は -9223372036854775808 〜 9223372036854775807
uint符号無し32bit又は64bit整数0 〜 4294967295 又は 0 〜 18446744073709551615
uintptr符号無し32bit又は64bit整数ポインタ、つまりメモリのアドレスを格納する符号無し整数です。

実行環境に依存しない数値型

int8符号あり8bit整数-128 〜 128
int16符号あり16bit整数-32768 〜 32678
int32符号あり32bit整数-2147483648 〜 2147483647
int64符号あり64bit整数-9223372036854775808 to 9223372036854775807
uint8符号無し8bit整数0 〜 256
uint16符号無し16bit整数0 〜 65535
uint32符号無し32bit整数0 〜 4294967295
uint64符号無し64bit整数0 〜 18446744073709551615
float32IEEE-75432bit浮動小数点数
float64IEEE-75464bit浮動小数点数
complex64float32の実数部と虚数部を持つ複素数
complex128float64の実数部と虚数部を持つ複素数
byteuint8の別名(エイリアス)
runeint32の別名(エイリアス)

文字列型

文字列型はbyteのスライス型のように振る舞います。

例:[ 'a' , 'b' , 'c' , 'd' ] == "abcd"

値は不変で、一度作成すると変更することはできません。文字列の連結などの操作する関数では新しい文字列型を作成して返します。

文字列型は、文字の実態と長さを持っている参照型です。

例:

a := "abcdefg"
b := a
go

この場合、b変数は文字列の参照と長さをa変数からコピーされています。 参照がコピーされただけなので、文字列の実態はコピーされてません。

文字列の長さは、len関数で数える事ができますが、それはバイト長であるので、文字数ではありません。 文字数を数える場合は、utf8パッケージのRuneCountInString関数を使用します。

関数型

関数型はJavaScriptのクロージャっぽく変数に関数を入れる事ができます。

hoge := 10
hogeFunc := func(){
    fmt.Println( "Hoge :" , hoge )
}

hogeFunc()
go

構造体型

構造体はフィールドと呼ばれる要素の集まりでで、それぞれが名前と型を持っています。

例:

struct {
	x , y int
}
go

上記の例で構造体の定義でできますが、使用し辛いので型宣言で名前を付けます。

例:

type Hoge struct {
	x , y int
}

hoge := Hoge{x:10,y:20 }
go

typeは型宣言で型に名前を付けることができます。

typeについては以下の記事が分かりやすいです。

メソッド群

構造体にメソッドを定義することで、オブジェクト指向のクラスっぽいメソッドと同じような事ができます。

type Hoge struct{
}

func (self *Hoge) Fugo(){
}

hoge := &Hoge{}
hoge.Fugo()
go

関数の名前の前に(self *Hoge)というレシーバーを書くことでメソッド群を定義できます。

メソッド群は構造体意外でも定義できます。

type HexInt int

func (self *HexInt) PrintHex{
    fmt.Printf("Hex:%X" , *self );
}
go

インターフェイス型

インターフェイス型はメソッド群を定義します。Javaのinterfaceの様なもので、 インターフェイス型で定義されたメソッド群を満たしていたら、その型は、 インターフェイス型を実装していると言えます。

type HogeInterface interface{
    Fugo()
}


type Hoge struct{
}

func (self *Hoge) Fugo(){
}

func HasFugo( hogeIf HogeInterface ){
}

hoge := &Hoge{}
HasFugo(hoge) //Hoge構造体はHogeInterfaceのメソッド群を満たしているので渡せる
go

マップ型

マップ型は、型の要素の順序を持たない集合で、ユニークなキーにより関連づけます。

map[string]int //stringをキー、値をintとするマップ
map[string]*Hoge //stringをキー、値をHoge構造体のポインターとするマップ
go

キーの型は==!=の比較演算子が実装されている必要があります。

配列型

配列型は固定長の配列です。スクリプト言語の様に長さが可変ではありません。 可変長にする場合はスライス型を使用します。

[10]int //int型の10の長さの配列
[10]string //string型の10の長さの配列

intArray := [10]int{1,2,3,4,5,6,7,8,9}
i := intArray[0]
go

例の様に他の言語と同じ様に配列をアクセスできます。

スライス型

スライス型は配列型の定義と非常に似ています。

[]int //int型の可変長のスライス型
[]string //string型の可変長のスライス型
go

スライス型は、配列の参照と長さ、キャパシティを持っています。 配列の参照は、配列の実態を指しています。 配列の長さは、配列に要素が格納されている数を表しています。 キャパシティは、現在の配列で格納できる大きさを表しています。

スライス型は配列と同じ様にアクセスできますが、注意点があります。 配列の参照は、キャパシティを超えて要素を追加すると変わります。 つまりキャパシティを超えると新しい配列を用意して格納します。

例:

s := []int{ 1 , 2 } // キャパシティは4だとします。
s = append( s , []int{ 3 , 4 , 5 } ... ) //appendでキャパシティを超えます。
cap(s) //キャパシティは8になります。
go

キャパシティを超えると、倍の大きさの配列を確保します。このタイミングで、 スライスの配列の参照は新しい配列を指します。

なぜ、このような挙動をするのかというと、あらかじめ余分に大きい配列を確保することによって、 要素の追加を効率的にします。

余談ですが、C++のvector型も同じような動作します。

チャンネル型

チャンネル型とはGo言語 / golangのマルチスレッドの機能を利用する型です。 CSP (Communicating Sequential Processes - Wikipedia)の 影響を受けた機能です。

チャンネル型は、ソケットの通信の様にメッセージを送信したり受信できます。

ch := make(chan int)

go func() { //別スレッドで実行
    for out := range ch { //chを受信
        fmt.Println("ch: ", out)
    }
}()
ch <- 10 //送信
go

ポインター型

PHPやRubyからGo言語 / golangを学びに来た場合、ポインターという概念が一番障害になります。 とは言っても、PHPやRubyなのどスクリプトは、このポインターを暗黙の内に使用しています。

たとえばスクリプトで

hoge = Hoge.new()
ruby

は、Hogeインスタンスのポインターをhogeに保存しているようなものです。 色々な参考書でも、このような機能を「参照を返している」と説明していると思います。 ポインターは、これと同じような機能です。

i  := 10
j  := i //iの値をコピー
jp := &i //iのポインターを保存。iの参照を保存しているとも言い換えることも出来ます。
go
hoge := Hoge{x:10,y:20} //構造体をコピーしている
hogeP := &Hoge{x:10,y:20} //構造体のポインターを保存している。Hoge.new()相当
go

値型と参照型と負荷

たとえば、関数の引数に構造体の値を指定します。 その場合、関数実行の度に構造体がコピーされる為に負荷が掛かります。

type Fugo struct{

}

func hoge( fugo Fugo ){
}
go

これを解決するには、func hoge( fugo *Fugo )とポインターの引数にします。 しかし何でもポインターにするのでも、負荷が掛かります。

Go言語 / golangの型は値型と参照型の2つあり、それを考慮して関数の引数の型をポインター型または値型にします。 値型と参照型は以下の通りです。

  • 値型
    • 数値
    • 構造体
    • 配列
  • 参照型
    • 文字列
    • インターフェイス
    • チャンネル
    • マップ
    • スライス

値型は関数に渡す場合コピーされるので値型の方が負荷掛かります。 ただし、64bit環境では数値型のほとんどがポインター型以下のサイズですので、その場合はコピー渡しで負荷は掛かりません。 構造体と配列をポインター型で関数に渡した方が負荷は掛かりません。

参照型は、実態のポインターを持っているので、これを関数にポインター渡しするのは、 つまりポインターのポインターを関数に渡すようなものです。その為、余計な負荷が掛かります。

下記の記事は適切なポイント型と参照型の使用の仕方を解説しています。


参考資料:Goプログラミング言語仕様 - golang.jp

prevnext