はじめに

コンピュータープログラミング言語では、なぜ変数が必要なのでしょうか。これは昔からある疑問です。LabExのURLがlabex.ioだと覚えておけるのと同じように、コンピュータープログラムも処理に使うデータを保持する必要があります。

変数は、データの一部を表すために使います。このセクションでは、Go言語で変数をどのように使うかを学びます。

学習ポイント:

  • 変数宣言
  • 変数の初期化
  • 変数の使用
  • 変数のライフタイム
  • 定数

変数とは

このステップでは、変数が何を表すのか、またGoで変数名と型にどのような制限があるのかを学びます。

変数とは何でしょうか。簡単に言えば、変数は変更可能なデータを格納・保存するための入れ物です。

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

変数の型が固定されているとは

変数には、1種類の型のデータだけを格納できるという意味です。つまり、変数という入れ物に格納できるものの種類は決まっています。

変数を果物用に使うなら、果物だけを入れるべきです。いったん果物を入れた入れ物を、クッキー用には使えません。コード上では、1つの変数に異なる2つの型のデータを代入できないという形で表れます。

変数の可変性

名前が示すとおり、変数の値は、その型で許可される範囲を超えない限り変更できます。

Go言語の変数には、次のルールがあります。

  • 名前は、英字、数字、アンダースコアで構成する必要があります
  • 変数識別子は数字で始められません。
  • 識別子には予約語を使用できません。予約語を確認する
  • 変数名では大文字と小文字が区別されます。ただし、同じ名前の2つの変数を大文字と小文字の違いだけで区別することは推奨されません

通常の宣言方法

このステップでは、varキーワードを使って型付き変数を宣言する方法を学びます。

Go言語で通常の方法により変数を宣言するときは、varキーワードを使います。

宣言の形式はvar identifier typeです。これは次の意味を持ちます。

var variableName variableType.

Goでよく使われる変数の型には、どのようなものがあるでしょうか。

キーワード 説明
int 整数。小学校の算数でも学ぶ、最も一般的なデータ型です。
string 文字列。二重引用符で囲んだ文字の並びです。例:"hello,world"
bool 真偽値。trueまたはfalseの2つの値で、真または偽を表します。

このセクションでは変数の型そのものを扱うことが目的ではないため、最も一般的な3種類だけを紹介します。

その他の型については、後続のコースで説明します。

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

ここでは、aという名前の整数変数を宣言します。

var a int

覚え方として、次のように心の中で読んでみてください。

Define a variable named `a`, which is of type int.

多くの従来のプログラミング言語とは異なり、Goでは変数の型を変数名の後ろに書きます。

この宣言方法により、コードを左から右へ読みやすくなり、C言語のような複雑な読み方を避けられます。詳しくは公式ドキュメントを参照してください。

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

適切な変数名は、その変数が表す意味を明確に示します。

変数名を付けるときは、名前の分かりやすさを意識し、略語の使用を避けます。

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

キャメルケース命名規則では、大文字と小文字を組み合わせて変数を表します。最初の単語は小文字で始め、その後に続く各単語の先頭文字を大文字にします。

例:currentDate。最初の単語currentは小文字で始まり、2番目の単語Dateは大文字で始まっています。

このようにすると、現在の日付を表す変数だとすぐに分かります。

この命名規則は、重要で頻繁に使う変数に一般的に使用します。一時的な変数は、重複を引き起こさない限り、簡略化しても構いません。

まとめて宣言する方法

このステップでは、複数の変数をまとめて宣言する方法を学びます。

ここでは、3つの変数を宣言します。

var a int // Declare an integer variable named a
var b int // Declare an integer variable named b
var c int // Declare an integer variable named c

よく見ると、3つの変数a, b, cはすべてint型であることに気付くでしょう。

この場合、カンマで変数名をつなぐと、コードを短くできます。

var a, b, c int // Declare three variables a, b, c as integers

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

var a int    // Declare an integer variable named a
var b string // Declare a string variable named b
var c bool   // Declare a boolean variable named c

パッケージをインポートするときにも、似た状況がありました。異なる名前の複数のパッケージをまとめてインポートするために、同じような書き方を使えます。

var (
    a int
    b string
    c bool
)

パッケージのインポートに似たこの宣言方法は、一般的にグローバル変数の定義に使われます。

デフォルト初期化

このステップでは、Goが変数に割り当てるデフォルト値を確認します。

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() {
    // Declare and initialize
    var a int = 1
    var b string = "labex"
    var c bool = true

    // Print the variables
    fmt.Println("Before modification:")
    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)

    // Modify the variables
    a = 233
    b = "labex"
    c = false

    // Print the modified variables
    fmt.Println("After modification:")
    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)
}

コードを実行すると、次のように出力されます。

$ go run varExercise.go
Before modification:
1
labex
true
After modification:
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 // Type is inferred
    fmt.Println(a)
}

さらに、変数を定義するためにvarキーワードを使う必要もありません。

この変数の宣言と初期化の方法は、まとめて宣言する方法と組み合わせることもできます。

a, b, c := 0, 0, 0
// Declare variables a, b, c as integers and assign 0 to each variable.

異なる型の値で変数を初期化することもできます。

a, b, c := 0, "", true
// Declare variables a, b, c as integer, string, and boolean, respectively.

:=の左辺にある各変数には、右辺に対応する初期値が必要です。

短縮宣言は非常に便利ですが、:=は代入操作ではないことに注意してください。これはGo独自の変数宣言方法であり、関数内でローカル変数を宣言・初期化するときに使います。変数の型は、式に基づいて自動的に推論されます。

次のようなコードを書くことがあります。

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

コンパイラは、変数aが再宣言されているためエラーがあると知らせます。しかし、次のように書いた場合はどうでしょうか。

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

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

コンパイラはこの書き方を誤りとして指摘しませんが、予期しない出力になります。

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

関数の外側にある各文は、キーワード(varfuncなど)で始めなければなりません。

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

では、グローバル変数とローカル変数とは何でしょうか。

これは変数のライフタイムという概念に関係します。次のセクションで説明します。

変数スコープ

このステップでは、ローカル変数、グローバル変数、仮引数のスコープを比較します。

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

変数を宣言したのに使用しないと、コードをコンパイルできないことに気付いたでしょうか。

つまり、Goのコンパイル時には、各変数が使用されているか、すなわちスコープ内で使用されているかが確認されます。

変数は、宣言される位置によって簡単に次の3種類に分けられます。

  • 関数内で定義する変数。ローカル変数と呼びます。
  • 関数の外側で定義する変数。グローバル変数と呼びます。
  • 関数定義内で定義する変数。仮引数と呼びます。

ローカル変数

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

package main

import "fmt"

func main() { // Function body
    var a int = 1 // Local variable
    fmt.Println(a)
}

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

同時に、main関数内で変数を使用しなければ、コンパイラはエラーを表示します。

グローバル変数

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

package main

import "fmt"

var a int = 1 // Global variable
func main() { // Function body
    fmt.Println(a)
}

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

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

答え

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

仮引数の詳細については、後続の関数関連コースで説明します。

変数のライフタイム

このステップでは、変数にアクセスできる期間と、同じスコープ内で変数を再宣言できない理由を学びます。

飛ぶ鳥は跡をくらまし、謀る兎は死して猟犬は煮られる。—『史記』

変数が役割を終えたら、メモリ使用量を減らすために破棄する必要があります。

  • グローバル変数:グローバル変数のライフタイムは、プログラムの実行時間全体と一致します。プログラムが停止すると、グローバル変数はメモリから解放されます。
  • ローカル変数:変数にアクセスする方法がなくなると、その変数が使用していたメモリ領域は回収されます。

このような設計が、Goの高い性能と効率的なメモリ使用を支えています。

変数のライフタイム中は、その変数を再宣言できません。

varExercise.goに次のコードを書いてみてください。

package main

import "fmt"

func main() {
    var a int = 1 // Local variable, lifetime is limited to the entire main function
    var a int = 2 // Redefinition
    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
// Value is fixed, declaration is valid.
const Pi = 3.14159
// The calculated value is also fixed, the declaration is valid.
const c = 1 / Pi
// Fixed return value from built-in function is valid.
const le = len("labex")
// The return value of the user-defined function is not fixed, the declaration is invalid.
const le = getLen("labby")
// `a` is a variable value that is not fixed, the declaration is invalid.
const k = a

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

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

まとめ

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

  • 変数を宣言する方法
  • 変数を初期化する方法
  • 変数のライフタイムという概念
  • 定数の使用方法と、定数宣言が有効かどうか

この実験では、Goにおける変数の基本的な使い方を確認し、さまざまな状況で変数を宣言・使用する方法を示し、定数について紹介しました。