プログラミングにおいて、変数は非常に重要な概念です。変数を理解することは、プログラミングの基礎を理解する上で欠かせないステップです。
変数は、プログラム中で使用される値やデータを格納するための仕組みです。変数には、名前と値があります。名前は変数を識別するためのものであり、値はその変数に格納されたデータを表します。
変数を使うことで、同じ値を何度も使用する必要がある場合に、簡単にその値を参照できます。また、変数に新しい値を代入することで、プログラムの動作を変更することができます。
本書では、変数の基本的な概念から始めて、より高度な使い方や実践的な応用方法までを解説していきます。変数を理解することで、プログラミングにおける基本的な制御構造や関数の理解にも役立つことでしょう。
__TOC__
var x = { prop: 1 };
var y = x;
x.prop = 2;
console.log(y.prop);
実行結果は2になります。これは、xがオブジェクトを参照しているため、yも同じオブジェクトを参照しているためです。そのため、xのプロパティを変更すると、yも同じ変更を反映します。
プログラミング言語には、静的型付け言語と動的型付け言語の2種類があります。
静的型付け言語では、変数に対してその型を明示的に宣言する必要があります。変数が宣言されると、その変数はその型に制限され、別の型の値を代入することはできません。静的型付け言語は、コンパイル時に型チェックを行い、実行時に予期しない型のエラーを防ぐことができます。
例えば、JavaやC++は静的型付け言語です。以下の例では、変数xがint型で宣言されているため、xには整数値しか代入できません。
int x = 10;
x = "Hello World"; // エラーが発生する
静的型付け言語の利点は、コンパイル時にエラーを発見できることや、型に対する厳密な制限によって安全性を高めることができることです。ただし、変数の型を明示的に宣言する必要があるため、コードの記述量が多くなる傾向があります。
静的型付け言語では、変数が型を持ちます。
動的型付け言語では、変数が宣言されるときにその型を明示的に宣言する必要はありません。変数には、代入される値に応じて適切な型が自動的に割り当てられます。つまり、変数が持つ値に応じて型が動的に変化するため、実行時に型チェックが行われます。
例えば、PythonやRubyは動的型付け言語です。以下の例では、変数xが最初に整数値として宣言されていますが、文字列を代入することができます。
x = 10
x = "Hello World"  # エラーは発生しない
動的型付け言語の利点は、コードの記述量が少なくなることや、柔軟性が高いことです。一方、実行時に型チェックを行うため、予期しない型のエラーが発生する可能性があることや、静的型付け言語に比べて実行速度が遅くなることがあります。
動的型付け言語では、変数に型はなく、変数が保持している値に型があります。
関数型言語では、変数には常に値が割り当てられ、再代入はできません。また、変数の型は宣言されたときに定まり、変更することはできません。
変数の値は、不変であるため、関数型言語では、値を引数として受け取り、新しい値を返す関数を重視します。つまり、関数は状態を変更するのではなく、値を受け取り、変換して返すものとして扱われます。
例えば、HaskellやOCamlは関数型言語です。以下の例では、変数xには数値が割り当てられていますが、再代入することはできません。
x = 10
x = 20 -- エラーが発生する
関数型言語の利点は、変数の値が不変であるため、プログラムの正確性が高くなり、スレッドセーフであることや、関数の再利用性が高くなることです。一方、状態を変更する必要がある場合や、変数の値を更新する必要がある場合には不向きであることがあります。
スコープとは、変数が参照可能な範囲を指します。プログラムの中で変数が定義される場合、その変数はその定義された場所によって、アクセス可能な範囲が異なります。以下に、主なスコープの種類を紹介します。
プログラムの全体でアクセス可能なスコープです。通常、グローバル変数やグローバル関数が定義されます。
JavaScript:
var x = 10; // グローバル変数
function func() {
  x = 20;
  console.log(x);
}
func(); // 出力: 20
console.log(x); // 出力: 20
関数内で定義された変数が属するスコープです。関数内で定義された変数は、関数の内部でのみアクセス可能です。
JavaScript:
function func() {
  var x = 10; // 関数スコープ内で定義された変数
  console.log(x);
}
func(); // 出力: 10
console.log(x); // エラーが発生する
ブロック内で定義された変数が属するスコープです。ブロックスコープは、通常、if文やfor文、while文などの制御構造の内部で使用されます。
JavaScript:
if (true) {
  let x = 10; // ブロックスコープ内で定義された変数
  console.log(x);
}
console.log(x); // エラーが発生する
レキシカルスコープとは、変数が定義された場所によって、変数のスコープが決定される仕組みです。レキシカルスコープを採用している言語では、変数のスコープは、その変数が定義された場所から始まり、その変数を含む親スコープの範囲まで及びます。
クロージャとは、関数とその関数が定義されたスコープとの結びつきを持つオブジェクトのことです。クロージャは、外部からアクセスできない変数を保持し、その変数にアクセスする関数を提供します。これにより、関数内で定義された変数を、その関数が呼び出されるたびに保持できます。
以下に、レキシカルスコープとクロージャの例を示します。
 
JavaScript:
function outerFunc() {
  var x = 10; // 外側の関数内で定義された変数
  function innerFunc() {
    console.log(x); // 外側の関数の変数にアクセスする
  }
  return innerFunc; // innerFunc関数を返す
}
var func = outerFunc(); // outerFunc関数を実行して、innerFunc関数を取得する
func(); // outerFunc関数が終了した後でも、innerFunc関数がx変数にアクセスできる
この例では、outerFunc関数内で定義された変数xにアクセスするために、innerFunc関数がクロージャを使用しています。outerFunc関数が終了した後でも、innerFunc関数がx変数にアクセスできるため、クロージャは、関数が終了した後に変数の値を保持するための有用な手段です。
ループ変数は、プログラムのループ（反復処理）中に使用される変数で、反復ごとに値が更新されます。ループ変数は、通常、反復回数を制御するために使用されます。
以下に、いくつかの言語でのループ変数の例を示します。
for (int i = 0; i < 10; i++) {
  printf("%d ", i);
}
この例では、iがループ変数です。iは0から始まり、iが10未満の場合にループが続き、ループごとにiの値が1ずつ増加します。printf関数は、iの値を表示します。
for (int i = 0; i < 10; i++) {
  System.out.print(i + " ");
}
 
この例では、C言語と同様にiがループ変数です。System.out.printメソッドは、iの値を表示します。
for (let i = 0; i < 10; i++) {
  console.log(i);
}
この例では、iがループ変数であり、letキーワードで宣言されています。console.logメソッドは、iの値を表示します。
for i in 0..9
  puts i
end
この例では、iがループ変数であり、0..9範囲オブジェクトを使用して、iの値を0から9まで反復します。putsメソッドは、iの値を表示します。
型推論は、コンパイラがコードを解析して、変数の型を自動的に推測する機能です。
変数の宣言時に型を指定する必要がなく、簡潔なコードを書くことができます。
以下に、いくつかの言語での型推論の例を示します。
C++では、autoキーワードを使用して型推論を行うことができます。autoを使用することで、コンパイラが変数の初期値から型を推論します。
 
auto:
auto x = 10; // xはint型に推論される
auto y = 3.14; // yはdouble型に推論される
また、decltypeキーワードを使用して、式から型を推論することもできます。
 
decltype:
int x = 10;
decltype(x) y = x; // yはint型に推論される
Java 10以降では、varキーワードを使用して型推論を行うことができます。
 
var x = 10; // xはint型に推論される
var y = 3.14; // yはdouble型に推論される
Rustでは、特別なキーワードはなく、「: 型」を省略すると型推論が行われます。
 
let x = 10; // xはi32型に推論される
let y = 3.14; // yはf64型に推論される
Goには2つの型推論方法があります。
Goでは、varキーワードを使って変数を宣言するときに、「: 型」を省略すると型推論が行われます。例えば、以下のように書くことができます。
var x = 10      // xはint型と推論される
var y = "hello" // yはstring型と推論される
Goでは、:=構文を使って変数を宣言し、コンパイラに型を推論させることもできます。:=構文はvarキーワードを省略することができます。例えば、以下のように書くことができます。
x := 10        // xはint型と推論される
y := "hello"   // yはstring型と推論される
このように、Goでは簡単に型推論を行うことができます。
Juliaでは、変数の宣言時に型を指定しなくても、初期値から型を推論することができます。
x = 10 # xはInt64型に推論される
y = 3.14 # yはFloat64型に推論される
C#における型推論は、varキーワードを使用します。このキーワードは、変数を宣言するときに使用される型名の代わりに、コンパイラが変数の型を自動的に推論します。変数に代入された値の型に基づいて、コンパイラは変数の型を決定します。例えば、以下のコードでは、numにint型が自動的に割り当てられます。
var num = 10;
Scalaにおける型推論は、コンパイラが自動的に変数の型を決定することで実現されます。変数を宣言するときに型を指定する必要がなく、コンパイラが式の型を解析して変数の型を決定します。例えば、以下のコードでは、messageにString型が自動的に割り当てられます。
val message = "Hello world!"
Kotlinにおける型推論は、変数を宣言するときにvarまたはvalキーワードを使用し、型名を省略することで実現されます。コンパイラは式から変数の型を推論し、必要に応じて暗黙的な型変換を実行します。例えば、以下のコードでは、numberにInt型が自動的に割り当てられます。
var number = 42
型ヒントとは、プログラム中の変数や関数の引数などに明示的に型情報を付与することです。動的型付け言語で使用されます。
以下にPythonとTypeScriptの例を示します。
age: int = 25
name: str = "John"
def add(a: int, b: int) -> int:
    return a + b
上記の例では、変数ageにint型、変数nameにstr型をアノテーションしています。また、add関数の引数aとbにint型を、戻り値にint型をアノテーションしています。
// 変数の型ヒント
let age: number = 25;
let name: string = "John";
// 関数の引数と戻り値の型ヒント
function add(a: number, b: number): number {
  return a + b;
}
上記の例では、変数ageにnumber型、変数nameにstring型をアノテーションしています。また、add関数の引数aとbにnumber型を、戻り値にnumber型をアノテーションしています。
Clojure は動的型付け言語であり、静的な型チェックは提供していません。しかし、Clojure 1.9 以降では、型ヒントを使用して変数に型情報を提供することができます。
型ヒントを使用するには、変数宣言に ^ を付けて型アノテーションを記述します。例えば、整数型の変数 x を定義する場合、以下のように書くことができます。
 
(def ^Integer x 10)
同様に、文字列型の変数 s を定義する場合、以下のように書くことができます。
 
(def ^String s "hello")
型ヒントは、Clojure の REPL などで評価される式に対しても使用できます。例えば、以下のように数値を足し合わせる関数を定義する場合、引数の型を指定することができます。
 
(defn ^Integer add [^Integer x ^Integer y]
  (+ x y))
このように、Clojure では型ヒントを使用して、コンパイラに型情報を提供することができます。ただし、実行時には引数の型が変更される可能性があるため、注意が必要です。