S.Hayakawaをフォローする

【Golang】Go言語の特徴とポインタについて – Gopherへの道 ①

バックエンド

こんにちは。
以前からGOやりたいと口に出していたら、ありがといことにGO言語で開発する機会をいただけることになってわくわくなs.hayakawaです。
現在は他言語で開発をしつつ、GO言語の基本的な内容を勉強中です。目指せGopher。

弊社はPHPでの開発が多く、GO言語に触れてみると新鮮なことが多くあります。
今回から、触ってみてわかったGO言語の基本や他言語と違う特徴などをまとめていきたいと思います。

これからGO言語を学んでみようという方や、興味を持った社内メンバーの参考になればうれしいです。
(ひそかに進行する社内Gopher増殖計画)

はじめに

まずはPHPとの違いを概観してみたいと思ったので、PHPには無いもの、GO言語にないと思ったものをまとめてみます。
まだまだあると思いますが、新しい発見があったら更新していきます。

PHPに無いもの(違うもの)

goルーチン、チャネル

並行処理が行える。関連するパッケージとしてsyncが用意されている

ポインタ

メモリ上のアドレス情報を扱える

defer

関数の遅延実行ができる

slice, array, map

PHPでいう配列の代わりにslice, array, mapを使う

struct

構造体

type V struct {
	X int
	Y int
}

func main() {
	v := V{5, 2}
	fmt.Println(v.X)   // 5
        fmt.Println(v.Y)   // 2
}

その他の特徴

静的型付け
コンパイル(クロスコンパイル)
シングルバイナリ

GOにないもの

GO言語はシンプルに書けることを重視してつくられていることもあり、
PHPなどの言語によくある機能が見当たらなかったりします。
じゃあGOではどう表現するの?など具体的なところは追々書いていきたいと思います。

クラスや継承

構造体とインターフェイスで表現する

try-catch-finally

関数の戻り値にerrorインターフェイスの値を追加し、エラーチェックを行う

f, err := os.Open("filename.ext")
if err != nil {
    log.Fatal(err)
}

今回のトピック

今回はGOの特徴の一つであるポインタについてまとめてみます。
PHPなどでは馴染みのない概念ですが、
GO言語のメソッドはポインタを使って独特な処理も行うので見ていきましょう。

ポインタ

GOではポインタを扱えます。
ポインタは変数などの値のメモリアドレスです。
基本的な挙動は以下です。

var p *int       // 宣言(int型の変数のポインタ)

i := 25
p = &i           // 変数iを参照するポインタ型変数p

fmt.Println(p)   // 出力例)0xc0000b6020
fmt.Println(*p)  // 25

ポインタを操作することで、ポインタが指す変数の値を直接変更することができます。

func main() {
	i := 5

	p := &i         // ポインタ定義
	fmt.Println(*p) // 5
	*p = 21         // ポインタを操作
	fmt.Println(i)  // 21
}

メソッドとポインタレシーバ

GOのメソッドの宣言方法について説明します。
GOでは、普通に引数を受け取る関数の他に、レシーバと呼ばれる機構をもったメソッドを作ることができます。

普通の関数

func Scale(val int) int{
	return val * 10
}

// 呼び出し
i := 5
Scale(i) // 50

レシーバを持つ関数(メソッド)

type Vertex struct {
	X float64
}

func (v *Vertex) Scale(f float64) {  // Vertex型構造体をレシーバvとして受け取る
	v.X = v.X * f
}

func main() {
	v := Vertex{5}
	v.Scale(10)       // 呼び出し
	fmt.Println(v.X)  // 50
}

GOでは関数名の前にレシーバを記述する形でメソッドを宣言できます。
このとき、レシーバの型部分に「*」をつけることでポインタレシーバとして構造体を受け取ることができます。
(つけなければ変数レシーバ)

変数レシーバは値渡し(コピー)で、ポインタレシーバは参照渡しです。
つまりポインタレシーバを使用することで、メソッドからレシーバの値を直接変更することができます。

type Greet struct {
	greet string
}

func (s Greet) translate() {   // 変数レシーバ
	s.greet = "hello"
}

func (s *Greet) translate2() { // ポインタレシーバ
	s.greet = "hello"
}

func main() {
	str := Greet{"こんにちは"}

	str.translate()
	fmt.Println(str.greet) // こんにちは
	str.translate2()
	fmt.Println(str.greet) // hello
}

ポインタレシーバの特徴として、レシーバに渡す値が変数でもポインタでも
よしなにポインタレシーバとして機能してくれるという点があります。

type Vertex struct {
	X float64
}

func (v *Vertex) Scale(f float64) {     // ポインタレシーバ
	v.X = v.X * f
}

func ScaleFunc(v *Vertex, f float64) {  // 普通の関数
	v.X = v.X * f
}

v := Vertex{5}
p := &v

ScaleFunc(v, 5) // コンパイルエラー
ScaleFunc(p, 5) // OK
v.Scale(5)      // OK  (&v).Scale(5)として解釈してくれる
p.Scale(10)     // OK

ポインタレシーバを使う理由

  • メソッドがレシーバで受け取った値を直接操作する場合
  • メソッドが呼び出される度に変数をコピーすることを避けられる(大きな構造体を操作する際に特に有効)

クラスがないGO言語では、構造体のフィールドを直接操作できるポインタレシーバは
とても重要な役割を持ったメソッドの形だと考えられます。

おわりに

今回は以上です。
GO言語はPHPと全く違ったアプローチでコーディングができるので学んでいて楽しいです。
まだまだ触りの部分なので、自分もGO言語でいろいろなことができるように理解を深めていきたいと思います。

今後もGO言語について学んだことをこちらで更新していきます。
次回は並行処理やインターフェイスについて書いてみたいなと思っています。

参考:https://go-tour-jp.appspot.com/welcome/1

画像:公式より
by Renée French