Go/変数

出典: フリー教科書『ウィキブックス(Wikibooks)』
< Go

Go言語では、プログラムで使用する変数を宣言することができます。変数は、プログラムが処理するデータを保持するために使用されます。Goでは、変数の宣言と同時に初期化することもできます。また、Goでは静的型付け言語であり、変数の型は宣言時に指定する必要があります。このチュートリアルでは、Go言語で変数を宣言し、初期化し、使用する方法を学びます。

変数[編集]

Go言語における変数は、プログラムで使用するデータを保持するために使用されます。変数は、変数名と変数の型を持ちます。Go言語では、静的型付け言語であるため、変数の型は宣言時に指定する必要があります。

変数は宣言されたブロック内で有効であり、ブロックを抜けると変数は破棄されます。Go言語では、短い変数宣言を使用して、変数を宣言することができます。また、変数は値を代入することで初期化することができます。

Go言語では、変数を宣言する際にvarキーワードを使用します。また、Go言語にはいくつかの基本的な型があります。整数型、浮動小数点型、文字列型、ブール型、および複素数型があります。さらに、配列型、スライス型、マップ型、構造体型、インターフェース型、関数型などの複合型もあります。

Go言語では、変数を使用する前に初期化する必要があります。未初期化の変数はゼロ値に初期化されます。また、Go言語では変数のスコープが明確に定義されているため、同じ変数名を再定義することはできません。

変数宣言[編集]

変数宣言では、1つまたは複数の変数を作成し、対応する識別子を結合し、それぞれに型と初期値を与えます[1]

構文
VarDecl     = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) ;
VarSpec     = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) ;

ゼロ値[編集]

Goで変数を使うためには、宣言が必要です。

宣言とゼロ値
package main

import "fmt"

// main関数は、Goプログラムのエントリーポイントです。
func main() {
    // 変数nを宣言します。
    var n int

    // Printf関数を使用して、変数nの値と型を出力します。
    fmt.Printf("var n int => 値:%v 型:%T\n", n, n)
}
実行結果
var n int => 値:0 型:int
解説
        var n int
int型の変数 n を宣言しています。
        fmt.Printf("var n int => 値:%v 型:%T\n", n, n)
fmt.Printf はC言語のprintf関数に影響を受けた関数で、fmt パッケージに属し書式化付きで与えられた引数を表示します。
ここで使った書式は %v と %T の2つの動詞で、%v は引数の値を引数の値にあった型で表示します。%T は引数の型を表示します。
ゼロ値
大事なのは値が 0 であることで、これは未定義な値がたまたま 0 だったのではなく言語仕様に従った結果だということです。
このようにある型の変数が初期化さなかったときに初期値とされる値をゼロ値(Zero value)と呼びます。
様々な型のゼロ値
package main

import "fmt"

// main関数は、Goプログラムのエントリーポイントです。
func main() {
	// 変数iは、整数型です。
	var i int
	fmt.Printf("var i int => 値:%v 型:%T\n", i, i)

	// 変数fは、浮動小数点数型です。
	var f float64
	fmt.Printf("var f float64 => 値:%v 型:%T\n", f, f)

	// 変数bは、真理値型です。
	var b bool
	fmt.Printf("var b bool => 値:%v 型:%T\n", b, b)

	// 変数sは、文字列型です。
	var s string
	fmt.Printf("var s string => 値:%v 型:%T\n", s, s)

	// 変数aは、5要素の整数配列型です。
	var a [5]int
	fmt.Printf("var a [5]int => 値:%v 型:%T\n", a, a)

	// 変数eは、整数スライス型です。
	var e []int
	fmt.Printf("var e []int => 値:%v 型:%T\n", e, e)

	// 変数rは、Rune型(int32型のalias)です。
	var r rune
	fmt.Printf("var r rune => 値:%v 型:%T\n", r, r)

	// 変数tは、文字列型のフィールド Name と int型のフィールド Age を持つ無名の構造体です。
	var t struct {
		Name string
		Age  int
	}
	fmt.Printf("var struct { Name string; Age int } => 値:%v 型:%T\n", t, t)
}
実行結果
var i int => 値:0 型:int
var f float64 => 値:0 型:float64
var b bool => 値:false 型:bool
var s string => 値: 型:string
var a [5]int => 値:[0 0 0 0 0] 型:[5]int
var e []int => 値:[] 型:[]int
var r rune => 値:0 型:int32 
var struct { Name string; Age int } => 値:{ 0} 型:struct { Name string; Age int }
型の説明の前なので、多くの型とその書式は初見かもしれませんが、数値は 0 、文字列は ""、複合型は要素ごとに再帰的にゼロ値が与えられます。

初期化と型推論[編集]

Goでは、変数宣言で型を省略すると初期値から型を推論します。

初期化と型推論
package main

import "fmt"

// main関数は、Goプログラムのエントリーポイントです。
func main() {
	// 変数nは、整数型で、初期値は5です。
	var n = 5
	fmt.Printf("var n = 5 => 値:%v 型:%T\n", n, n)
}
実行結果
var n = 5 => 値:5 型:int
解説
        var n = 5

で変数 n を初期値を 5 で宣言しています。 明示的に型を伴って宣言していませんが、初期値の 5 から int が型推定されます。

fmt.Printf
        fmt.Printf("var n = 5 => 値:%v 型:%T\n", n, n)

短い変数宣言[編集]

変数宣言にはもう1つの構文があります。

短い変数宣言
短い変数宣言は、以下の構文を使用します。
構文
ShortVarDecl = IdentifierList ":=" ExpressionList ;

これは、初期化式を持つが型を持たない通常の変数宣言の短縮形です。

コード例
package main

import "fmt"

// main関数は、Goプログラムのエントリーポイントです。
func main() {
	// 変数nは、整数型で、初期値は5です。
	n := 5
	fmt.Printf("var n = 5 => 値:%v 型:%T\n", n, n)
}
実行結果
(略)
解説
        n := 5

同じ様に、int 型の変数 n を初期値を 5 で宣言しています。 var を伴った宣言と異なり、初期値は省略できません。

短い変数宣言は、if文、switch文やfor文に「文スコープな変数」を宣言する時に使われます。

複数の変数の一括宣言[編集]

構文
VarDecl     = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) ;

の後半 "(" { VarSpec ";" } ")" は、宣言子の繰り返しを表しています。

コード例
package main

import "fmt"

// aは、整数型のパッケージスコープの変数です。
var a = 100

// bは、文字列型のパッケージスコープの変数です。
var b = "string"

// vは、無名構造体のスライス型のパッケージスコープの変数です。
var v = []struct {
	name   string
	number int
}{
	{"abc", 1},
	{"def", 2},
	{"ghi", 3},
	{"xyz", 9},
}

// fは、浮動小数点数型のパッケージスコープの変数です。
var f = 3.14

// main関数は、Goプログラムのエントリーポイントです。
func main() {
	fmt.Printf("a: 値:%v 型:%T\n", a, a)
	fmt.Printf("b: 値:%v 型:%T\n", b, b)
	fmt.Printf("v: 値:%v 型:%T\n", v, v)
	fmt.Printf("f: 値:%v 型:%T\n", f, f)
}
実行結果
a: 値:100 型:int
b: 値:string 型:string
v: 値:[{abc 1} {def 2} {ghi 3} {xyz 9}] 型:[]struct { name string; number int }
f: 値:3.14 型:float64

a,bそれにfはある程度予想が着くと思います。 vは匿名構造体配列の複合リテラルです。 この様に fmt.Printf の %v ならびに %T の2つの動詞(C言語で言う型指定子)は、ユーザー定義を含め任意のオブジェクトの値と型を表示できるので便利です。

整数と浮動小数点数[編集]

上記の例のように、浮動小数点数リテラルの型は float64 です。この他に float32 という型もありますが、 float という型はありません。

整数型のビット精度[編集]

浮動小数点数型以外に整数型 int にも、ビット幅を伴った、int32, int64int8, int16 があります。

このような低精度の整数型にはニーズがないと、感じることがあるかもしれないが回帰型ニューラルネットワークなどの人工知能応用では精度は必ずしも必要なく、メモリーフットプリントの削減と計算時間の削減という意味で今後もニーズがあります。

関数スコープ[編集]

キーワード var を使って宣言した場合も、 簡略表記「:=」を使った場合にも関数の中で宣言された変数のスコープは関数のブロックです(関数の中のfor文やif文で宣言された場合は文スコープになります)。 また簡略表記「:=」はパッケージスコープの変数の宣言には使えません。

文字列型[編集]

文字列変数を使うには、初期化に文字リテラル("文字列" の形式)や文字列を返す関数を使い型推論させる方法と、型名 string を明示する方法があります。

他の言語での文字列型
C言語
string型が無く、'\0'で終端されたchar型の配列で表現していますが、文字列の連結などは標準ライブラリの関数となっており、結果を表すバッファのサイズなどへの責任はプログラマーが負い。このことが度々バッファオーバーフロー等を引き起こし脆弱性の原因となっています。
C++
string型はなく標準テンプレートクラスライブラリーの string クラスを使います。
Java
プリミティブには文字列はなく、String クラスを使います。
JavaScript
文字列プリミティブとStringオブジェクトがあります。
Fortran
ALLOCATABLEを宣言に併用することで可変長文字列に対応(Fortran 2003から)


キーワード[編集]

以下のキーワードは予約済みで、識別子として使用することはできません[2]

Goのキーワード一覧
break        default      func         interface    select
case         defer        go           map          struct
chan         else         goto         package      switch
const        fallthrough  if           range        type 
continue     for          import       return       var

intfloat32 などの(プリミティブな)型名はキーワードには含まれず、予め宣言された識別子となります。

予め宣言された識別子[編集]

ユニバースブロック(universe block[3])では、以下の識別子が暗黙的に宣言されています[4]

予め宣言された識別子
Types:
	any bool byte comparable
	complex64 complex128 error float32 float64
	int int8 int16 int32 int64 rune string
	uint uint8 uint16 uint32 uint64 uintptr

Constants:
	true false iota

Zero value:
	nil

Functions:
	append cap close complex copy delete imag len
	make new panic print println real recover
Go1.18で予め宣言された識別子に型 any が追加されました。これはinterface{}のaliasです。

型変換[編集]

変換は,式の型を変換によって指定された型に変更します。変換は,ソースの中で文字通りに現れるかもしれませんし,式が現れる文脈によって暗示されるかもしれません。 明示的な変換は,T(x)という形式の式で,Tは型であり,xはT型に変換できる式である[5]

構文
Conversion = Type "(" Expression [ "," ] ")" .

Goは静的型システムを導入しており、C言語より厳密に演算における型の一致性を求めます(IntegerとRealの足し算ですら明示的な型変換が必要なPascalと同等に厳格です)。 JavaScriptやRubyなどの動的な型システムを採用したシステムは、「数値型に文字列型を足す」、「文字列を1進める」などの異なる型同士の演算に於いて暗黙の型変換が行われます。 Goでは、どうしても異なる型を持った変数を足しあわせる処理などを場合、どちらかの変数の型に変換し、型を一致させる必要があります。

数値どうしの型変換[編集]

整数型で宣言した数を浮動小数型にしたり、float32型で宣言した数をfloat64型に変換するには、

コード例
package main

import "fmt"

func main() {
    i := 8 // 整数型の変数 i を定義し、8を代入
    fmt.Println("i = ", i)
    fmt.Println("i / 3 = ", i/3) // 整数同士の演算では、小数点以下は切り捨て

    f := float64(i) // i を浮動小数点数型にキャスト
    fmt.Println("f = ", f)
    fmt.Println("f / 3 = ", f/3) // 浮動小数点数同士の演算では、小数点以下も計算される
}
実行結果
i =  8
i / 3 =  2
f =  8
f / 3 =  2.6666666666666665

やや直感的ではありませんが、キーワード var を使った変数宣言で型名を明示した場合の初期値には暗黙の型変換は起こりません。

    f := float64(i)

    var f float64 = 
コンパイルエラー
# command-line-arguments 
./Main.go:10:9: cannot use i (type int) as type float64 in assignment

ではなく

    var f float64 = float64(i)

とする必要があります。

ちなみに C言語の感覚で、

    f := 1.0 * i

とすると f の型は(float64 ではなく)int です。

附録[編集]

チートシート[編集]

package main

import "fmt"

func main() {
    // 型の宣言
    var i int          // 整数型
    var f float64      // 浮動小数点数型
    var b bool         // 真理値型
    var s string       // 文字列型
    var a [5]int       // 整数型の配列型(5要素)
    var e []int        // 整数型のスライス型
    var r rune         // Unicodeのコードポイントを表す整数型
    var m map[int]int  // 整数型のキーと値を持つマップ型
    var c chan int     // int型の値を送受信するチャネル型
    var p *int         // int型の値を指すポインタ型

    // 値の代入
    i = 42                 // 42を代入
    f = 3.14               // 3.14を代入
    b = true               // trueを代入
    s = "Hello, world!"    // "Hello, world!"を代入
    a = [5]int{1, 2, 3, 4, 5}  // [1 2 3 4 5]を代入
    e = []int{1, 2, 3, 4, 5}  // [1 2 3 4 5]を代入
    r = 'あ'               // 'あ'(日本語の「あ」のUnicodeのコードポイント)を代入
    m = map[int]int{1: 2, 3: 4}  // {1:2, 3:4}を代入
    c = make(chan int)     // チャネルを生成し、cに代入
    p = &i                // iのアドレスをポインタ型の変数pに代入

    // 宣言と代入を同時に行う(型推論)
    j := 100                // 整数型の変数jを定義し、100を代入
    g := 3.14               // 浮動小数点数型の変数gを定義し、3.14を代入
    h := "Hello, world!"    // 文字列型の変数hを定義し、"Hello, world!"を代入
    k := [5]int{1, 2, 3, 4, 5}  // 整数型の配列型(5要素)の変数kを定義し、[1 2 3 4 5]を代入
    l := []int{1, 2, 3, 4, 5}  // 整数型のスライス型の変数lを定義し、[1 2 3 4 5]を代入
}

用語集[編集]

  1. 変数 (variable): データを格納するための名前付きの場所。Goでは、varキーワードを使用して宣言されます。
  2. 型 (type): 変数に割り当てる可能性のあるデータの種類を示す。例えば、整数型、文字列型、構造体型、インターフェース型、などがあります。
  3. 初期化 (initialization): 変数に最初に値を割り当てること。Goでは、:=演算子を使用して初期化することができます。
  4. 値 (value): 変数に割り当てられたデータの実際の内容。
  5. 定数 (constant): 値が変更できない変数。Goでは、constキーワードを使用して宣言されます。
  6. ポインタ (pointer): 変数のメモリアドレスを格納する変数。ポインタを使用すると、変数を間接的に変更することができます。
  7. ポインタ型 (pointer type): ポインタが指す変数の型。ポインタ型は、*演算子で示されます。
  8. リテラル (literal): データの固定値。リテラルは、コード内で変数に直接割り当てることができます。
  9. スコープ (scope): 変数がアクセス可能なコード内の範囲。Goでは、ブロックスコープがあります。
  10. パッケージスコープ (package scope): パッケージ内の全てのファイルでアクセス可能な変数。
  11. ローカルスコープ (local scope): ブロック内のみアクセス可能な変数。
  12. シャドウイング (shadowing): スコープ内で同じ名前の変数が宣言された場合、以前の変数を覆い隠すこと。
  13. エイリアス (alias): 一つの型に複数の名前を割り当てること。Goでは、typeキーワードを使用して宣言されます。

脚註[編集]

  1. ^ “Declarations_and scope ¶”. The Go Programming Language Specification. The Go website. (Jul 26, 2021). https://golang.org/ref/spec#Declarations_and_scope. 
  2. ^ “Keywords ¶”. The Go Programming Language Specification. The Go website. (Jul 26, 2021). https://golang.org/ref/spec#Keywords. 
  3. ^ ユニバースブロックは、すべてのGoソーステキストを包含しています。
  4. ^ “Predeclared identifiers ¶”. The Go Programming Language Specification. The Go website. (March 10, 2022). https://golang.org/ref/spec#Predeclared_identifiers. 
  5. ^ “Conversions ¶”. The Go Programming Language Specification. The Go website. (Jul 26, 2021). https://golang.org/ref/spec#Conversions.