3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

A Tour of Go(Basics)

Posted at

A Tour of Go(Basics)

新入社員の浦川仁成です。

A Tour of Goで勉強した内容についてまとめました。

A Tour of Go

Packages, variables, and functions.

fmt.Println

package main

import (
	"fmt"
	"math/rand"
)

func main() {
	fmt.Println("My favorite number is", rand.Intn(10))
}
  • メインパッケージを宣言する
  • fmt、math/randモジュールをインポートする
  • 好きな数字をランダムで出力する(0~9?)

fmt.printf

func main() {
	fmt.Printf("Now you have %g problems.\n", math.Sqrt(7))
}
  • 少数を出力する

fmt.Printlnとfmt.Printfの違い

func main() {
	name := "Alice"
	age := 30
	
	// Println関数を使用した出力
	fmt.Println("My name is", name, "and I am", age, "years old.")
	
	// Printfメソッドを使用した出力
	fmt.Printf("My name is %s and I am %d years old.\\n", name, age)
}

↓出力結果

My name is Alice and I am 30 years old.
My name is Alice and I am 30 years old.

**Println**関数は、複数の引数を取り、スペースで区切って出力します。また、最後に改行を追加します。

**Printfメソッドは、フォーマット文字列と引数を取ります。フォーマット文字列は、出力される文字列の形式を指定するために使用されます。フォーマット文字列内の %
プレースホルダは、引数の値で置き換えられます。フォーマット文字列の種類に応じて、変換指定子が使用されます。たとえば、
%dは整数、%fは浮動小数点数、%s**は文字列を表します。

Functions

引数は変数名の 後ろ に型名を書く

func add(x int, y int) int {
	return x + y
}

func main() {
	fmt.Println(add(42, 13))
}
  • 二つの整数型の引数を受け取り、その和を返すaddメソッドを作成する
func method(name string) string{
	return name
}

Functions continued

関数の2つ以上の引数が同じ型である場合には、最後の型を残して省略して記述できます。

func add(x, y int) int {
	return x + y
}

func main() {
	fmt.Println(add(42, 13))
}

Multiple results

関数は複数の戻り値を返すことができます。

func swap(x, y string) (string, string) {
	return y, x
}

func main() {
	a, b := swap("hello", "world")
	fmt.Println(a, b)
}
  • 二つの文字列を受け取り、順番を逆にして返すswap関数を定義

**:=**は、短縮変数宣言と呼ばれる書き方

  • 通常、変数を宣言するには以下のように**var**キーワードを使います。
var 変数名  = 初期値
  • 短縮変数宣言では、変数宣言と初期化を同時に行うことができます。具体的には、以下のように書くことができます。
変数名 := 初期値

Named return values

Goでの戻り値となる変数に名前をつける( named return value )ことができます。戻り値に名前をつけると、関数の最初で定義した変数名として扱われます。

func split(sum int) (x, y int) {
	x = sum * 4 / 9
	y = sum - x
	return
}

func main() {
	fmt.Println(split(17))
}

Variables

varステートメントは変数( variable )を宣言します。 関数の引数リストと同様に、複数の変数の最後に型を書くことで、変数のリストを宣言できます。

var c, python, java bool

func main() {
	var i int
	fmt.Println(i, c, python, java)
}
  • bool型の変数を3つ定義する
  • int型の変数を定義する

↓出力結果

0 false false false

Variables with initializers

var 宣言では、変数毎に初期化子( initializer )を与えることができます。

初期化子が与えられている場合、型を省略できます。その変数は初期化子が持つ型になります。

var i, j int = 1, 2

func main() {
	var c, python, java = true, false, "no!"
	fmt.Println(i, j, c, python, java)
}

↓出力結果

1 2 true false no!

Short variable declarations

関数の中では、 var 宣言の代わりに、短い := の代入文を使い、暗黙的な型宣言ができます。

なお、関数の外では、キーワードではじまる宣言( varfunc, など)が必要で、 := での暗黙的な宣言は利用できません。

Go言語には、変数のスコープを明確にするための規則があります。関数内での変数の宣言には、代入式の := を用いた暗黙的な宣言が可能ですが、この場合、変数のスコープは宣言されたブロック内に限定されます。一方、関数の外で変数を宣言する場合には、変数のスコープがパッケージレベルになります。このため、明示的な宣言が必要になります。

func main() {
	var i, j int = 1, 2
	k := 3
	c, python, java := true, false, "no!"

	fmt.Println(i, j, k, c, python, java)
}

Basic types

Go言語の基本型(組み込み型)は次のとおりです:

bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // uint8 の別名

rune // int32 の別名
     // Unicode のコードポイントを表す

float32 float64

complex64 complex128
var (
	ToBe   bool       = false
	MaxInt uint64     = 1<<64 - 1
	z      complex128 = cmplx.Sqrt(-5 + 12i)
)

func main() {
	fmt.Printf("Type: %T Value: %v\n", ToBe, ToBe)
	fmt.Printf("Type: %T Value: %v\n", MaxInt, MaxInt)
	fmt.Printf("Type: %T Value: %v\n", z, z)
}

↓出力結果

Type: bool Value: false
Type: uint64 Value: 18446744073709551615
Type: complex128 Value: (2+3i)
  • **%T**は、引数の型を出力するフォーマット指定子です。
  • **%v**は、引数の値を出力するフォーマット指定子です。
  • **%dは整数値を10進数で、%fは浮動小数点数を小数点以下6桁で、%sは文字列をそのまま、%q**は文字列をクォートして出力する指定子です。

Zero values

変数に初期値を与えずに宣言すると、ゼロ値( zero value )が与えられます。

ゼロ値は型によって以下のように与えられます:

数値型(int,floatなど): 0
bool型: false
string型: "" (空文字列( empty string ))
func main() {
	var i int
	var f float64
	var b bool
	var s string
	fmt.Printf("%v %v %v %q\n", i, f, b, s)
}

↓出力結果

0 0 false ""

Type conversions

変数 v 、型 T があった場合、 T(v) は、変数 v を T 型へ変換します。(キャスト)

いくつかの変換を見てみましょう:

var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

よりシンプルに記述できます:

i := 42
f := float64(i)
u := uint(f)

Goでの型変換は明示的な変換が必要です。

func main() {
	var x, y int = 3, 4
	var f float64 = math.Sqrt(float64(x*x + y*y))
	var z uint = uint(f)
	fmt.Println(x, y, z)
}

↓出力結果

3 4 5

  • math.Sqrtは平方根を求める関数

Type inference

明示的な型を指定せずに変数を宣言する場合( := や var = のいずれか)、変数の型は右側の変数から型推論されます。

右側の変数が型を持っている場合、左側の新しい変数は同じ型になります:

var i int
j := i // j is an int

右側に型を指定しない数値である場合、左側の新しい変数は右側の定数の精度に基いて int
float64complex128の型になります:

i := 42           // int
f := 3.142        // float64
g := 0.867 + 0.5i // complex128

Constants

定数( constant )は、 const キーワードを使って変数と同じように宣言します。

定数は、文字(character)、文字列(string)、boolean、数値(numeric)のみで使えます。

なお、定数は := を使って宣言できません。

const Pi = 3.14

Numeric Constants

数値の定数は、高精度な  ( values )です。

型のない定数は、その状況によって必要な型を取ることになります。

const (
	// Create a huge number by shifting a 1 bit left 100 places.
	// In other words, the binary number that is 1 followed by 100 zeroes.
	Big = 1 << 100
	// Shift it right again 99 places, so we end up with 1<<1, or 2.
	Small = Big >> 99
)

func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
	return x * 0.1
}
  • **const()**で囲まれた変数は定数となります。

  • Big は、1 を 100 回左にシフトした値です。シフト演算子 << は、左オペランドの値を右オペランドのビット数だけ左にシフトします。例えば、1 << 38 になります。

    つまり、1 << 100 は、1 を 100 回左にシフトした値を表します。二進数で表現すると、1 の後に 0 が 100 個続く数値になります。つまり、Big2 の 100 乗に相当します。

    一方、Small は、Big を 99 回右にシフトした値です。シフト演算子 >> は、左オペランドの値を右オペランドのビット数だけ右にシフトします。例えば、8 >> 31 になります。

    つまり、Big を 99 回右にシフトすると、1 の後に 0 が 99 個続く数値になります。つまり、Small2 に相当します。ここで、Big が 100 回シフトされているのに対して、Small は 99 回シフトされているため、Small の値は Big の値よりも 2 倍小さくなっています。

    なお、このようなシフト演算は、2 の累乗での乗算や除算に相当するため、コンピュータ内部での計算には非常に効率的な方法です。また、Go言語に限らず、他のプログラミング言語でも同様に使われているため、覚えておくと便利です。

Flow control statements: for, if, else, switch and defer

For

基本的に、 for ループはセミコロン ; で3つの部分に分かれています:

  • 初期化ステートメント: 最初のイテレーション(繰り返し)の前に初期化が実行されます
  • 条件式: イテレーション毎に評価されます
  • 後処理ステートメント: イテレーション毎の最後に実行されます
func main() {
	sum := 0
	for i := 0; i < 10; i++ {
		sum += i
	}
	fmt.Println(sum)
}

For continued

初期化と後処理ステートメントの記述は任意です。

func main() {
	sum := 1
	for ; sum < 1000; {
		sum += sum
	}
	fmt.Println(sum)
}

For is Go's "while"

セミコロン(;)を省略することもできます。つまり、C言語などにある while
 は、Goでは forだけを使います。

func main() {
	sum := 1
	for sum < 1000 {
		sum += sum
	}
	fmt.Println(sum)
}

Forever

ループ条件を省略すれば、無限ループ( infinite loop )になりますので、無限ループをコンパクトに表現できます。

func main() {
	for {
	}
}

If

Go言語の ifステートメントは、先ほどの forループと同様に、括弧 ( )は不要で、中括弧 { }は必要です。

func sqrt(x float64) string {
	if x < 0 {
		return sqrt(-x) + "i"
	}
	return fmt.Sprint(math.Sqrt(x))
}

func main() {
	fmt.Println(sqrt(2), sqrt(-4))
}

↓出力結果

1.4142135623730951 2i

  • まず **math.Sqrt**関数を使って **xの平方根を求めます。そして、fmt.Sprint関数を使って、求めた平方根を文字列に変換しています。fmt.Sprint**関数は、引数に渡された値を文字列に変換して返す関数であり、複数の引数を受け取ることができます。

If with a short statement

if ステートメントは、 for のように、条件の前に、評価するための簡単なステートメントを書くことができます。

ここで宣言された変数は、 if のスコープ内だけで有効です。

func pow(x, n, lim float64) float64 {
	if v := math.Pow(x, n); v < lim {
		return v
	}
	return lim
}

func main() {
	fmt.Println(
		pow(3, 2, 10),
		pow(3, 3, 20),
	)
}
  • math.Pow(x,y)は、xのy乗を返します。xが浮動小数点数型、yが整数型でなければなりません。また、xが負の場合、yは整数である必要があります。

If and else

ifステートメントで宣言された変数は、 elseブロック内でも使うことができます。

func pow(x, n, lim float64) float64 {
	if v := math.Pow(x, n); v < lim {
		return v
	} else {
		fmt.Printf("%g >= %g\n", v, lim)
	}
	// can't use v here, though
	return lim
}

func main() {
	fmt.Println(
		pow(3, 2, 10),
		pow(3, 3, 20),
	)
}
  • %gは浮動小数点数を指数表記または10進表記のどちらかで出力することができるフォーマット指定子です。%e%fの間の表記を自動的に選択し、より短い表現を使用します。

Exercise: Loops and Functions

関数とループを使った簡単な練習として、平方根の計算を実装してみましょう: 数値 x が与えられたときに z² が最も x に近い数値 z を求めたいと思います。

コンピュータは通常ループを使って x の平方根を計算します。 いくつかの z を推測することから始めて、z² がどれほど x に近づいているかに応じて z を調整できます。

z -= (z*z - x) / (2*z)

実際の平方根に近い答えになるまでこの調整を繰り返すことによって、推測はより良いものなります。

これを func Sqrt に実装してください。 何が入力されても z の適切な開始推測値は 1 です。 まず計算を 10 回繰り返してそれぞれの z を表示します。 x (1, 2, 3, ...) のさまざまな値に対する答えがどれほど近似し、 推測が速くなるかを確認してください。

Hint: 浮動小数点の変数を初期化して宣言するには、型でキャストするか、浮動小数点を使ってみてください:

z := 1.0
z := float64(1)

次に値が変化しなくなった (もしくはごくわずかな変化しかしなくなった) 場合にループを停止させます。 それが 10 回よりも多いか少ないかを確認してください。 x や x/2 のように他の初期推測の値を z に与えてみてください。 あなたの関数の結果は標準ライブラリの math.Sqrt にどれくらい近づきましたか?

(メモ: アルゴリズムの詳細について興味がある人のために説明すると、 上の z² − xという式は、z² が最終的な期待値 x からどのくらい離れているかを表しています。 除算の 2z は z² の導関数で、z² の変化の大きさに応じて z の調整値を変化させます。 この一般的なアプローチはニュートン法と呼ばれています。 多くの関数で有効ですが、平方根で特に有効です。)

第一段階

package main

import (
	"fmt"
	"math"
)

func Sqrt(x float64) float64 {
	z := 1.0
	for i := 0;i < 10;i++{
		z -= (z*z - x) / (2*z)
		fmt.Println(z)
	}
	return math.Sqrt(x)
}

func main() {
	fmt.Println(Sqrt(2))
}

第二段階

package main

import (
	"fmt"
	"math"
)

func Sqrt(x float64) float64 {
	z := 1.0
	n :=100.0
	for i := 0;i < 10;i++{
		z -= (z*z - x) / (2*z)
		if math.Abs(z - n) < 0.000000000000001{
			return math.Sqrt(x)
		}
		n = z
		fmt.Println(z)
	}
	return math.Sqrt(x)
}

func main() {
	fmt.Println(Sqrt(2))
}

Switch

switchステートメントは if - elseステートメントのシーケンスを短く書く方法です。

func main() {
	fmt.Print("Go runs on ")
	switch os := runtime.GOOS; os {
	case "darwin":
		fmt.Println("OS X.")
	case "linux":
		fmt.Println("Linux.")
	default:
		// freebsd, openbsd,
		// plan9, windows...
		fmt.Printf("%s.\n", os)
	}
}
  • osという変数にオペレーティングシステムの名前を代入し、switch分で条件に応じた文字列を出力している。
  • どの条件にも合致しないときは、defaultで設定した処理を実行する

Switch evaluation order

switch caseは、上から下へcaseを評価します。 caseの条件が一致すれば、そこで停止(自動的にbreak)します。

func main() {
	fmt.Println("When's Saturday?")
	today := time.Now().Weekday()
	switch time.Saturday {
	case today + 0:
		fmt.Println("Today.")
	case today + 1:
		fmt.Println("Tomorrow.")
	case today + 2:
		fmt.Println("In two days.")
	default:
		fmt.Println("Too far away.")
	}
}

Switch with no condition

条件のないswitchは、 switch trueと書くことと同じです。

func main() {
	t := time.Now()
	switch {
	case t.Hour() < 12:
		fmt.Println("Good morning!")
	case t.Hour() < 17:
		fmt.Println("Good afternoon.")
	default:
		fmt.Println("Good evening.")
	}
}

Defer

defer ステートメントは、 defer へ渡した関数の実行を、呼び出し元の関数の終わり(returnする)まで遅延させるものです。

defer へ渡した関数の引数は、すぐに評価されますが、その関数自体は呼び出し元の関数がreturnするまで実行されません。

func main() {
	defer fmt.Println("world")

	fmt.Println("hello")
}
  • deferの呼び出し元であるmainメソッドが終了する際に、”world”が出力される

Stacking defers

deferへ渡した関数が複数ある場合、その呼び出しはスタック( stack)されます。 呼び出し元の関数がreturnするとき、 deferへ渡した関数は LIFO(last-in-first-out)の順番で実行されます。

func main() {
	fmt.Println("counting")

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

	fmt.Println("done")
}

↓出力結果

counting
done
9
8
7
6
5
4
3
2
1
0
  • 数字がスタックされ、最後に逆順に出力される

More types: structs, slices, and maps.

Pointers

ポインタとは?

https://zenn.dev/urakawa_jinsei/articles/a098fd840dfcbb

変数 Tのポインタは、 *T型で、ゼロ値は nilです

var p *int

&オペレータは、そのオペランド( operand )へのポインタを引き出します。

i := 42
p = &i

*オペレータは、ポインタの指す先の変数を示します。

fmt.Println(*p) // ポインタpを通してiから値を読みだす
*p = 21         // ポインタpを通してiへ値を代入する
func main() {
	i, j := 42, 2701

	p := &i         // point to i
	fmt.Println(*p) // read i through the pointer
	*p = 21         // set i through the pointer
	fmt.Println(i)  // see the new value of i

	p = &j         // point to j
	*p = *p / 37   // divide j through the pointer
	fmt.Println(j) // see the new value of j
}

↓出力結果

42
21
73
  • 変数pにiのポインタを代入する
  • pのポインタを出力する
  • pのポインタを21に設定する(iのポインタも21になる)
  • 21が出力される
  • pにjのポインタを代入する
  • pのポインタを73に変更する(jのポインタも73になる)
  • 73が出力される

Structs

struct(構造体)は、フィールド( field)の集まりです。

type Vertex struct {
	X int
	Y int
}

func main() {
	fmt.Println(Vertex{1, 2})
}

↓出力結果

{1 2}
  • JAVAでいうクラスのようなもの?
  • しかし、structにはメソッドは定義できない

Struct Fields

structのフィールドは、ドット( . )を用いてアクセスします。

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
	v.X = 4
	fmt.Println(v.X)
}

Pointers to structs

structのフィールドは、structのポインタを通してアクセスすることもできます。

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
	p := &v
	p.X = 1e9
	fmt.Println(v)
}

↓出力結果

{1000000000 2}
  • **1e9**は、指数表記による数値表現で、数値 **1**に **10**の **9**乗を掛けた値であることを表しています。

Struct Literals

structリテラルは、フィールドの値を列挙することで新しいstructの初期値の割り当てを示しています。

Name: 構文を使って、フィールドの一部だけを列挙することができます(この方法でのフィールドの指定順序は関係ありません)。 例では X: 1 として X だけを初期化しています。

& を頭に付けると、新しく割り当てられたstructへのポインタを戻します。

type Vertex struct {
	X, Y int
}

var (
	v1 = Vertex{1, 2}  // has type Vertex
	v2 = Vertex{X: 1}  // Y:0 is implicit
	v3 = Vertex{}      // X:0 and Y:0
	p  = &Vertex{1, 2} // has type *Vertex
)

func main() {
	fmt.Println(v1, p, v2, v3)
}

↓出力結果

{1 2} &{1 2} {1 0} {0 0}
  • 何も設定しなければ、intの初期値である0が出力される

Arrays

[n]T 型は、型 T の n 個の変数の配列( array )を表します。

以下は、intの10個の配列を宣言しています:

var a [10]int
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)
}

↓出力結果

Hello World
[Hello World]
[2 3 5 7 11 13]

Slices

配列は固定長です。一方で、スライスは可変長です。より柔軟な配列と見なすこともできます。 実際には、スライスは配列よりもより一般的です。

型 []T は 型 T のスライスを表します。

コロンで区切られた二つのインデックス low と high の境界を指定することによってスライスが形成されます:

a[low : high]

これは最初の要素は含むが、最後の要素は除いた半開区間を選択します。

次の式は a の要素の内 1 から 3 を含むスライスを作ります。

a[1:4]
func main() {
	primes := [6]int{2, 3, 5, 7, 11, 13}

	var s []int = primes[1:4]
	fmt.Println(s)
}

↓出力結果

[3 5 7]
  • 変数sに、変数primesの要素番号1~3のデータを代入する

What is the difference between an array and a slice in Go?

**[]**の中に要素数を指定しない場合、それはスライスを表します。

Slices are like references to arrays

スライスは配列への参照のようなものです。

スライスはどんなデータも格納しておらず、単に元の配列の部分列を指し示しています。

スライスの要素を変更すると、その元となる配列の対応する要素が変更されます。

同じ元となる配列を共有している他のスライスは、それらの変更が反映されます。

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)
}

↓出力結果

[John Paul George Ringo]
[John Paul] [Paul George]
[John XXX] [XXX George]
[John XXX George Ringo]
  • b[0]を”XXX”に変更したことで、bとnames[1:3]にも変更が反映される

Slice literals

スライスのリテラルは長さのない配列リテラルのようなものです。

これは配列リテラルです:

[3]bool{true, true, false}

そして、これは上記と同様の配列を作成し、それを参照するスライスを作成します:

[]bool{true, true, false}
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)
}

↓出力結果

[2 3 5 7 11 13]
[true false true true false true]
[{2 true} {3 false} {5 true} {7 true} {11 false} {13 true}]
  • **[]struct { i int; b bool }は、要素がibという2つのフィールドを持つ構造体を要素とするスライスを表しています。この構造体は、整数値を表すiフィールドと真偽値を表すb**フィールドを持っています。

Slice defaults

スライスするときは、それらの既定値を代わりに使用することで上限または下限を省略することができます。

既定値は下限が 0 で上限はスライスの長さです。

以下の配列において

var a [10]int

これらのスライス式は等価です:

a[0:10]
a[:10]
a[0:]
a[:]
func main() {
	s := []int{2, 3, 5, 7, 11, 13}

	s = s[1:4]
	fmt.Println(s)

	s = s[:2]
	fmt.Println(s)

	s = s[1:]
	fmt.Println(s)
}

↓出力結果

[3 5 7]
[3 5]
[5]
  • 配列のindexは0から始まる
  • 上限値はその値を含まない
  • sがその都度更新される

Slice length and capacity

スライスは長さ( length )と容量( capacity )の両方を持っています。

スライスの長さは、それに含まれる要素の数です。

スライスの容量は、スライスの最初の要素から数えて、元となる配列の要素数です。

スライス s の長さと容量は len(s) と cap(s) という式を使用して得ることができます。

十分な容量を持って提供されているスライスを再スライスすることによって、スライスの長さを伸ばすことができます。

func main() {
	s := []int{2, 3, 5, 7, 11, 13}
	printSlice(s)

	// Slice the slice to give it zero length.
	s = s[:0]
	printSlice(s)

	// Extend its length.
	s = s[:4]
	printSlice(s)

	// Drop its first two values.
	s = s[2:]
	printSlice(s)
}

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

↓出力結果

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 slices

Go言語において、**nil**はゼロ値であり、ポインタ型、インタフェース型、マップ型、スライス型、チャネル型、関数型の変数に対して使用されます。

**nilは、変数が参照するメモリアドレスが存在しないことを示します。つまり、変数が何も指していない、または値が設定されていない状態を表します。nil**を使用することで、変数の初期化や、エラー処理、オブジェクト指向プログラミングの概念などで役立ちます。

例えば、スライス型の変数を初期化せずに使用しようとすると、ランタイムエラーが発生しますが、**nil**を使用して変数を初期化することで、エラーを回避できます。

func main() {
	var s []int
	fmt.Println(s, len(s), cap(s))
	if s == nil {
		fmt.Println("nil!")
	}
}

↓出力結果

[] 0 0
nil!
  • nilはJavaのnull

The difference between len and cap

Go言語において、**len()cap()**はスライス型やマップ型などのコレクション型のデータ構造に対して使用されます。

**len()**は、スライスやマップ、文字列などの長さを返す関数です。スライスの場合、要素数を返します。マップの場合、キー-値ペアの数を返します。文字列の場合、文字列の長さをバイト数で返します。

例えば、以下のコードでは、**len()**関数を使用して、スライスの長さを取得しています。

s := []int{1, 2, 3, 4, 5}
fmt.Println(len(s)) // 5

**cap()**は、スライスや配列の容量を返す関数です。スライスの場合、メモリ上に確保された領域の大きさを返します。配列の場合、配列の要素数と同じ値を返します。

例えば、以下のコードでは、**cap()**関数を使用して、スライスの容量を取得しています。

s := make([]int, 5, 10)
fmt.Println(cap(s)) // 10

つまり、**len()はスライスやマップの要素数を返し、cap()はスライスや配列の容量を返します。このため、スライスの要素数がlen()で、スライスの容量がcap()**で取得できます。

len()は実際に入っている要素の数、cap()は容量のこと

Creating a slice with make

スライスは、組み込みの make 関数を使用して作成することができます。 これは、動的サイズの配列を作成する方法です。

make 関数はゼロ化された配列を割り当て、その配列を指すスライスを返します。

a := make([]int, 5)  // len(a)=5

make の3番目の引数に、スライスの容量( capacity )を指定できます。 cap(b) で、スライスの容量を返します:

b := make([]int, 0, 5) // len(b)=0, cap(b)=5

b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:]      // len(b)=4, cap(b)=4
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)
}

↓出力結果

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

スライスは、他のスライスを含む任意の型を含むことができます。

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], " "))
	}
}

↓出力結果

X _ X
O _ X
_ _ O
  • 多次元

Appending to a slice

スライスへ新しい要素を追加するには、Goの組み込みの append を使います。

スライスへ新しい要素を追加するにはGoの組み込みの append を使います

上の定義を見てみましょう。 append への最初のパラメータ s は、追加元となる T 型のスライスです。 残りの vs は、追加する T 型の変数群です。

append の戻り値は、追加元のスライスと追加する変数群を合わせたスライスとなります。

もし、元の配列 s が、変数群を追加する際に容量が小さい場合は、より大きいサイズの配列を割り当て直します。 その場合、戻り値となるスライスは、新しい割当先を示すようになります。

func main() {
	var s []int
	printSlice(s)

	// append works on nil slices.
	s = append(s, 0)
	printSlice(s)

	// The slice grows as needed.
	s = append(s, 1)
	printSlice(s)

	// We can add more than one element at a time.
	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)
}

↓出力結果

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つ目はインデックスの場所の要素のコピーです。

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)
	}
}

↓出力結果

2**0 = 1
2**1 = 2
2**2 = 4
2**3 = 8
2**4 = 16
2**5 = 32
2**6 = 64
2**7 = 128
  • Javaでいう拡張for文

Range continued

インデックスや値は、 " _ "(アンダーバー) へ代入することで捨てることができます。

for i, _ := range pow
for _, value := range pow

もしインデックスだけが必要なのであれば、2つ目の値を省略します。

for i := range pow
func main() {
	pow := make([]int, 10)
	for i := range pow {
		pow[i] = 1 << uint(i) // == 2**i
	}
	for _, value := range pow {
		fmt.Printf("%d\n", value)
	}
}

↓出力結果

1
2
4
8
16
32
64
128
256
512
  • 長さが10の0で初期化されたスライスを作る
  • 2のインデックス乗を配列に代入する
  • valueのみを出力する

Exercise: Slices

Pic 関数を実装してみましょう。 このプログラムを実行すると、生成した画像が下に表示されるはずです。 この関数は、長さ dy のsliceに、各要素が8bitのunsigned int型で長さ dx のsliceを割り当てたものを返すように実装する必要があります。 画像は、整数値をグレースケール(実際はブルースケール)として解釈したものです。

生成する画像は、好きに選んでください。例えば、面白い関数に、 (x+y)/2 、 x*y 、 x^y などがあります。

ヒント:( [][]uint8 に、各 []uint8 を割り当てるためにループを使用する必要があります)

ヒント:( uint8(intValue) を型の変換のために使います)

package main

import "golang.org/x/tour/pic"

func Pic(dx, dy int) [][]uint8 {
	s := make([][]uint8, dy)
	for i := range s {
		s[i] = make([]uint8,dx)
	}
	for x := range s{
		for y := range s[x]{
			s[x][y] = uint8(x * y)
		}
	}
	return s
}

func main() {
	pic.Show(Pic)
}

Maps

map はキーと値とを関連付けます(マップします)。

マップのゼロ値は nil です。 nil マップはキーを持っておらず、またキーを追加することもできません。

make 関数は指定された型のマップを初期化して、使用可能な状態で返します。

以下は、Go言語で空のマップを作成する方法です。

m := make(map[key_type]value_type)

ここで、**key_typeはマップのキーの型を表し、value_type**はマップの値の型を表します。例えば、文字列をキーとして整数を値として持つマップを作成する場合は、以下のように書きます。

m := make(map[string]int)
type Vertex struct {
	Lat, Long float64
}

func main() {
	m := make(map[string]Vertex)
	m["Bell Labs"] = Vertex{
		40.68433, -74.39967,
	}
	fmt.Println(m["Bell Labs"])
}

↓出力結果

{40.68433 -74.39967}
  • float64の値を2つ持つVertexというstructを宣言する
  • キーがstring,バリューがVertex型のmapを作成する

Map literals

mapリテラルは、structリテラルに似ていますが、 キー ( key )が必要です。

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)
}

↓出力結果

map[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]

Map literals continued

もし、mapに渡すトップレベルの型が単純な型名である場合は、リテラルの要素から推定できますので、その型名を省略することができます。

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)
}

Mutating Maps

map m の操作を見ていきましょう。

m へ要素(elem)の挿入や更新:


m[key] = elem

要素の取得:

elem = m[key]

要素の削除:

delete(m, key)

キーに対する要素が存在するかどうかは、2つの目の値で確認します:

elem, ok = m[key]

もし、 m に key があれば、変数 ok は true となり、存在しなけば、 ok は false となります。

なお、mapに key が存在しない場合、 elem はmapの要素の型のゼロ値となります。

Note: もし elem や ok をまだ宣言していなければ、次のように := で短く宣言できます:

elem, ok := m[key]
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)
}

↓出力結果

The value: 42
The value: 48
The value: 0
The value: 0 Present? false

Exercise: Maps

WordCount 関数を実装してみましょう。string s で渡される文章の、各単語の出現回数のmapを返す必要があります。 wc.Test 関数は、引数に渡した関数に対しテストスイートを実行し、成功か失敗かを結果に表示します。

strings.Fields で、何かヒントを得ることができるはずです。

Note: このテストスイートで何を入力とし、何を期待しているかについては、golang.org/x/tour/wcを見てみてください。

package main

import (
	"golang.org/x/tour/wc"
	"strings"
)

func WordCount(s string) map[string]int {
	sl := strings.Fields(s)
	m := make(map[string]int)
	for _,word := range sl{
		m[word] += 1
	}
	return m
}

func main() {
	wc.Test(WordCount)
}

Function values

関数も変数です。他の変数のように関数を渡すことができます。

関数値( function value )は、関数の引数に取ることもできますし、戻り値としても利用できます。

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))
}

↓出力結果

13
5
81

Function closures

Goの関数は クロージャ( closure ) です。 クロージャは、それ自身の外部から変数を参照する関数値です。 この関数は、参照された変数へアクセスして変えることができ、その意味では、その関数は変数へ"バインド"( bind )されています。

例えば、 adder 関数はクロージャを返しています。 各クロージャは、それ自身の sum 変数へバインドされます。

Go言語のクロージャは、関数とその関数が参照する変数を包み込むオブジェクトです。クロージャを作成する場合、関数内で定義された変数は、関数が終了してもスコープを保持し、後続の呼び出しで状態を保持します。これにより、関数内での処理を状態とともに保持することができます。

クロージャを使うことで、関数内で使用する変数をグローバル変数にせずに、関数に内包させることができます。また、クロージャを用いることで、関数が必要になった時点での状態を保持し、後続の処理で使用できるため、プログラムの効率を上げることができます。

func getCounter() func() int {
    i := 0
    return func() int {
        i++
        return i
    }
}
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),
		)
	}
}

↓出力結果

0 0
1 -2
3 -6
6 -12
10 -20
15 -30
21 -42
28 -56
36 -72
45 -90
  • **func adder() func(int) intは、int型の引数を受け取り、int**型の値を返す関数を返す高階関数(関数を返す関数)を定義しています。
  • 変数sumはバインドされているため、次々に更新される

Exercise: Fibonacci closure

関数を用いた面白い例を見てみましょう。

fibonacci (フィボナッチ)関数を実装しましょう。この関数は、連続するフィボナッチ数(0, 1, 1, 2, 3, 5, ...)を返す関数(クロージャ)を返します。

package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
	prev1, prev2 := 0, 1
	return func() int {
		curr := prev1
		prev1, prev2 = prev2, prev1+prev2
		return curr
	}
}

func main() {
	f := fibonacci()
	for i := 0; i < 10; i++ {
		fmt.Println(f())
	}
}
3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?