急いで学ぶGo lang#4 関数・ポインタ・制御構文

go lang

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Goの制御構文

今回は関数やポインタ、条件分岐、ループなどを使ってみます。

動作環境

今回使用した動作環境は以下のとおりです。

  • OS : MacOS X 10.9.4
  • Go : 1.4.1
  • IDE : IntelliJ IDEA 14 CE

ここを参考に、GOPATHとGOROOTは設定しておいてください。

Goの関数

関数定義

Goで関数はfuncを使って定義します。funcに続いて関数名、引数があれば引数を記述します。戻り値があればそれも記述します。

// func functionName ( arg1 arg1Type, ・・・) (result1 resultType・・・)
func add(x int, y int) int {
	return x + y
}

z:= add(2,3) //z = 5

可変長引数

引数に...を指定すれば可変長引数をとることも可能です。

func fArgs(strArgs ...string) {
	for index, value := range strArgs {
		fmt.Println(index, value)
	}
}

// 0 Go
// 1 Java
// 2 Ruby
fArgs("Go", "Java", "Ruby")

複数の戻り値

Goの関数は下記のように複数の戻り値を返すこともできます。

func someResult(x int) (int, string) {
	return x, "hello"
}

num, str := someResult(10)

関数型

typeキーワードを使うと、型を宣言することができます。
Goにおいて関数は変数の一種なので、typeを使用して定義することが可能になります。

package main

//関数の型を宣言
type funcTemplate func(string) string

func greet(name string) string {
  return "hello," + name
}

func x(f funcTemplate) {
  fmt.Println(fmt.Sprintf("%T", f))
}

func main() {
  //main.funcTemplateが出力される
  x(greet)
}

ポインタ

引数の渡し方

Javaとかでもよくでてくるやつです。inc1関数はint型の値をうけとってインクリメントします。
inc1に渡された引数の値がコピーされるので、渡した引数numの値がインクリメントされることはありません。
Javaの場合、プリミティブ型はすべてこの動きになりますね。

func inc1(i int) {
	i++
	fmt.Println("inc1: i =  " + strconv.Itoa(i))
}

num := 10
inc1(num) // inc1: i = 11
fmt.Println("i =  " + strconv.Itoa(i)) // i = 10

inc2は引数にint型でなく、int型のポインタを受け取ります。
そして呼び出し元では「&」を使ってint型変数のアドレスを渡します。
※この場合はポインタがコピー(値渡し)されます

func inc2(i *int) {
	*i++
	fmt.Println("inc2: i =  " + strconv.Itoa(*i))
}

num := 10
inc2(&num) // inc2: i = 11
fmt.Println(num)  // i = 11

inc2ではうけとったポインタ型変数に「*」をつけて使用しています。*をつけると、ポインタの参照している先の値を使うことができます。
こうすることでアドレスを渡された変数の参照先の値を書き換えてます。
そのため、呼び出し元でも値がインクリメントされています。

ポインタを引数に指定した場合、コピーされないのでメモリの無駄遣いがありません。
構造体などの大きいオブジェクトを渡すときなどは参照渡しにすると思います。
(2015/2/16追記)
Goでは引数の渡し方はすべて値渡しとなります。(Javaのオブジェクトのような参照渡しはない)
ポインタを渡した場合もポインタの値渡し(ポインタのコピー)となるので、
オブジェクトのサイズによるメモリの無駄が少なくてすみます。

Goの制御構文

if

どこにでもある条件分岐です。条件部分に()はいりません。

x := false

if x == true {
  fmt.Println("x is true")
} else {
  fmt.Println("x is false")
}

条件部分で変数宣言が可能になってます。下記プログラムでxはif文の中でのみ使用できるローカル変数です。

func retTrue() bool {
  return true
}

if x := retTrue(); x == true {
  fmt.Println("x is true")
} else {
  fmt.Println("x is false")
}

//fmt.Println(x) <- エラー

条件分岐が複数ある場合、else ifももちろん使えます。

for

制御構文の基本、ループです。
expression1はループの開始前に呼ばれ、expression3は毎回ループの終了時にコールされます。
expression2は条件式です。

for expression1; expression2; expression3 {
 ・・・
}

for i := 0; i < 10; i++ {
  fmt.Println(i)
}

expression1とexpression3は省略可能なので、条件式だけを記述することもできます。
Goにはwhileループはありませんが、下記のようにすればwhileループのように書くことができます。

j := 0
for j < 10 {
  fmt.Println(j)
  j++
}

switch

switch-case文もあります。case部分には,区切りで複数の値を書くことができます。

i := 5
switch i {
case 1:
  fmt.Println("i == 1")
case 2, 3, 4:
  fmt.Println("i == 2, 3 or 4")
default:
  fmt.Println("All I know is that i is an integer")
}

Goのswitch文の場合、デフォルトでcaseの最後にbreakがあることになっています。
もし下のcase文も実行したい場合、「fallthrough」キーワードを使用すれば続くcaseコードを実行可能です。

i := 1
switch i {
case 1:
  fmt.Println("i == 1")
  fallthrough
case 2, 3, 4:
  fmt.Println("i == 2, 3 or 4")
default:
  fmt.Println("All I know is that i is an integer")
}

//実行結果
// i == 1
// i == 2, 3 or 4

参考サイトなど

  • Syohei YOSHIDA

    引数の渡し方ですが, Goはすべて値渡し(pass by value)であり, 参照渡し(pass by reference)はありません.
    ポインタを渡したときも, ポインタはコピーされます(それが指し示すものはコピーされないので,
    大きな構造体へのポインタを渡すのであれば, コピーサイズは小さく済む).

    参考
    https://golang.org/ref/spec
    http://golang.org/doc/faq

  • syuta

    ご指摘ありがとうございます。該当箇所を修正しました。

  • http://gravatar.com/mpyw mpyw

    > (Javaのオブジェクトのような参照渡しはない)

    JavaもGo同様に参照渡しはありませんね。全て「値の値渡し」あるいは「参照の値渡し」です。

    http://qiita.com/mpyw/items/bd38da57837d35214aae

    これはPHPの例ですが、Javaで「swapAndEditValRef」「swapAndEditValRef」相当の実装をすることは困難です。渡された変数のシンボルそのものを操作することは出来ません。あくまで操作出来るのはシンボルではなくシンボルをもとに参照される値のみです。