Go 言語の変数入門

GolangBeginner
オンラインで実践に進む

はじめに

コンピュータプログラミング言語において、なぜ変数が必要なのか。これは古くからの問いである。私たちが LabEx の URL がlabex.ioであることを覚えているのと同じように、コンピュータプログラムも使用するためにいくつかのデータを覚える必要がある。

変数の目的は、1 つのデータを表すことである。このセクションでは、Go 言語において変数がどのように使用されるかを探っていく。

ポイント

  • 変数宣言
  • 変数初期化
  • 変数の使用
  • 変数の生存期間
  • 定数

変数とは何か?

変数とは何か。簡単に言えば、変数は 1 つの可変データを格納および保存するために使用されるコンテナである。

Go のようなコンパイル言語では、変数の型は固定されている。

変数の型が固定されているとはどういう意味か

これは、変数が 1 つの型のデータのみを保持できることを意味する。言い換えると、変数コンテナに格納されるアイテムは固定されている。

変数が果物を保持するために使用される場合、それは果物のみを保持する必要がある。コンテナが果物を保持した後は、クッキーを保持するために使用できなくなる。これは、変数に 2 つの異なる型のデータを割り当てることができないというコードに反映される。

変数の可変性

名前の通り、変数の値は、その型が許す範囲を超えない限り変更することができる。

Go 言語は、変数に関して次のルールを持っている:

  • 名前は必ず英字、数字、およびアンダースコアで構成されなければならない。
  • 変数識別子は数字で始めることはできない。
  • 識別子は予約語ではなければならない。予約語を確認する
  • 変数名は大文字小文字が区別されるが、同じ名前の 2 つの変数を区別するために異なる大文字小文字を使用することは推奨されない

一般的な宣言方法

Go 言語では、一般的な方法で変数を宣言するために使用されるキーワードはvarである。

宣言の形式は:var identifier typeで、これは次の意味を持つ:

var variableName variableType.

Go における一般的な変数型は何か。

キーワード 説明
int 整数。小学校の算数で学ぶ最も一般的なデータ型。
string 文字列。二重引用符で囲まれた文字の列で、たとえば:"hello,world"
bool ブール型。真偽を表し、2 つの可能な値:trueまたはfalseがある。

このセクションの焦点は変数型ではないため、最も一般的な 3 つの型のみを挙げている。

より多くの型については、次のコースで説明する。

変数をどのように宣言するか

では、aという名前の整数型の変数を宣言してみよう。

var a int

どのように覚えればよいか。頭の中で黙々と言ってみると:

`a`という名前の変数を定義し、それはint型である。

多くの伝統的なプログラミング言語とは異なり、Go で使用される変数型は変数名の後に置かれる。

このような変数の宣言方法は、コードを左から右に読みやすくし、C 言語の螺旋状の読み取りロジックを回避する。詳細については、公式ドキュメントを参照のこと。

変数にはどのような名前を付けるべきか

良い変数名は、変数の意味を明確に示す必要がある。

変数を命名する際には、その表現力に注意を払い、略語を使わないようにする必要がある。

ここでは、変数命名の基本的な方法:キャメルケース命名規則について簡単に紹介する。

キャメルケース命名規則は、混合ケースの文字を使って変数を表す。最初の単語は小文字で、その後の各単語の先頭文字は大文字になる。

たとえば:currentDate。最初の単語currentは小文字で、2 番目の単語Dateは大文字で始まる。

このように、現在の日付を表す変数は簡単に理解できる。

この命名規則は、一般的に重要で頻繁に使用される変数に使用され、一時的な変数は重複を引き起こさない限り簡略化することができる。

一括宣言方法

では、3 つの変数を宣言してみましょう。

var a int // a という名前の整数型変数を宣言する
var b int // b という名前の整数型変数を宣言する
var c int // c という名前の整数型変数を宣言する

細心の注意を払っている学生の方は、a, b, cの 3 つの変数がすべてint型であることに気付かれたかもしれません。

その場合、変数名をカンマで結合して、コード量を削減することができます。

var a, b, c int // 3 つの変数 a, b, c を整数型として宣言する

しかし、3 つの変数が異なる型の場合どうなるでしょうか。

var a int    // a という名前の整数型変数を宣言する
var b string // b という名前の文字列型変数を宣言する
var c bool   // c という名前のブール型変数を宣言する

パッケージをインポートする際に似たような状況に遭遇したことがあるかもしれません。たくさんの異なる名前のパッケージを一緒にインポートする必要があるので、似たような書き方を使うことができます。

var (
    a int
    b string
    c bool
)

このようなパッケージをインポートする際のような宣言の仕方は、一般的にグローバル変数を定義する際に使われます。

デフォルト初期化

Go では、変数が宣言されるときにすべての変数に初期値が与えられます。変数の初期値が何であるかを調べてみましょう!

~/projectディレクトリにvarExercise.goという名前のファイルを作成します。

touch ~/project/varExercise.go

次のコードをファイルに書き込みます。

package main

import "fmt"

func main() {
    var a int
    var b string
    var c bool
    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)
}

自分で実行してみて、以下の表と一致するかどうかを確認してみてください。

go run varExercise.go

初期値の型は以下のとおりです。

キーワード 説明 初期値
int 整数 0
string 文字列 ""
bool ブール型 false

標準初期化

変数の型は初期値によって決定できるので、デフォルト値や既に宣言された変数を変更できるでしょうか。

var a int = 1
var b string = "labex"
var c bool = true

a = 233
b = "labex"
c = false

上記のように、変数を宣言した後に=を追加し、その後に変数型と互換性のある初期値を指定すればよい。値を変更したい場合は、変数名の後に=と同じ型の別の値を指定するだけでよい。

varExercise.goファイルを変更します。

package main

import "fmt"

func main() {
    // 宣言と初期化
    var a int = 1
    var b string = "labex"
    var c bool = true

    // 変数を出力
    fmt.Println("変更前:")
    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)

    // 変数を変更
    a = 233
    b = "labex"
    c = false

    // 変更後の変数を出力
    fmt.Println("変更後:")
    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)
}

コードを実行すると、以下の出力になります。

$ go run varExercise.go
変更前:
1
labex
true
変更後:
233
labex
false

自分で実行して初期値を変更してみてください。

先ほど言ったように、変数に割り当てる初期値は変数宣言と同じ型でなければならない。異なる場合にはどうなるでしょうか。

たとえば、a変数に"labex"を初期値として割り当ててみましょう。

package main

import "fmt"

func main() {
    var a int = "labex"
    fmt.Println(a)
}

コードを実行すると:

$ go run varExercise.go
## command-line-arguments
./varExercise.go:6:12: cannot use "labex" (type untyped string) as type int in assignment

図のように、int型の変数に"labex"のような文字列型を割り当てることはできない。これは、Go が強力な型付きのコンパイル言語であり、コンパイルできないためである。

推論による型宣言

Go は初期値によって変数の型を決定できるので、型を明示的に指定するステップを省略することで、型宣言のプロセスを簡略化できますか?

package main

import "fmt"

func main() {
    // var a int = 1
    var a = 1 // 型が推論される
    fmt.Println(a)
}

今では、変数を定義する際にvarキーワードさえ必要ありません。

このような変数の宣言と初期化の方法は、一括宣言方法と組み合わせることもできます。

a, b, c := 0
// 変数 a, b, c を整数型として宣言し、初期値を 0 とする
a, b, c := 0, "", true
// 変数 a, b, c をそれぞれ整数型、文字列型、ブール型として宣言する

短い宣言は非常に便利ですが、:=は代入演算子ではないことに注意してください。これは変数を宣言する方法であり、Go に固有のもので、関数内のローカル変数を宣言および初期化するために使用されます。変数の型は式に基づいて自動的に推論されます。

時々、次のようなコードを書きます。

func main() {
    a := 1
    println(a)
    a := 2
    println(a)
}

コンパイラはコードにエラーがあると言います。なぜなら、変数aが再宣言されているからです。ただし、次のように書くと:

func main() {
    a := 1
    if true {
        a := 2
        println(a) // 出力:2
    }
    println(a) // 出力:1
}

このような出力があるのは、上の値が1aと下の値が2aが同じ変数スコープ(同じ波括弧内)にないため、コンパイラはそれらを 2 つの異なる変数として扱うからです。

コンパイラはあなたの間違いを指摘しませんが、予期しない出力があります。

Go では、次のように規定されています。

関数の外の各文はキーワード(varfuncなど)で始める必要があります。

したがって、短い変数宣言はローカル変数の宣言にのみ使用でき、グローバル変数の宣言には使用できません。

では、グローバル変数とは何か、ローカル変数とは何か?

これは変数の生存期間の概念に関係しており、次のセクションで説明します。

変数のスコープ

変数のスコープとは、プログラム内の変数が有効な範囲、つまりどのように使用できるかを指します。

宣言した変数を使用しない場合、コードがコンパイルされないことに気付いたはずです。

言い換えると、Go をコンパイルする際には、各変数が使用されているか、つまりそのスコープ内で使用されているかどうかがチェックされます。

宣言位置に基づいて、変数を簡単に 3 つのタイプに分類できます。

  • 関数内で定義される変数は、ローカル変数と呼ばれます
  • 関数外で定義される変数は、グローバル変数と呼ばれます
  • 関数定義内で定義される変数は、形式パラメータと呼ばれます

ローカル変数

このセクションでは、定義する変数の大部分はローカル変数です。

package main

import "fmt"

func main() { // 関数本体
    var a int = 1 // ローカル変数
    fmt.Println(a)
}

ローカル変数は関数本体の中で定義されます。たとえば、main関数内で定義されたaのように。変数aのスコープはmain関数内に限定されます。

同時に、main関数内で変数が使用されていない場合、コンパイラはエラーを投げます。

グローバル変数

ただし、グローバル変数を定義することもできます。

package main

import "fmt"

var a int = 1 // グローバル変数
func main() { // 関数本体
    fmt.Println(a)
}

グローバル変数は関数本体の外で定義され、そのスコープはプログラム全体をカバーします。どの関数でも呼び出されていなくても、コンパイラはエラーを報告しません。

グローバル変数が呼び出されていなくてもエラーにならない理由を考えてみてください。

回答

これは、グローバル変数が別のパッケージで呼び出される可能性があるからです。

形式パラメータ変数に関する詳細は、後続の関数関連のコースで説明されます。

変数の生存期間

飛び去る鳥は影を隠す。狡兎死に、走狗烹る。 - 史記

変数が目的を果たしたら、メモリ使用量を削減するために破棄する必要があります。

  • グローバル変数:グローバル変数の生存期間は、プログラム全体の実行時間と一致します。プログラムが停止すると、グローバル変数はメモリからクリアされます。
  • ローカル変数:変数にアクセスできなくなったとき、そのメモリ空間は解放されます。

このような設計が、Go の高性能と効率的な空間利用の鍵となっています。

変数の生存期間中は、再宣言することはできません。

varExercise.goに次のように書くことができます。

package main

import "fmt"

func main() {
    var a int = 1 // ローカル変数、生存期間は main 関数全体に限定されます
    var a int = 2 // 再定義
    fmt.Println(a)
}

コードを実行した後:

go run varExercise.go

次のエラーメッセージが表示されます。

./varExercise.go:7:9: a redeclared in this block
previous declaration at./varExercise.go:6:9

コンパイラがaが再定義されていることを教えてくれます。

定数

人生の多くのことは定数のようなものです。私たちはそれを認識することはできますが、変更することはできません。

プログラムの実行全体を通して変更されない変数の場合、それを定数として定義する必要があります。

定数は変数と非常によく似ており、不変の値を持つ変数と考えることもできます。

定数を宣言する際には、var キーワードを const キーワードに置き換えるだけで済みます。

const Pi = 3.14159 // 型推論による初期化 (Using type inference initialization)

~/project ディレクトリに constExercise.go という名前のファイルを作成します。

touch ~/project/constExercise.go

定数を変更しようとするとどうなるでしょうか?

package main

import "fmt"

func main() {
    const Pi = 3.14159
    Pi = 2 // Error: cannot assign to Pi
    fmt.Println(Pi)
}

コードを実行します。

$ go run constExercise.go
## command-line-arguments
./constExercise.go:7:8: cannot assign to Pi

コンパイラは、Pi の値を再代入できないことを教えてくれます。

定数を宣言する際には、必ず初期値を指定する必要があります。

そして、定数に割り当てられる初期値は、コンパイル時に固定されている必要があります。

ユーザー定義関数の戻り値は、Go では固定されているとは見なされません。

var a int = 1
// 値は固定されており、宣言は有効です。
const Pi = 3.14159
// 計算された値も固定されており、宣言は有効です。
const c = 1 / Pi
// 組み込み関数からの固定された戻り値は有効です。
const le = len("labex")
// ユーザー定義関数の戻り値は固定されておらず、宣言は無効です。
const le = getLen("labby")
// `a` は固定されていない変数値であり、宣言は無効です。
const k = a

定数宣言が有効かどうかは、次の表にまとめられます。

宣言の種類 有効性
固定値と固定値の式 有効
非固定値(変数)およびそれに対応する式 無効
組み込み関数 (len()) が固定値と固定値の式を受け取る場合 有効
ユーザー定義関数 無効

まとめ

この実験で学んだことを振り返りましょう。

  • 変数を宣言する方法
  • 変数を初期化する方法
  • 変数の生存期間の概念
  • 定数の使用方法とその宣言が有効かどうか

この実験では、Go における変数の基本的な使い方を振り返り、さまざまな状況で変数を宣言および使用する方法を示し、定数を紹介しました。