goの基本文法 No.3

4 min read読了の目安(約4200字

2020-09-28執筆
A Tour of Goに沿ってGoの文法をまとめます。
これまでにJavaScriptやPythonなどのリッチ言語しか使ったことがなかった自分のためにまとめたものです。
なのでこれまでに上のようなリッチ言語を触ったことがあり、これからGoを触ってみようと考えている人の参考になればと思います。
文章の構成は、基本的に

  1. 概要
  2. コード
  3. コードの説明

の構成にしているつもりです。概要で簡単な説明をし、コードで例を示して、さらにコードの説明で細かい説明をするといった構成です。

ポインタ

プログラムとメモリの関係

どのようなプログラムも実行する際は、そこで扱うデータを保持する必要があります。関数など処理の内容や手順を示したものでさえコンピュータからすればデータです。
プログラムが実行されるとコンピュータはデータの保持に必要な領域をメモリ上に確保します。コードに記述されている変数のための領域もここで確保され、何か値が代入されるとこの確保した領域に書き込まれます。

ポインタ

ポインタは変数に代入されている値ではなくその変数のために確保されているメモリアドレスを保持する変数です。ポインタの宣言の仕方は以下になります。

var 変数名 *// 一般
変数名 := &変数 // 短縮

具体例で見てみましょう。

// code:3-1
// ①
var num int = 10
var p *int
p = &num
fmt.Println(p)
fmt.Println(*p)
// ②
var num int = 10
p := &num
fmt.Println(p)
fmt.Println(*p)
/*実行結果(①も②も結果は同じ)
0xc00002c008 // これがメモリアドレス。16進数表示でメモリアドレスを表している。
10
*/

①は短縮せずに書いた形で②は短縮して書いた形です。短縮せずに書いた場合は型の前に*をつけることでその型用のポインタ変数になります。
どちらも変数の前に&をつけて代入することで代入した変数のポインタになります。
最初の出力ではポインタ変数そのもの、つまりメモリアドレスそのものを出力しています。
0xc00002c008
少し補足をすると最初の0xが16進数であることを表していて、残りのc00002c008が実際にメモリ内での変数numのアドレスを表しています。

次の出力ではポインタ変数が指しているメモリアドレスに実際に保持されている値を出力しています。このようにポインタ変数の前に*をつけるとそのポインタ変数が指しているアドレスの値を参照することが出来ます。

ポインタをどのような時に使うか

変数はプログラム実行時にメモリ上のある領域を割り振られました。コードを書いている時点でどこに割り振られるかはわかりません。また、変数にはスコープと呼ばれる変数が使える有効範囲があります。以下の例をみてみましょう。

// code:3-2
// 実行すると変数の値に1加算する関数(インクリメント)
func inc(x int) {
	x = x + 1
}

func main() {
	x := 0
	for i := 0; i < 10; i++ {
		inc(x)
	}
	fmt.Println(x)
}
/*実行結果
0
*/

実行結果を見たらわかるようにxの値は書き換えられません。これは変数xの有効範囲が宣言されたmain関数の中だけだからです。inc(x)が実行されるとxの値のコピーが

func inc(x int) {
	x = x + 1
}

の中で処理されます。よってmain関数内のxはゼロのままでありゼロが出力されます。

変数xには有効範囲があるので変数xの値を書き換える方法は工夫次第でいくつかあるでしょうが、ポインタを理解すればxの値が記録されているメモリアドレスを直接書き換える方法が浮かびます。

// code:3-3
func pointerInc(x *int) {
	*x++
}

func main() {
	x := 0
	p := &x
	for i := 0; i < 10; i++ {
		pointerInc(p)
	}
	fmt.Println(x)
}
/*実行結果
10
*/

上の例では、pointerInc関数に渡しているのは、xのポインタつまりメモリアドレスです。pointerInc関数内では、引数で受け取ったメモリアドレスの値についてインクリメントを行っているので、main関数内の変数xの値が書き換えられます。よって10と出力されます。
(メモリアドレスの値にアクセスする時はポインタ変数の前に*を付けるので*x、それをインクリメントするので*x++です。ここら辺は似た記号が頻出し混乱するかもしれませんので一つ一つ確認しながら見ていってください)

ポインタの概念はわかりにくいです。私も全てを理解したとは思っていません。しかし感の良い人は察していると思いますが、どのプログラムもメモリ上で動く以上ポインタはどの言語にも存在します。それをコードを書くプログラマーに見せているか見せていないかの差です。Goはポインタをプログラマーに見せています。コードを書く上でメモリ管理もコードでできることになります。つまり出来ることが多くなるので、そこが出来ない他の言語より有利とも取れます。

構造体

型を自分で定義

goの基本文法 No.1で紹介した型だけでは大したことが出来ないじゃないかと思う方もいるかもしれません。Goは自分で型を定義することも出来ます。

// code:3-4
type MyInt int // ①

func main() {
	var x MyInt = 12
	fmt.Printf("値:%d, 型:%T ", x, x)
}
/*実行結果
値:12, 型:main.MyInt
*/

上では①でintを用いて整数を扱うMyIntという型を新たに定義しています。定義した型の使い方はこれまで同様です。すでにあるint型を再定義しているだけなので意味が内容に感じるかもしれませんが、プログラムの中で特別な用途で整数を扱いたい場合に通常の整数と差別化するために任意の名前で再定義することはあるみたいです。後述するinterfaceと組み合わせた時に真価をはっきするとかしないとか。

struct

次に紹介する構造体は現段階でもその威力を感じることが出来るでしょう。これまで見てきた型はどれも一つの値を表すものばかりでした。しかし、数学のベクトルのように複数の値をセットで扱いたかったり、もしくは「名前・年齢・性別」といった文字列・数値をセットで扱いたかったり、データに構造を持たせたいことの方が多いでしょう。構造体はデータに構造を持たせるためのものです。

type 型名 struct {
    プロパティ1 型
    プロパティ2 型
    …
}

構造体は上のようにして宣言します。プロパティが例えば「名前・年齢・性別」といったセットにされるものになります。具体例で見てみましょう。

// code:3-5
// ①
type Vertex struct {
    x int
    y int
}
// ①'
type Person struct {
    name   string
    age    int
    active bool
}
func main() {
	v := Vertex{1, 2} // ②
	fmt.Println(v)
	fmt.Println(v.x) // ③
	fmt.Println(v.y)
}
/*実行結果
{1 2}
1
2
*/
  1. ここでは2次元のベクトルを定義しようと思うのでプロパティは整数型のx, yを指定しています。①’も今回のとは関係ありませんが別の構造体の定義として参考にしてください。
  2. 実際に構造体の変数を宣言するときは、型名{ }の形で書きます。それぞれのプロパティはカンマ,で区切って指定します。
  3. それぞれのプロパティは、ドット.で呼び出します。

構造体リテラル

リテラルとはコードに直接数値や文字列を書いたものを指します。下のコードでは=の右側(右オペランド)がリテラルになります。

// code:3-6
var (
	v1 = Vertex{1, 2}  // has type Vertex
	v2 = Vertex{Y: 3, X: 1}  // Y:0 is implicit
	v3 = Vertex{}      // X:0 and Y:0
)
func main() {
	fmt.Println(v1, v2, v3)
}
/*実行結果
{1 2} {1 3} {0 0}
*/

変数の初期値の設定は上のような方法があります。何も設定しない場合は型にあったゼロ値が設定されます(v3)。

構造体のポインタ

構造体のポインタも通常のポインタと違いはありません。

// code:3-7
type Vertex struct {
	X int
	Y int
}
func main() {
	v := Vertex{1, 2}
	p := &v
	p.X = 10 // ①
	fmt.Println(v)
}
/*実行結果
{10, 2}
*/

上のコードでは、省略した形でVertex型のポインタ変数pを定義しています。注目して欲しいのは①の部分です。ここではプロパティXに10を代入しています。本来ポインタ型が値にアクセスするには、前に*を付けて(*p).X = 10とするように思われます。Goは*を付けなくても動作するようになっています。