##概要
「処理が速い」「並行処理」が得意ということから、
今後大規模webサービスなどで重宝されると思いましたので、
学習していきたいと思います。
今回は、ポインタなどを見ていきたいと思います。
##参考サイト
以下を実施してみました。
##環境
GoogleChrome
※ブラウザ操作だけで学習できるため、 エディタを用意する必要がありません。
※私は、ローカルにCentOS8環境を用意して、チュートリアルを行います。
※Goインストール手順です。よろしければ、ご活用ください。
CentOS8へのGo言語インストール手順
##ポインタ
ポインタは値のメモリアドレスを指します。
package main
import "fmt"
func main() {
i, j := 42, 2701
//iのメモリアドレスを取得
p := &i
//「*」をつけることをポインタで取得した変数の値を出力
fmt.Println(*p)
//出力している値を上書き
*p = 21
//上書きしたため、iの値が変更されている
fmt.Println(i)
//jのメモリアドレスを取得
p = &j
//「*」をつけることをポインタで取得した変数の値を出力
fmt.Println(*p)
//出力している値を上書き
*p = *p / 37
//上書きしたため、jの値が変更されている
fmt.Println(j)
}
//実行
go run pointers.go
//実行結果
42
21
2701
73
##Structs
struct (構造体)は、フィールド( field )の集まりです。
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
fmt.Println(Vertex{1, 2})
}
//実行
go run structs.go
//実行結果
{1 2}
- Struct Fields
structのフィールドは、ドット( . )を用いてアクセスする。
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
//Xフィールドへアクセスし、代入
v.X = 4
//代入した値が反映されている
fmt.Println(v.X)
}
//実行
go run struct-fields.go
//実行結果
4
- Pointers to structs
structのフィールドは、structのポインタを通してアクセスすることもできます。
フィールドXを持つstructのポインタ p がある場合、フィールド X にアクセスするには (*p).X のように書くことができます。
しかし、この表記法は大変面倒ですので、Goでは代わりに p.X と書くこともできます。
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
//structsをポインタに代入
p := &v
//structsのXフィールドに値を代入
p.X = 1e9
fmt.Println(v)
}
##structリテラル
structリテラルは、フィールドの値を代入することで新しいstructの初期値の割り当てを行っている。
Name: 構文を使って、フィールドの一部だけを列挙することができます
(この方法でのフィールドの指定順序は関係ありません)。
※例: X: 1 として X だけを初期化など
& を頭に付けると、新しく割り当てられたstructへのポインタを戻します。
package main
import "fmt"
type Vertex struct {
X, Y int
}
var (
//値を代入することで新しいstructの初期値の割り当てを行う
v1 = Vertex{1, 2}
//Xだけ初期値割り当てを行う
v2 = Vertex{X: 1}
//初期化
v3 = Vertex{}
//&を頭に付けると、新しく割り当てられたstructへのポインタを戻す
p = &Vertex{1, 2}
)
func main() {
fmt.Println(v1, p, v2, v3)
}
//実行
go run struct-literals.go
//実行結果
{1 2} &{1 2} {1 0} {0 0}
##配列(Arrays)
[n]T型で表す。
T型の配列にn個まで変数を入れられるという意味になります。
package main
import "fmt"
func main() {
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)
primes := [6]int{2, 3, 5, 7, 11, 13}
fmt.Println(primes)
}
//実行
go run array.go
//実行結果
Hello World
[Hello World]
[2 3 5 7 11 13]
##スライス(Slices)
スライスは可変長です。
より柔軟な配列と見なすこともできます。
実際には、スライスは配列よりもより一般的とのことです。
[]T型と表す。
T型のスライスという意味になります。
コロンで区切られた二つのインデックス low と high の
境界を指定することによってスライスが形成されます
例:a[low : high]
package main
import "fmt"
func main(){
primes := [6]int{2, 3, 5, 7, 11, 13}
//1番目の値から4番目の値の間を取得
//1番目は含む・4番目は含まない
var s []int = primes[1:4]
fmt.Println(s)
}
//実行
go run slices.go
//実行結果
[3 5 7]
- スライスは配列への参照のようなもの
スライスはどんなデータも格納しておらず、
単に元の配列の部分列を指し示しています。
スライスの要素を変更すると、
その元となる配列の対応する要素が変更されます。
同じ元となる配列を共有している他のスライスは、
それらの変更が反映されます。
package main
import "fmt"
func main(){
names := [4]string{
"John",
"Paul",
"George",
"Ringo",
}
//配列を確認
fmt.Println(names)
//配列の中身をスライスに格納
a := names[0:2]
b := names[1:3]
//格納されていることを確認
fmt.Println(a, b)
//スライスの中身を変更
b[0] = "XXX"
//変更した内容が配列にも反映されているか確認
fmt.Println(a, b)
fmt.Println(names)
}
//実行
go run slice-pointers.go
//実行結果
[John Paul George Ringo]
[John Paul] [Paul George]
[John XXX] [XXX George]
[John XXX George Ringo]
- スライスのリテラル(初期値割り振りの認識)
・配列リテラル
[3]bool{true, true, false}
・スライスリテラル
[]bool{true, true, false}
package main
import "fmt"
func main() {
q := []int{2, 3, 5, 7, 11, 13}
fmt.Println(q)
r := []bool{true, false, true, true, false, true}
fmt.Println(r)
s := []struct {
i int
b bool
}{
{2, true},
{3, false},
{5, true},
{7, true},
{11, false},
{13, true},
}
fmt.Println(s)
}
//実行
go run slice-literals.go
//実行結果
[2 3 5 7 11 13]
[true false true true false true]
[{2 true} {3 false} {5 true} {7 true} {11 false} {13 true}]
- スライスの長さ( length )と容量( capacity )
スライスの長さ:スライスに含まれる要素の数。
スライスの容量:スライスの最初の要素から数えて、元となる配列の要素数。
package main
import "fmt"
func main(){
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)
//スライスの長さを0にする
s = s[:0]
printSlice(s)
//スライスの長さを伸ばす
s = s[:4]
printSlice(s)
//スライスの値を削除
s = s[2:]
printSlice(s)
}
func printSlice( s []int){
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
//実行
go run slice-len-cap.go
//実行結果
len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
len=2 cap=4 [5 7]
- スライスのゼロ値
スライスのゼロ値は nil です。
nil スライスは 0 の長さと容量を持っており、
何の元となる配列も持っていません。
※宣言するだけの時に使用する認識
package main
import "fmt"
func main() {
var s []int
fmt.Println(s, len(s), cap(s))
if s == nil {
fmt.Println("nil!")
}
}
//実行
go run nil-slices.go
//実行結果
[] 0 0
nil!
- スライスは、組み込みのmake関数を使用して作成
動的サイズの配列を作成する方法です。
make関数はゼロ化された配列を割り当て、その配列を指すスライスを返す。
※make関数が対応する型はslice,map,chanelのみ
※上記は、Goのnew関数とmake関数の違いを解説する参照
package main
import "fmt"
func main() {
a := make([]int, 5)
printSlice("a", a)
b := make([]int, 0, 5)
printSlice("b", b)
c := b[:2]
printSlice("c", c)
d := c[2:5]
printSlice("d", d)
}
func printSlice(s string, x []int) {
fmt.Printf("%s len=%d cap=%d %v\n",
s, len(x), cap(x), x)
}
//実行
go run making-slices.go
//実行結果
a len=5 cap=5 [0 0 0 0 0]
b len=0 cap=5 []
c len=2 cap=5 [0 0]
d len=3 cap=3 [0 0 0]
- Slices of slices
スライスは、他のスライスを含む任意の型を含むことができる。
package main
import (
"fmt"
"strings"
)
func main() {
// Create a tic-tac-toe board.
board := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}
// The players take turns.
board[0][0] = "X"
board[2][2] = "O"
board[1][2] = "X"
board[1][0] = "O"
board[0][2] = "X"
for i := 0; i < len(board); i++ {
fmt.Printf("%s\n", strings.Join(board[i], " "))
}
}
//実行
go run slices-of-slices.go
//実行結果
X _ X
O _ X
_ _ O
- スライスへ新しい要素を追加
Goの組み込みの append を使います。
package main
import "fmt"
func main() {
var s []int
printSlice(s)
s = append(s, 0)
printSlice(s)
s = append(s, 1)
printSlice(s)
s = append(s, 2, 3, 4)
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
//実行
go run append.go
//実行結果
len=0 cap=0 []
len=1 cap=1 [0]
len=2 cap=2 [0 1]
len=5 cap=6 [0 1 2 3 4]
##Range
for ループに利用する range は、スライスや、マップ( map )を
ひとつずつ反復処理するために使います。
スライスをrangeで繰り返す場合、rangeは反復毎に2つの変数を返します。
1つ目の変数はインデックス( index )で、2つ目はインデックスの場所の要素のコピーです。
package main
import "fmt"
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main(){
for i, v := range pow{
fmt.Printf("2**%d = %d\n", i,v)
}
}
//実行
go run range.go
//実行結果
2**0 = 1
2**1 = 2
2**2 = 4
2**3 = 8
2**4 = 16
2**5 = 32
2**6 = 64
2**7 = 128
- Range continued
インデックスや値は、 " _ "(アンダーバー) へ代入することで捨てることができます。
※ソースは一部変更しております。
package main
import "fmt"
func main(){
pow := make([]int, 10)
for i := range pow {
pow[i] = 1 << uint(i)
fmt.Println(pow)
}
for _, value := range pow{
fmt.Printf("%d\n", value)
}
}
//実行
go run range-continued.go
//実行結果
[1 0 0 0 0 0 0 0 0 0]
[1 2 0 0 0 0 0 0 0 0]
[1 2 4 0 0 0 0 0 0 0]
[1 2 4 8 0 0 0 0 0 0]
[1 2 4 8 16 0 0 0 0 0]
[1 2 4 8 16 32 0 0 0 0]
[1 2 4 8 16 32 64 0 0 0]
[1 2 4 8 16 32 64 128 0 0]
[1 2 4 8 16 32 64 128 256 0]
[1 2 4 8 16 32 64 128 256 512]
1
2
4
8
16
32
64
128
256
512
##Maps
map はキーと値とを関連付けます。
マップのゼロ値は nil です。 nil マップはキーを持っておらず、
またキーを追加することもできません。
make 関数は指定された型のマップを初期化して、使用可能な状態で返します。
package main
import "fmt"
type Vertex struct{
Lat, Long float64
}
var m map[string]Vertex
func main(){
m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
fmt.Println(m["Bell Labs"])
}
//実行
go run maps.go
//実行結果
{40.68433 -74.39967}
- Map literals
mapリテラルは、structリテラルに似ていますが、 キー ( key )が必要です。
package main
import "fmt"
type Vertex struct {
Lat, Long float64
}
var m = map[string]Vertex{
"Bell Labs": Vertex{
40.68433, -74.39967,
},
"Google": Vertex{
37.42202, -122.08408,
},
}
func main() {
fmt.Println(m)
}
//実行
go run map-literals.go
//実行結果
map[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]
- Map literals continued
もし、mapに渡すトップレベルの型が単純な型名である場合は、
リテラルの要素から推定できますので、その型名を省略することができます。
package main
import "fmt"
type Vertex struct {
Lat, Long float64
}
var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}
func main() {
fmt.Println(m)
}
//実行
go run map-literals-continued.go
//実行結果
map[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]
- Mapsの操作
■m へ要素(elem)の挿入や更新の場合
m[key] = elem
■要素の取得:
elem = m[key]
■要素の削除:
delete(m, key)
■キーに対する要素が存在するかどうか
※2つの目の値で確認します
elem, ok = m[key]
※もし、 m に key があれば、変数 ok は true となり、
存在しなければ、 ok は false となります。
package main
import "fmt"
func main() {
m := make(map[string]int)
//挿入
m["Answer"] = 42
fmt.Println("The value:", m["Answer"])
//更新
m["Answer"] = 48
fmt.Println("The value:", m["Answer"])
//削除
delete(m, "Answer")
fmt.Println("The value:", m["Answer"])
//検索
v, ok := m["Answer"]
fmt.Println("The value:", v, "Present?", ok)
}
//実行
go run matating-maps.go
//実行結果
The value: 42
The value: 48
The value: 0
The value: 0 Present? false
##関数(Function values)
関数も変数です。他の変数のように関数を渡すことができます。
関数値( function value )は、関数の引数に取ることもできますし、戻り値としても利用できます。
package main
import (
"fmt"
"math"
)
func compute(fn func(float64, float64) float64) float64 {
return fn(3, 4)
}
func main() {
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(5, 12))
fmt.Println(compute(hypot))
fmt.Println(compute(math.Pow))
}
//実行
go run function-values.go
//実行結果
13
5
81
- Function closures
Goの関数は クロージャ( closure ) です。
クロージャは、それ自身の外部から変数を参照する関数値です。
この関数は、参照された変数へアクセスして変えることができ、
その意味では、その関数は変数へ"バインド"( bind )されています。
package main
import "fmt"
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
neg(-2*i),
)
}
}
//実行
go run function-closures.go
//実行結果
0 0
1 -2
3 -6
6 -12
10 -20
15 -30
21 -42
28 -56
36 -72
45 -90
##まとめ
次回はメソッドなどを見ていきたいと思います。