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

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

プログラミング Go言語 / golang Go言語入門 / golang 入門

f:id:nasust:20161122141513p:plain The Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/) CC3.0

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



型の一覧

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

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

論理型

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

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

数値型

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

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

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

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

この中で特に使用するのがint型です。ソースコードの数値は暗黙で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
float32 IEEE-754 32bit 浮動小数点数
float64 IEEE-754 64bit 浮動小数点数
complex64 float32の実数部と虚数部を持つ複素数
complex128 float64の実数部と虚数部を持つ複素数
byte uint8の別名(エイリアス)
rune int32の別名(エイリアス)

文字列型

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

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

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

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

例:

a := "abcdefg"
b := a

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

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

関数型

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

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

hogeFunc()

構造体型

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

例:

struct {
    x , y int
}

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

例:

type Hoge struct {
    x , y int
}

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

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

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

Goを学びたての人が誤解しがちなtypeと構造体について #golang - Qiita

メソッド群

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

type Hoge struct{
}

func (self *Hoge) Fugo(){
}

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

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

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

type HexInt int

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

以下の記事で構造体とメソッド群でオブジェクト指向っぽい事ができることを解説しています。 Go言語入門 / golang 入門 Go言語 / golangはオブジェクト指向っぽい事が出来る - nasust blog

インターフェイス

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

type HogeInterface interface{
    Fugo()
}


type Hoge struct{
}

func (self *Hoge) Fugo(){
}

func HasFugo( hogeIf HogeInterface ){
}

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

以下の記事でインターフェイス型でオブジェクト指向の多様性など実現できることを解説しています。 Go言語入門 / golang 入門 オブジェクト指向の多様性と継承はどうなるのか - nasust blog

マップ型

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

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

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

配列型

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

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

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

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

スライス型

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

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

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

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

例:

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

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

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

余談ですが、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 //送信

ポインタ型

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

例えばスクリプト

hoge = Hoge.new()

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

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

値型と参照型と負荷

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

type Fugo struct{

}

func hoge( fugo Fugo ){
}

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

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

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

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

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

Goでxxxのポインタを取っているプログラムはだいたい全部間違っている - Qiita


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


Go言語 / golang 入門 目次に戻る

広告を非表示にする