変数とは何か
変数とは、値を保持する名前付きのメモリ領域です。C++ ではすべての変数が固定の型を持ち、それは宣言時に選ばれ、決して変わりません。これが C++ を静的型付けにしている理由です。コンパイラはプログラムが実行される前にすべての変数の型を把握しており、間違った種類の値を格納するコードのコンパイルを拒否します。
宣言には 3 つの部分があります:型、名前、そして(ほぼ常に)初期値です。
int age = 30; は「age という名前の int を作り、その中に 30 を入れる」と読みます。セミコロンは文を終えます。これはコメントのページがすべての文について示したのと同じです。isActive が 1 と表示された点に注目してください。bool はデフォルトで 1/0 として表示されます。これはデータ型のページが踏まえる内容です。
初期化 vs. 代入
この 2 つは似て見えますが別の操作であり、その違いはこのページで最も重要な考え方の 1 つです。
初期化は宣言の一部として変数に最初の値を与えます。代入はすでに存在する変数の値を変更します。
初期化せずに宣言し、後で代入することもできますが、注意が必要です。
これが動くのは、score を読む前に代入したからです。危険なのは先に読むことで、次で扱います。
未初期化変数の落とし穴
これは他の多くの言語には存在しない、C++ の典型的な罠です。宣言されたが値を一度も与えられていないローカル変数を読むことは未定義動作です。変数はそのメモリにたまたまあったバイトを保持しています。
int score; // 初期化されていない
std::cout << score; // 未定義動作 - ゴミを表示するか、「動く」か、クラッシュする
コンパイラは平然とこれをビルドします。0 を表示するかもしれませんし、32766 を表示するかもしれませんし、実行のたびに、あるいはマシンごとに異なる挙動を示すかもしれません。そのため、こうしたバグは追跡するのが非常に厄介です。防御策は 2 つあります。
- 常に宣言時に初期化する。
int score = 0;はコストがゼロで、問題全体を取り除きます。 - 警告を有効にする。
-Wall -Wextraでコンパイルすると、コンパイラは多くの未初期化の読み取りを、被害を受ける前に指摘してくれます。
すべてのローカル変数について最初の選択肢を選んでください。作成したその瞬間に、妥当な初期値を与えるのです。
初期化のスタイル:=、()、{}
C++ には初期値を書く方法がいくつかあります。ほとんどは同じことをしますが、波かっこ初期化には知っておく価値のある追加の安全機能が 1 つあります。
{} を使う理由は、縮小変換(narrowing)を拒否することです。これは、こっそりデータを失わせる代入のことです。= では小数値が黙って切り捨てられますが、{} ではコンパイラが止めてくれます。
int x = 3.9; // コンパイルされる - x は黙って 3 になる(.9 は捨てられる)
int y{3.9}; // コンパイルエラー - double から int への縮小は許されない
これをコンパイル時に捕まえられるのは、まさに望ましいことです。最近のよくある習慣は、この追加チェックのためにデフォルトで {} を使い、コピー初期化のほうが自然に読める場合だけ = に戻すというものです。
命名規則と慣習
C++ はいくつかの厳格な規則を課し、その上に皆が慣習を重ねます。規則は次の通りです。名前は文字・数字・_ を含められます。数字で始められません。予約語(int や return など)にはできません。そして大文字と小文字を区別します(age と Age は別の変数です)。アンダースコアに続けて大文字で始まる名前、あるいはアンダースコアが 2 つ連続する名前は避けてください。それらは実装のために予約されています。
ほとんどの C++ コードが従う慣習は次の通りです。
- 変数は
snake_caseかcamelCaseを使います。どちらかを選んで一貫させてください:item_countまたはitemCount。 - 名前は値を説明すべきです:
cではなくcount、xではなくuser_email。
明確な名前は飾りではありません。それは未来の自分がコードを読む手段です。total_price = item_count * price_per_item は、t = c * p には決して到達できないやり方で、それ自体が説明になっています。
変数のスコープ
変数は、それが宣言されたブロック - つまり { ... } - の中だけに存在し、閉じかっこで破棄されます。これがその**スコープ(scope)**です。ループや if ブロックの内側で宣言された変数は、その外側からは見えません。
i も square もループに属し、ループが終わると消えます。total は外側のブロックで宣言されているので生き残ります。内側のブロックは外側のブロックの名前を*シャドーイング(shadow)*することもできます。同じ名前の新しい変数が、内側のブロックが閉じるまで外側の変数を一時的に隠すのです。これはよくある混乱の元なので、ネストしたスコープをまたいで名前を再利用するのは避けてください。
実践的な教訓は、各変数をそれを必要とする最小のブロックで宣言し、そこで初期化することです。スコープが狭ければ、注意を奪い合う名前が減り、設定された場所から離れた所で値を読む機会も減ります。
次へ:データ型
このページのすべての変数は型から始まりました - int、double、string、bool。次のページでは C++ のデータ型を詳しく分解します:基本型とそのサイズ、符号付きと符号なしの整数、float と double、char、そして用途に合った正しい型の選び方です。
よくある質問
C++ で変数を宣言するにはどうすればよいですか?
型を書き、次に名前を書き、必要に応じて値を与えます:int age = 30;。型(int)は変数の生涯を通じて固定されます。名前(age)はそれを参照する手段です。値なしで宣言することもできます(int age;)が、ローカル変数の場合は値を代入するまでゴミを保持したままになるので、宣言の場所で必ず初期化してください。
C++ における初期化と代入の違いは何ですか?
初期化は宣言の一部として変数に最初の値を与えます:int x = 5; や int x{5};。代入はすでに存在する変数の値を変更します:x = 7;。この区別が重要なのは、宣言されたが一度も初期化されていないローカル変数を読むことが未定義動作だからです。
C++ で未初期化の変数を使うと何が起きますか?
未初期化のローカル変数の値を読むことは**未定義動作(undefined behavior)**です。変数はそのメモリにたまたまあったバイトを保持しているため、プログラムはランダムな数値を表示したり、運良く動いたり、クラッシュしたりします。コンパイラは止めてくれません(多くは -Wall で警告しますが)。そのため、解決策はローカル変数を宣言するときに必ず値を与えることです。