Go

Go言語の基本文法3(配列, スライス, マップ, 構造体)

こんにちは、イサムです!

本記事では、前回に引き続きGo言語の基本構文についてまとめます。

対象読者
  • Go言語の基本構文について知りたい!
この記事でやること
  • Go言語の基本文法についてのまとめ

配列

配列とは、同じデータ型を集めて並べたデータ型ものです。
各データのことをそれぞれ要素と呼び、各要素はすべて同じ型である必要があり、配列の定義時に指定した要素数は変更できません。
そのため、後で配列にデータを追加することはできません。
また、添字(インデックス)で各要素にアクセスすることができます。

配列の宣言方法

  1. var 変数名 [要素数]型
  2. var 変数名 = [要素数]型{値、…}
  3. 変数名 := []型 {}
  4. 変数名 := []型 {値、…}
  5. 変数名 := […]型 {値、…}

宣言時に初期値を指定しなかった場合、指定したデータ型のゼロ値で初期化されます。
また、宣言時に要素数をしていなかった場合(要素数を「…」で宣言)、代入した値の要素数で推論します。

// ①配列の宣言とゼロ値での初期化
var array1 int
println(array1) // 0 0 0 0

// ②配列の宣言と値代入での初期化
var array2  = int{1, 2, 3, 4}
println(array2) // 1 2 3 4

// ③varを省略した配列の宣言とゼロ値での初期化
array3 := int{}

// ④varを省略した配列の宣言と値代入での初期化
array4 := int{1, 2, 3, 4}

// ⑤要素数推論
array5 := [...]int{1, 2, 3, 4}
println(len(array5)) // 4

配列の操作方法

要素取得

取得対象の配列[取得対象の添字]

配列の添字を指定することで、要素の値を取得することができます。

array := int{1, 2, 3, 4}

// 配列の要素にアクセス
println(array[0]) // 1 

要素更新

更新対象の配列[更新対象の添字] = 更新する値

配列の添字を指定し、更新する値を代入することで指定した要素の値を更新することができます。

array := int{1, 2, 3, 4}

array[0] = 10
// 配列の要素にアクセス
println(array[0]) // 10

長さ取得

len(取得対象の配列)

len関数で配列の長さを取得することができます。

array := int{1, 2, 3, 4}

// 配列の長さを取得
println(len(array)) // 4

スライス

スライスとは、可変長配列のことで、ある配列の一部を取り出すことも可能です。
スライスは参照型なので、ある配列から切り出したスライスの値を変更すると参照先の配列の値も変更されてしまいます。
また、スライスのゼロ値は、nilになります。

長さと容量

スライスは長さと容量を持っており、長さは、現在の要素数、容量は、切り出しの開始位置から最後までの要素数になります。

スライスの宣言方法

  1. var 変数名 []型
  2. var 変数名 = []型{値、…}
  3. 変数名 := []型 {}
  4. 変数名 := []型 {値、…}
  5. 変数名 := make([]型, 長さ, 容量)
  6. 変数名 := make([]型, 長さ)
  7. 変数名 := 配列[開始添字:終了添字]

配列と違い要素数の指定は不要です。
また、宣言時に初期値を指定しなかった場合、nilに初期化されます。
make関数を使用して動的にスライスを作成すると

  • データ型の初期値が代入される
  • 容量を省略すると長さと同じになる
  • 長さが容量を超えてはいけない

ある配列の部分配列のスライスを作成した場合、開始添字~終了添字-1の要素を取得し、取得方法は以下のようになります。

操作説明
s := array[start:end]startからend-1までの要素を取得
s := array[start:]startから最後尾までの要素を取得
s := array[:end]先頭からend-1までの要素を取得
s := array[:]先頭から最後尾までの要素を取得
// ①スライスの宣言とnilで初期化
var slice1 []int
fmt.Println(slice1) // []

// ②スライスの宣言と初期値で初期化
var slice2  = []int{1, 2, 3, 4}
fmt.Println(slice2) // 1 2 3 4

// ③varを省略したスライスの宣言とnilで初期化
slice3 := []int{}

// ④varを省略したスライスの宣言と初期値で初期化
slice4 := []int{1, 2, 3, 4}

// ⑤makeを使ったスライスの初期化
slice5 := make([]int, 4, 5)

// ⑥makeを使ったスライスの初期化(容量省略)
slice6 := make([]int, 4)

// ⑦配列の一部を切り出してスライスを宣言
arr := int{1, 2, 3}
slice7 := arr[0:3]

スライスの操作方法

値取得

取得対象のスライス[取得対象の添字]

スライスの添字を指定することで、要素の値を取得することができます。

slice := []int{1, 2, 3, 4}

// スライスの値取得
println(slice[0]) // 1 

値更新

更新対象のスライス[更新対象の添字] = 更新する値

スライスの添字を指定し、更新する値を代入することで指定した要素の値を更新することができます。

slice := []int{1, 2, 3, 4}

slice[0] = 10

// スライスの値取得
println(slice[0]) // 10

長さと容量取得

長さ: len(取得対象のスライス)
容量: cap(取得対象のスライス)

len関数でスライスの長さ、cap関数でスライスの容量を取得することができます。

arr := int{1, 2, 3, 4}
slice := arr[0:3]

// スライスの長さを取得
println(len(slice)) // 3

// スライスの容量を取得
println(cap(slice)) // 4

要素追加

新規スライス = append(追加対象のスライス, 追加する値1, 追加する値2, …)

配列は宣言時に要素数が固定されるため要素の追加はできませんが、スライスは宣言後にappend関数を使用することで要素の追加ができます。
ただし、append関数は新しいスライスを返すことになるので元のスライスに追加されません。

slice := []int{1, 2, 3, 4}
newSlice := append(slice, 10)
fmt.Println(newSlice) // 1 2 3 4 10

// 追加元が変更されていないことの確認
fmt.Println(slice) // 1 2 3 4

コピー作成

copy(コピー先スライス, コピー元スライス)

copy関数でスライスのコピーを作成することができます。
参照ではなく、コピーなので、コピー先の要素を変更しても、コピー元の要素は変更されません。

slice1 := []int{1, 2, 3, 4}
slice2 := make([]int, 4, 4)

// スライスのコピーを作成
copy(slice2, slice1)

// コピー先の要素を変更
slice2 = 10
fmt.Println(slice2) // 1 2 3 10

// コピー元が変更されていないことの確認
fmt.Println(slice1) // 1 2 3 4

代入

コピー先のスライス = コピー元のスライス

スライスの型が一致している場合、スライスを代入することができます。
ただし、代入先のスライスは代入元の値を参照しているため、代入先の要素を変更すると代入元のスライスの要素も変更されます。

slice1 := []int{1, 2, 3, 4}
slice2 := []int{}

// スライスを代入
slice2 = slice1

// 代入先の要素を変更
slice2 = 10
fmt.Println(slice2) // 1 2 3 10

// 代入元が変更されてることの確認
fmt.Println(slice1) // 1 2 3 10

マップ

マップとは、連想配列のことで、キーと値をマッピングさせているデータ構造になります。
配列では、整数値の順番の添字でアクセスすることができましたが、マップの場合は、各値にキーを割り当て、そのキーで値にアクセスすることができます。
また、スライスのゼロ値は、nilになります。

マップの宣言方法

  1. var 変数名 map[キーの型]値の型
  2. var 変数名 = map[キーの型]値の型{値、…}
  3. 変数名 := map[キーの型]値の型 {}
  4. 変数名 := map[キーの型]値の型 {値、…}
  5. 変数名 := make(map[キーの型]値の型, 容量)
  6. 変数名 := make(map[キーの型]値の型)

宣言時に初期値を指定しなかった場合、nilに初期化されます。
①の場合、容量が確保されないため、マップにキーと値を追加しようとするとパニックが発生するので注意が必要になります。③の場合は、容量が確保されるため、マップへのキーと値の追加が可能になります。
make関数を使用して動的にマップを作成すると

  • データ型の初期値が代入される
  • 容量を省略すると要素数と同じになる
// ①マップの宣言とnilでの初期化
var map1 map[string]int
fmt.Println(map1) // map[]

// ②マップの宣言と初期値での初期化
var map2  = map[string]int{"a":1, "b":2, "c":3, "d":4}
fmt.Println(map2) // map[a:1 b:2 c:3 d:4]

// ③varを省略したマップの宣言とリテラルでの初期化
map3 := map[string]int{}

// ④varを省略したマップの宣言と初期値で初期化
map4 := map[string]int{"a":1, "b":2, "c":3, "d":4}

// ⑤makeを使ったマップの初期化
map5 := make(map[string]int, 4)

// ⑥makeを使ったマップの初期化(容量省略)
map6 := make(map[string]int)

マップの操作方法

値取得

取得対象のマップ[取得対象のキー]

マップのキーを指定することで、2つの値を取得でき、1つ目は、要素の値、2つ目は、要素が存在しているかのbool値(存在する:true/存在しない:false)を取得することができます。
指定したキーの要素が存在しない場合、値はマップ宣言時に指定した型の初期値を取得します。

map1 := map[string]int{"a":1, "b":2, "c":3, "d":4}

// マップの値取得
v1, has1 := map1["a"]
if has1 {
  println(v1) // 1 
}

// 存在しないキー
v2, has2 := map1["e"]
println(v2) // 0
println(has2) // false

要素数取得

len(取得対象のマップ)

len関数でマップの要素数を取得することができます。

map1 := map[string]int{"a":1, "b":2, "c":3, "d":4}

// マップの要素数取得
println(len(map1)) // 4

要素追加

追加対象のマップ[追加するキー] = 追加する値

make関数で作成したマップに要素を追加することができます。

map1 := make(map[string]int)

// mapに要素を追加
map1["a"] = 1
fmt.Println(map1) // map[a:1]

要素更新

更新対象のマップ[更新する要素のキー] = 更新する値

指定したキーのマップの要素の値を更新することができます。

map1 := map[string]int{"a":1, "b":2, "c":3, "d":4}

// mapの要素を更新
map1["a"] = 10
fmt.Println(map1) // map[a:10 b:2 c:3 d:4]

要素削除

delete(削除対象のマップ, 削除する要素のキー)

delete関数を使用することで、マップの要素を削除することができます。

map1 := map[string]int{"a":1, "b":2, "c":3, "d":4}

// mapに要素を追加
delete(map1, "a")
fmt.Println(map1) // [b:2 c:3 d:4]

Range

配列、スライス、マップの要素を1つずつ取り出し、繰り返し処理をするために使用します。

配列とスライスの繰り返し処理

配列、スライスをrangeキーワードを使用して繰り返し処理をする場合、2つの変数を取得でき、1つ目は、インデックスで、2つ目は、そのインデックスの値を取得できます。

// 配列の繰り返し処理
array := [...]int{1, 2, 3, 4}
for i, v := range array {
  fmt.Println(i, v)
}

// スライスの繰り返し処理
slice := []int{1, 2, 3, 4}
for i, v := range slice {
  fmt.Println(i, v)
}

マップの繰り返し処理

マップをrangeキーワードを使用して繰り返し処理をする場合、配列、スライスと同様に2つの変数を取得でき、1つ目は、インデックスで、2つ目は、そのインデックスの値を取得できます。
ただし、繰り返しする順番は決まっていないため、実行する度に変更されます。

map1 := map[string]int{"a":1, "b":2, "c":3, "d":4}

for i, v := range map1{
  fmt.Println(i, v)
}

構造体

構造体とは、データ型が異なる変数を集めることができるデータ型のことです。各変数はフィールドと呼ばれ、各変数の型は同じでも異なっていても大丈夫です。
また、各フィールドの型には、組み込み型、ユーザー定義型が使用できます。ただし、型リテラルを使用することはできません。

構造体の宣言方法

元々型定義なし

  1. var 変数名 struct {フィールド1: 型 フィールド2: 型 …}
  2. var 変数名 = struct {フィールド1: 型 フィールド2: 型 …} {フィールド1: 値, フィールド2: 値, …,}
// ①構造体の定義
var user1 struct {
  name string
  age int
}

// ②構造体の定義と初期化
var user2 = struct {
  name string
  age int
} {
  name: "Osamu";,
  age: 20,
}

元々型定義あり

type 型名 struct{フィールド1 フィールド2 …}で構造体の型定義ができます。

  1. 構造体の型定義後、変数定義した後、フィールドの値を設定
  2. 構造体の型定義後、変数定義とフィールドの初期値を設定
  3. 構造体の型定義後、変数定義とフィールドの初期値を設定(フィールド省略)
  4. 構造体の型定義後、ポインタ型で変数定義(new関数)
  5. 構造体の型定義後、ポインタ型で変数定義
  6. 構造体の型定義後、ポインタ型で変数定義とフィールドの初期値を設定
  7. 構造体の型定義後、ポインタ型で変数定義とフィールドの初期値を設定(フィールド省略)

「.」を使用することで、定義した構造体のフィールドにアクセスすることができます。
フィールドを省略した場合、構造体の型定義した順番に、初期値を定義する必要があります。

// 構造体の型定義
type User struct {
  name string
  age int
}

// ①変数定義後、フィールドの値を設定
var user1 User
user1.name= "Osamu1";
user1.age = 10

// ②変数定義とフィールドの初期値を設定
user2 := User{name: "Osamu2", age: 20}

// ③変数定義とフィールドの初期値を設定(フィールド省略)
user3 := User{"Osamu3", 30}

// ④ポインタ型で変数定義(new関数)
user4 := new(User)

// ⑤ポインタ型で変数定義
user5 := &User{}

// ⑥ポインタ型で変数定義とフィールドの初期値を設定
user6 := &User{name:"Osamu6", age:10}

// ⑦ポインタ型で変数定義とフィールドの初期値を設定(フィールド省略)
user7 := &User{"Osamu7", 10}

コンストラクタ関数の使用

コンストラクタ関数(初期化関数)を使用して、構造体を初期化できます。

type User struct {
  name string
  age int
 }

// コンストラクタ関数
func NewUser(name string, age int) *User{
  return &User{name: name, age: age}
}

func main() {
  user := NewUser("Osamu", 10)
}

構造体の使用

構造体とスライス

定義した構造体の型をスライスに適用することができます。

type User struct {
  name string
  age int
}

// 構造体Userのスライス
type Users []*User

func NewUser(name string, age int) *User {
  return &User{name: name , age: age}
}

func main() {
  user1 := NewUser("user1", 10)
  user2 := NewUser("user2", 20)
  user3 := NewUser("user3", 30)

  users := Users{}
  users = append(users, user1, user2, user3)
  for _ , v := range users {
    fmt.Println(*v)
  }

  users2 := make(Users, 0)
  users2 = append(users2, user1, user2, user3)
  for _ , v := range users {
    fmt.Println(*v)
  }
}

構造体とマップ

定義した構造体の型をマップに適用することができます。

type User struct {
  name string
  age int
}

func main() {
  // マップの値の型に構造体
  users1 := map[int]User {
    1: {name: "user1", age: 10},
    2: {name: "user2", age: 20},
  }
  for i , v := range users1 {
    fmt.Println(i,v)
  }

  // マップのキーの型に構造体
  users2 := map[User] string{
    {name: "user1", age: 10}: "Tokyo",
    {name: "user2", age: 20}: "LA",
  }
  for i , v := range users2 {
    fmt.Println(i,v)
  }
}

構造体の埋め込み

構造体のフィールドに構造体を定義することもでき、フィールドに定義した構造体のフィールドやメソッドにアクセスすることができます。
メソッドの章でも記述した通り、Go言語には、クラスという概念は存在しません。そのため、この構造体内の埋め込みを使用することでクラスの継承に似た振る舞いをすることができます。

type name struct{
  firstName string
  lastName string
}

func (n *name) ShowName() {
  fmt.Println(n.firstName , n.lastName)
}

// 構造体nameを埋め込み
type Person struct {
  name name
  age int
}

func NewPerson(firstName string, lastName string, age int) *Person {
  return &Person{name: name{firstName, lastName}, age: age}
}

func main() {
  p := NewPerson("Osamu", "Tanaka", 20)
  p.name.ShowName()
}

構造体のタグ

構造体にはタグによって実行時に参照可能なメタ情報を付与することができます。

タグの記述方法

タグの記述方法は、文字列または文字列をバッククォートで囲んだRAW文字列で記述することができますが。基本的には、RAW文字列で記述します。
構造体の1つのフィールドに対して、複数のタグを定義することや1つのタグで複数の値を定義できます。
タグを記述するにあたって下記のような注意点があります。

  • タグのキーと値の区切りのコロンの後に空白は入れない
  • 複数のタグを定義する場合は、空白を入れてから次のタグを定義する
  • 1つのタグで複数の値を定義する場合、カンマで区切りその後に空白は入れない
文字列
type User struct {
  Name string "名前"
  Age int "年齢"
}
RAW文字列
type User struct {
  Name string `json:"name" validate:"required,IsExists"`
  Age int "年齢" `json:"age" validate:"required,IsExists"`
}

タグの用途

json

jsonパッケージで構造体をJSON形式にエンコード、JSON形式を構造体にデコードする際に、jsonタグを参照してキー名として利用します。

query

echoライブラリを使用する場合、構造体のタグを利用して構造体にタグ名と一致したクエリパラメータをマッピングできます。

param

echoライブラリを使用する場合、構造体のタグを利用して構造体にタグ名と一致したパスパラメータをマッピングできます。

validate

validatorライブラリを使用する場合、構造体のタグを利用して構造体にマッピングした値をバリデーションできます。
デフォルトで設定されているタグだけでなく、自作のタグを作成し、バリデーション処理を記述することもできます。

xorm

xormライブラリを使用する場合、構造体のタグにを利用して構造体にタグ名と一致したDBの列名の取得データをマッピングできます。

まとめ

この記事では、前回に引き続きGo言語の基礎文法をまとめました。

少しGo言語をいじってみたいと思ったら、ブラウザ上で実行できる公式チュートリアルでコードを実際に書いてみると雰囲気がつかめるかもしれません。
今後、さらにGo言語のことについて記事にしていきたいと思います。

もし「記事の内容が間違えている!」等ありましたら、Twitterまたはお問い合わせフォームからご連絡いただければと思います。

最後までご覧いただきありがとうございました。

参考サイト

【Go】基本文法⑤(連想配列・ Range)

A Tour of Go