Go 型・関数
型
<組み込み型>
- 整数 :
int
byte(int8 のエイリアス)
rune(int32 のエイリアス)
int64
uint(負数以外(0 以上)を扱う) など - 浮動小数点数 : float32, float64
- 文字列 : string
- 真偽値 : bool
<型変換(キャスト)>
基本は、strconvパッケージを使う。
ただし、数値同士のキャストはT(v)
でできる。
var f float64 = 10
var n int = int(f)
よく使うもの
-
Atoi
: 文字列 → int(32 か 64 なのかは環境依存) -
Itoa
: int → 文字列
i, _ := strconv.Atoi("-42") // Atoi は返り値が2つあり、2つ目は使わないため、 _ で、これから使わないことを明示。
s := strconv.Itoa(-42)
あまり使わないもの
- ParseX : 文字列 → X
- ParseInt : 文字列 → int(指定の bit 値への変換)
- ParseFloat : 文字列 → float
- ParseUint : 文字列 → uint
- ParseBool : 文字列 → bool
- FormatX : X → 文字列
- FormatInt : int8 など(int が指す bit 値ではない bit 値) → 文字列
- FormatUint : uint → 文字列
- FormatFloat : float → 文字列
- FormatBool : bool → 文字列
<型の確認>
型の確認は、reflect.TypeOf()
でできる。
fmt.Println( reflect.TypeOf(v) )
<コンポジット型>
複合データ型。構造体、配列、スライス、マップ がある。
構造体
型の異なるデータを集めたデータ型。
- 初期化方法 :
struct { name string, age int }
- 操作 : 参照も代入も、
.
でアクセスできる。var person struct { name string age int } // .でアクセス person.name = "太郎" fmt.Println(person.name) // 太郎
配列
同じ型のデータを集めたデータ型。
要素数は変更できない。
- 初期化方法
[2]string{"hoge", "fuga"}
[...]string{"hoge", "fuga"}
-
[...]int{2: 10, 5: 20}
arr[i]
にあたる位置に値を指定する方法。この例だと[0 0 10 0 0 20]
となる。
- 操作
- アクセス :
arr[3]
- 長さ :
len(arr)
- スライス演算 :
arr[2:5]
配列からスライスを作る。この例だとarr[2] から arr[5-1] まで
を抽出したものになる。スライス演算arr := [...]string{"a", "b", "c", "d", "e", "f"} fmt.Println(arr[2:5]) // [c d e]
- アクセス :
2 次元配列
func main() {
var twoD [3][5]int
for i := 0; i < 3; i++ {
for j := 0; j < 5; j++ {
twoD[i][j] = (i + 1) * (j + 1)
}
fmt.Println("Row", i, ":", twoD[i])
}
fmt.Println(twoD)
}
/* 出力
Row 0 : [1 2 3 4 5]
Row 1 : [2 4 6 8 10]
Row 2 : [3 6 9 12 15]
[[1 2 3 4 5] [2 4 6 8 10] [3 6 9 12 15]]
/*
スライス
配列の一部を切り出したデータ型。
(スライスは配列を切り出したものなので、背後には必ず配列が存在する。)
配列との大きな違いは、スライスの長さ(サイズ)が固定ではなく動的であること。
スライスは下記 3 つの要素を持っている。(この 3 要素の構造体である。)
- ポインタ : 基となる配列の最初の要素の場所(切り出した場所)を表す。
- len : 切り出した長さ。
- cap : 基になる配列に対して、ポインタの位置(切り出した位置)から配列の終端までの要素数。つまり、このスライスはどこまで拡張できるのかを表す量。
- 初期化方法
-
arr[2:5]
スライス演算子s[i:p]
によって、(既にある)配列から切り出す方法。
この例だとarr[2] から arr[5-1] まで
を抽出したものになる。
i と p は省略することもでき、配列の全要素をベースにする場合はarr[:]
となる。 -
[]int{1, 2, 3}
スライスリテラルで、基になる配列は指定せずにスライスだけを作る方法。
(正しくは、内部的に{1, 2, 3}
の配列が自動で作られ、それをスライスが参照している。) -
[]int{2: 10, 5: 20}
slice[i]
にあたる位置に値を指定する方法。この例だと[0 0 10 0 0 20]
となる。 -
make([]int, 3, 10)
make メソッドによって指定した要素数、容量のスライスを作る方法。make([]型名, length, capacity)
-
- 操作
- アクセス :
sl[3]
- 長さ :
len(sl)
- 容量 :
cap(sl)
- 要素の追加 :
sl = append(sl, 50, 60)
- 要素の削除
下記のどちらも、(削除したい要素を除外した)削除しない側の 2 つの要素郡をくっつけることで、要素を削除したスライスを作成している。- i 番目の要素のみ削除 :
a = append(a[:i], a[i+1]...)
- i~j 番目の要素を削除 :
a = append(a[:i], a[j]...)
- i 番目の要素のみ削除 :
- アクセス :
空のスライスを作る方法
p := []byte{}
配列をコピーして使う方法
copy(移す先のスライス, 元の配列から欲しい要素のスライス)
を使う。
func main() {
// 配列
letters := [...]string{"A", "B", "C", "D", "E"}
fmt.Println("Before", letters) // Before [A B C D E]
slice1 := letters[0:2] // A, B
slice2 := make([]string, 3) // cap=3と指定した 空のスライスが生成される
// copy!!
copy(slice2, letters[1:4]) // slice2にletters[1:4]をコピーする -> B, C, D
slice1[1] = "X"
fmt.Println("slice1", slice1) // slice1 [A X]
fmt.Println("slice2", slice2) // slice2 [B C D] : 要素が変更されていない!
fmt.Println("After", letters) // After [A X C D E] : slice1が基にしている配列は当然変更される
}
マップ
キーと値をマッピングしたデータ型。
- 初期化方法
map[string]int{ "hoge": 1, "fuga": 2 }
make(map[string]int)
- 操作
- アクセス :
m["x"]
- 存在確認 :
n, ok := m["z"]
で、n には値、ok には bool が入る。 - 追加 :
m["z"] = 30
- 削除 :
delete(m, "z")
- アクセス :
<ユーザ定義型>
type 型名 基底型
で定義する。(基底型 : 基にする型。)
なので、ユーザ定義型というのは struct だけでなく、type MyInt int
とかも定義できる。
関数
- 引数の型は、変数名の後ろに書く。
- 2 つ以上の引数の型が同じ場合は、1 つに省略できる。
- 複数の戻り値を返せる。
使わない戻り値は、ブランク変数_
によって明示すること。Go の変数は必ず使わないといけないため。
func sample(x, y int) (string, string) {
// 処理
// return ...
}
Named return value
返り値の定義で変数を定義できる。
それで、それをそのまま関数内で使えるのはもちろん、return とするだけでその変数が返される。
func greetingPrefix(language string) (prefix string) {
switch language {
case japanese:
prefix = japaneseHelloPrefix
case french:
prefix = frenchHelloPrefix
default:
prefix = englishHelloPrefix
}
return
}
<ポインター>
ポインターじゃないとどうなるか
多くの言語と同じように、Go は「値渡し」の言語。
つまり、b := a
では(変数 b に変数 a のメモリ位置を共有している訳でなく、)変数 a の値のコピーを変数 b に格納している。
(ローカルコピー(メモリ内の新しい変数)を作成している。)
よって、関数の引数で変数(値)を渡しても、その関数内での変更は呼び出し元に反映されない。
その引数は、元の変数の値のコピーなので。
func main() {
first := "ジョン"
updateName(first)
println(first) // ジョン と出力される
}
func updateName(name string) {
name = "田中" // "田中"に書き換えたつもりだが...
}
ポインターの用途
ただ、そうじゃなくて「関数内で値を変更したい」というときにポインタを使う。
ポインターでは、値ではなくアドレスメモリを渡す。そうすることで、呼び出し元にも反映される。
(ポインターを使うことで、上記の updateName 関数で行う変更を main 関数の first 変数にも反映させることができる。)
ポインターは、変数の格納先(メモリアドレス)を表す値。
- &演算子 : ポインタを渡す。
- *演算子 : ポインターを参照して、(ポインターが示す)格納先のオブジェクトへアクセスする。
func main() {
first := "ジョン"
updateName(&first) // ポインター(メモリアドレス)を渡す
println(first) // 田中 と出力される
}
func updateName(name *string) { // 注: 変数名でなく、型のとなりに*を書く
*name = "田中" // ポインター先の文字列をupdate
}
<メソッド>
メソッドは、レシーバ(= 構造体 や その他何かのデータ)に紐付けられた関数。
メソッドによって、作成した構造体やデータに動作を追加できる。
レシーバは、第 0 引数みたいなイメージ。(なので変数の値のコピーが発生する。)
文法 : func (変数 レシーバ) メソッド名() 返却型 { ... }
type triangle struct {
size int
}
func (t triangle) perimeter() int {
return t.size * 3
}
func main() {
t := triangle{3}
fmt.Println("この三角形の外周:", t.perimeter())
}
レシーバにポインターを使う
メソッドに、変数でなくポインターを渡すほうが良い場合がある。(変数のアドレスを参照する。)
- メソッドで変数を更新する場合。
- 引数が(データ容量として)大きすぎる場合。→ そのコピーを回避したい。
type triangle struct {
size int
}
// ポインタを使わないメソッド
func (t triangle) perimeter() int {
return t.size * 3
}
// ポインタを使うメソッド
func (t *triangle) doubleSize() {
t.size *= 2 // return しない
}
func main() {
t := triangle{3}
t.doubleSize() // ポインタを参照して tが更新される
fmt.Println("size:", t.size) // size: 6
fmt.Println("perimeter:", t.perimeter()) // perimeter: 18
}
埋め込まれた構造体のメソッドを呼び出すことができる
type triangle struct {
size int
}
type coloredTriangle struct {
triangle // 構造体を埋め込む
color string
}
func (t triangle) perimeter() int {
return t.size * 3
}
func main() {
t := coloredTriangle{triangle{3}, "blue"}
fmt.Println("size:", t.size)
// coloredTriangleが、triangleのメソッドも使える
fmt.Println("perimeter:", t.triangle.perimeter())
// fmt.Println("perimeter:", t.perimeter()) // ↑の書き方のシンタックスシュガー
}
また、埋め込んだ構造体のメソッドをオーバーロードすることもできる。
※ オーバーロード(多重定義) : 同じ名前の関数等を定義すること。
その場合、下記のように、埋め込んだ側・埋め込まれた側どちらのメソッドも使用することができる。
type triangle struct {
size int
}
func (t triangle) perimeter() int {
return t.size * 3
}
type coloredTriangle struct {
triangle
color string
}
func (t coloredTriangle) perimeter() int {
return t.size * 5
}
func main() {
t := coloredTriangle{triangle{3}, "blue"}
fmt.Println("size:", t.size)
// coloredTriangleで、coloredTriangleのメソッドを使う
fmt.Println("perimeter:", t.perimeter()) // perimeter: 15
// coloredTriangleで、triangleのメソッドも使える
fmt.Println("perimeter:", t.triangle.perimeter()) // perimeter: 9
}
Discussion