概要
- 前回の続き。
- 今回はデータ型について学習した
- 基本データ型 - int, bool, float, rune, string
- 合成型 - 配列, リスト, Map, 構造体
- javaやts(typescript)との違いも言及しつつ進める
インプットの教材
基本データ型
- 整数や浮動小数等は、型の種類が複数あるが、とりあえずデフォルト型のintとfloatを押さえればよいか
- 他の基本データ型はjavaと大体一緒。
rune
が聞きなじみがないが、文字を格納するchar
のことだった。
変数の宣言
-
var 変数名 データ型 = 値
の形式。データ型は省略すると値に応じたデフォルトの型となる - その他、一括宣言
var{}
や、関数内宣言:= 値
がある。関数内宣言はjavaやtsでは見ないのでチェック。
変数宣言とデータ型の例
// 整数
var i1 int = 10
var i2 = 10
// 浮動小数
var f1 float64 = 10.5
var f2 = 10.5
// bool
var b1 bool = true
var b2 = false
// string
var s1 = "hello"
var s2 string = "hello2"
// rune
var r1 = 'h'
var r2 rune = 'h'
// 一括宣言
var i3, s3 = 20, "abc"
var (
i4 = 10
i5 int
s4,s5 = "aa","bb"
)
// 関数内での変数宣言
i6 := 10
- 暗黙的な型変換は行われない。明示的な型変換を毎回行う。
- Javaでは、小さい型から大きい型への代入は暗黙的な型変換が行われる。(逆の大きい型から小さい方への変換は明示的な型変換(キャスト)が必要。)
- Goの場合、型変換を必須にすることでルールをシンプルにした模様。トレードオフで記載が冗長になるが。
型変換
// 型のキャスト
var i1,f1 = 10, 10.5 // int, float
//f1 = i1 // NG:暗黙的キャストはできない
f1 = float64(i1) // OK:int → floatへの変換は明示的に行う。
b1 = s1 == s2 // bool型は真偽値の結果で入れる。
// なお、int型からstring(またはその逆)への変換はGoでは互換性がない
castNum := 10
//castStr := string(castNum) //NG
定数の宣言
- constで行う。
const 定数名 = 値
- 定数名は変数名と同じcamelCase。
- 大文字から始まるプロパティは、パッケージ外からアクセスできるものを定義するルールらしい。
- 変数や定数について、宣言方法、命名規則などtsに近い。
- 定数は、イミュータブルな値のみ設定できる。
- 基本データ型(リテラル値)は設定できる。イミュータブルな値であるため。
- 配列や構造体などはイミュータブルな値であるため設定できない。
定数の宣言
const con1 = "定数"
const (
con2 = "const"
con3 = "const2"
)
配列とスライス
- 配列は固定長のデータをリスト管理
- スライスは可変長のデータリストを管理。基本的にはスライスを使う。(Javaのlist)
- スライス(配列も)はミュータブルなデータ型であることに注意
- 値の追加(append)では新しいスライスを作成するため、戻り値への代入が必要
配列、sliceの作成
// 配列の作成
var l1 = [3]int{10,20,30}
var l2 = [...]string{"aa","bb"}
// スライス
var s1 = []int{10,20,30}
// スライスは追加できる(戻り値が必要)
s1 = append(s1, 40);
- コピーする場合は、makeで同じ領域の空スライスを作って、copy処理でコピーする方法がよい。
sliceのコピー
s := []int{10,20,30,40}
// makeでコピー元と同じ長さの配列を用意
s2 := make([]int, len(s))
// copyを実施
copy(s2,s)
s2 = append(s2, 50)
fmt.Println(s, s2) //[10 20 30 40] [10 20 30 40 50]
マップ
-
map[キーの型]値の型
で宣言。初期値はnil
- mapへの追加は、
m[key] = 値
。keyはなければ追加 - mapの参照は
m[key]
。値がなければデフォルト値を返す。 - mapの削除は、
delete(m, key)
。戻り値なし。
// 宣言
var m1 map[int]string // NG: nilマップ。値の追加不可となるためこの宣言はしない。
m2 := map[int]string{} // OK: 空マップの追加。値の追加可能
// 値の読み書き
m2[1] = "値1"
m2[2] = "値2"
m2[3] = "値3"
fmt.Println(m2[2]) // 値2
// 削除
delete(m2, 2)
fmt.Println(m2[2]) // 値なし:nil
fmt.Println(len(m2)) // 2
- カンマokイディオム
- 値と、値の存在可否を取得するイディオム。
- キーに紐づく値が存在しない場合、値の型のデフォルト値がとれる
- そのため、値が存在しないのか、デフォルト値を設定しているかの区別をつけるため、存在可否も返す。
-
v, ok :=m[key]
の構文で判別ができるようになる。
- 値と、値の存在可否を取得するイディオム。
m3 := map[string]int{
"first": 0,
"second": 1,
}
val, ok := m3["first"]
fmt.Println(val,ok) // 0, true → キーが存在し、値が0
val2, ok2 := m3["notkey"]
fmt.Println(val2,ok2) // 0, false → キーがなく、値はデフォルト0
構造体
- 構造体の継承はできない。(Javaのクラスとは異なる。)
-
type 変数名 struct{}
キーワードで作成する(構文はtsのtypeと近しい。structがあるかないか。)-
type
は型。struct
が構造体を表す。
-
// 構造体の作成
type human struct {
name string
age int
}
// 初期化
var taro human
hanako := human{} //Mapとは違い、varと同様に、0値で初期化。
takashi := human{"たかし",20}
yuki := human{name: "ゆき"}
fmt.Println(taro) // { 0}
fmt.Println(hanako) // { 0}
fmt.Println(takashi) // {たかし 20}
fmt.Println(yuki) // {ゆき 0}
// 値の設定
hanako.name = "はなこ"
fmt.Println(taro) // {はなこ 0}
- 無名構造体は変数宣言と同時に作成する。型は定義しないため、
struct
キーワードのみ
// 無名構造体
var game struct {
title string
price int
}
game.title = "FF"
game.price = 7000
// 無名構造体 - 初期化時に設定(リテラル代入)
game2 := struct {
title string
memo string
} {
title: "DQ",
memo: "楽しい",
}
- 別の型への代入は、構造体の構成が一致する場合可能。
type human1 struct {
name string
age int
}
h1 := human1{}
type human2 struct {
name string
age int
}
h2 := human2{}
type human3 struct {
age int
name string
}
h3 := human3{}
// 構造体の構成が一致する場合、型変換で代入できる
h1 = human1(h2) // OK: 一致する
h1 = human1(h3) // NG: 一致しない。(コンパイルエラー)
// 無名関数の場合は直接代入できる
var h4 struct {
name string
age int
}
h1 = h4
おわりに
-
基本型、配列、スライス、マップ、構造体について学んだ。細かい違いはあれど、概ねJavaやtsと同じ。
- 宣言方法や、構造体の書き方など、tsの方が構文は近いかも。
- sliceやMapの種類は一つ、Setはないなど、Goの方がシンプルではある。
- この辺りは、継承がなく、派生クラスがないからか。
-
ただし、継承がないのは大きな違いに感じる。
- オブジェクト指向による設計は難しくなりそうなので、大規模開発には向いてなさそう。
- マイクロサービス化が主流になってきているので、シンプルな設計にしているのかも。
-
関数内宣言(名称これでいいのか?)
:=
による変数宣言は、Javaやtsにはない構文なので覚えておく。