その1の続き
データ
ユーザー定義型
C++のように組み込み型(int, float, string)などをユーザーの好きな型名に置き換えること(要はtypedefやusing)が可能となっている。
置き換える場合typeキーワードを使う。
type 型名 置き換える型
例えばint型をMyInt型として扱いたい場合
type MyInt int
var myInt MyInt = 0
この様にすることでMyInt型という新しい整数型を定義できる。
Go言語は型に関して厳密な言語で例えint型を別型にしたMyInt型であってもMyInt型の変数にint型の変数を代入するとエラーになる。
var myInt MyInt = 0
var intValue int = 3
myInt = intValue // エラー
myInt = MyInt(intValue) // 代入する場合キャストをする必要がある
ポインタ型
ポインタ型は変数へのアドレス参照をする型になる。
初期化されてないポインタ型はnil
ポインタ型は型名の前に*を付けて定義する。
ポインタ型に値型を参照させる場合頭に&をつければアドレスに変換される。(C言語と同じと覚えて良さそう)
var value int = 5
var pointer *int = &value
配列
配列を利用する場合
var 変数名 [サイズ]型
と記載する。
配列の初期化をする場合{}を使う。初期化を行わない場合はデフォルト値が入る。
var array [5]int = [5]int{ 1, 2, 3, 4, 5 }
array1 := [5]int{ 1, 2, 3, 4, 5 }
array2 := [...]int{ 1, 2, 3 } // この様にサイズを省略することも可能
Go言語の配列は連続したメモリ領域を確保するC言語の配列と同じデータ構造となっている。
また配列間のコピーも可能でarray1に対してarrayを代入した場合arrayの中身がarray1に対して コピーされる
配列はサイズの情報を持たないが、 lenを利用することで配列の長さを取得できる。
また、 rangeを使うことでも配列の全要素に対してアクセスすることができる。
// lenを利用してのfor
for i := 0; i < len( array ); i++ {
fmt.Println( array[i] )
}
// rangeを使用してのfor
for i, v := range array{
fmt.Println( fmt.Sprintf( "index:%d value:%d", i, v ) )
}
多次元配列を利用する場合[]の数を増やせば定義することができる。
2次元配列を宣言する場合
var matrix [3][3]int = [3][3]int{
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 },
}
fmt.Println( matrix )
for i, array := range( matrix ){
for j, value := range( array ) {
fmt.Println( fmt.Sprintf( "index[%d][%d] value:%d", i, j, value ) )
}
}
こうなる
スライス
スライスは配列をラップして、データ列に対して使いやすいインターフェースを提供する。
配列と違い参照型になるため、別のスライスを代入した場合コピーされずに同じメモリ領域を参照するようになる。
スライスの定義方法として
- 配列の割当
s := array[開始位置:終了位置] - リテラル
s := []型{型リテラル} - makeを使う
s := make([]型, サイズ, キャパシティ )
の3つの方法がある。
それぞれを実際に書くと下のようになる
array := [5]int{ 1, 2, 3, 4, 5 }
slice1 := array[:] // arrayの領域を参照する。
slice1[0] = 5
fmt.Println( "array ", array ) // array[ 5, 2, 3, 4, 5 ]となる
slice2 := []string{ "slice1", "slice2", "slice3" }
fmt.Println( "slice ", slice2 )
slice3 := make([]int, 10 ) // 長さ10のint配列を宣言
配列割当は実際の配列だけでなくスライスを割当に利用しても良い。
また配列割当の場合全体ではなく配列の一部をスライスに割当することができ、開始位置から終了位置-1までを参照する。
array := [5]int{ 1, 2, 3, 4, 5 }
slice1 := array[1:3]
とした場合 arrayの指定インデックス1から2のメモリ空間を参照する。
スライスの参照先を変更するのではなく、参照しているしているメモリ空間に値をコピーする場合 copyを使う。
slice := make([]int, 2)
copy(slice, array[1:3])
スライスは可変長配列としても取り扱うことができ(ほぼC++のvectorと同じだが)、定義したサイズを増やすことができる。
サイズを増やす場合 appendを使う。
slice1 := make([]int, 3)
fmt.Println( slice1 )
slice1 = append( slice, 10 ) // サイズを増やしたsliceを返す
slice1 = append( slice, 1, 2, 3 ) // この様に復数一気に増やすことも出来る。
slice2 := make( []int, 3 )
slice2 = append( slice2, slice1... ) // スライスの後ろに他のスライスを追加することもできる。
appendは第一引数にスライス 第二引数以降に増やす値を可変長引数で指定を行い指定数だけ増やすことができる。
第二引数に配列やスライスを指定する場合だけ 違う書き方になるので注意。
また、appendでサイズを増やす場合キャパシティを超える領域の確保をしようとするとメモリの再確保が走るため負荷が上がる。
この辺はC++のvectorも一緒だが、配列の定義として連続したメモリ空間の領域に確保する仕様のためしょうが無い。
マップ
マップは連想配列と同じものである。
初期化されてないマップ型はnilとなる。
連想配列のよくある使われ型として文字列型をキーにする使い方だが、Go言語ではキー型の比較演算子==, !=が存在していればキーにすることが可能となっている。
var map1 map[string]int = map[string]int{ "1" : 1, "2" : 2 }
map1["3"] = 3
map2 := map[string]int{}
map3 := make(map[string]int, 4) // スライスと違い第二引数はキャパシティになる
動的割当
メモリ割り当ての方法として newとmakeという2つの組み込み関数が用意されている。
- new
new(T型)として呼び出す。
戻り値は*T型という引数に指定した方のポインタ型で戻り値となる。
value := new(int)
newでメモリ割当を行った場合、メモリ確保だけでなくゼロ化(初期化)も行う。
-
make
makeはnewと同じく動的メモリ割当をするものだが、利用目的が異なる。
makeによる割当はスライス、マップ、チャネルだけ利用することができる。
これらの型がnewと分かれている理由は、隠蔽されたデータ構造への参照のためnewの初期化で行われる内容だとnilとなってしまうため別で使用可能にするための関数が存在する。
また戻り値もnewとは違いポインタ型ではない値を返す。
構造体
Go言語にはクラスの概念がなく、復数のデータ型を持った構造を扱いたい場合構造体を利用します。
var 変数名 struct{
フィールド
}
これで復数の変数を纏めて扱う構造体が定義できたが、これだと同じ構造体を利用する時に冗長になり、型としても違う物になるのでこの構造体を型として定義して扱いやすくしてやる。
type 型名 struct{
フィールド
}
var structValue 型名
typeキーワードで構造体を型として定義して、この型の変数が再利用可能な形で定義できる。
初期化
構造体を初期化する場合復数のパターンがあります。
type StructTest struct{
value1 int
value2 int
}
// フィールドに設定
var structTest StructTest
structTest.value1 = 1;
structTest.value2 = 2;
// 初期化リスト指定
structTest2 := StructTest{ 5, 4 }
fmt.Println( structTest2.value1 ) // 5
fmt.Println( structTest2.value2 ) // 4
// 初期化リスト指定 フィールド指定
structTest3 := StructTest{ value1:5, value2:4 }
fmt.Println( structTest3.value1 ) // 5
fmt.Println( structTest3.value2 ) // 4
変数定義後にフィールドを指定する方法、初期化リストを指定する方法、初期化リストでフィールド指定する方法があります。
2番目の初期化リスト指定の場合変数の定義順番(今回であればvalue1, value2)でパラメータが初期化されてます。
type StructTest struct{
str string
value1 int
value2 int
}
structTest2 := StructTest{ 5, 4 } // エラー
fmt.Println( structTest2.str )
fmt.Println( structTest2.value1 )
fmt.Println( structTest2.value2 )
先ほどと違いこの様に構造体のフィールドの一番最初に文字列の変数を追加した場合パラメータリストを指定すると、型が違うためにエラーとなります。