変数とは、プログラムが読んだり変えたりできる値を保持する、名前の付いたメモリの一片です。Cではどんな種類の値を保持するかを常に前もって述べ、その選択はその変数が生きている間ずっと変わりません。
変数を宣言する
形は型、名前、セミコロンです。
int age;
double price;
char grade;
各行はそのデータ型に十分なメモリを確保し、そこに名前を結びつけます。まだ何も格納されていません - 領域は存在しますが、その中身は以前に走った何かが置いていったものです。
同じ瞬間に値を与えることもでき、それを初期化と呼びます。
書式指定子に注目してください。int には %d、double には %f、char には %c です。printf は渡された値を調べません - あなたの書式文字列を信用するので、不一致はエラーではなく意味不明な出力を生みます。
宣言のあとの代入
変数がいったん存在すれば、= で変更できます。
Cにおける = は等価ではなく代入です。score = score + 10 は「score に入っているものを取り、10 を足し、結果を score に戻す」と読めます。比較の演算子は == で、この2つを取り違えるのはあまりによくあるバグなので、真偽値のページに独立した節があるほどです。
省略記法の score += 10 は同じことをより少ない文字で行います。複合代入の演算子がすべての算術を網羅しています。
未初期化変数の罠
これがC言語で単独最多の初心者のバグであり、しかもエラーを出しません。
実行してみてください。正しい答えである 15 が見えるかもしれません。巨大なでたらめな数が見えるかもしれません。2回目の実行や別のマシンでは違うものが見えるかもしれません。そのどれもが「正しい」振る舞いです。未初期化のローカル変数を読むことは未定義動作であり、C標準は何が起きるかについて何の要求も課していないからです。
多くの言語と違い、Cはローカル変数をゼロにしません。ローカル変数はスタックの一片であり、スタックは以前の関数呼び出しの残り物でいっぱいです。その残り物がたまたまゼロであることもあり、それこそがこのバグを危険にしています。テストでは動き、本番で失敗することがよくあるのです。
対策は1文字です。
習慣にしましょう。すべての変数を宣言の時点で初期化すること。 まだ本当に意味のある値がないなら、ゴミよりは 0 や NULL のほうがましです。そして -Wall を付けてコンパイルすれば、たいていの場合を捕まえられます。
warning: 'total' is used uninitialized in this function
グローバル変数と static 変数は例外で、これらは自動的にゼロ初期化されます。それに頼るのは構いませんが、それでも = 0 と書けば意図が説明されます。
まとめて宣言する
同じ型の変数は、初期化子ありでもなしでも1つの宣言を共有できます。
これは合法で、x, y, z のように密接に関連する値ではよく使われます。しかしここに古典的な読み違いが潜んでいます。
int a = 0, b = 0, c = 0; /* 3つとも 0 */
int d, e, f = 0; /* 0 なのは f だけ - d と e はゴミ */
初期化子は行にではなく、1つの名前に結びつきます。値の意味が異なるなら、それぞれに自分の行を与えましょう - 読みやすくなりますし、差分も見やすくなります。
ポインタを1行で宣言すると、同じ罠がより鋭い形で現れます。
int *p, q; /* p は int へのポインタ。q はただの int */
* は型ではなく名前に結びつきます。ポインタは1行に1つ宣言しましょう。
命名の規則と慣習
コンパイラの規則です。
- 英字、数字、アンダースコアのみ。
- 数字で始められない:
total2は構いませんが2totalは不可。 - Cのキーワードにはできない:
int、return、for、doubleという名前の変数は不可。 - 大文字小文字を区別する:
count、Count、COUNTは3つの別々の変数です。 - アンダースコアで始まる名前や、二重アンダースコアを含む名前は処理系のために予約されています。作らないこと。
int itemCount; /* 合法 */
int item_count; /* 合法 - 通常のCの流儀 */
int _count; /* 合法だが予約されている - 避けること */
int item-count; /* エラー: '-' は減算の演算子 */
int 3rd_place; /* エラー: 数字で始まっている */
int float; /* エラー: キーワード */
ほとんどのCコードが従う慣習です。変数と関数には snake_case、マクロの定数には UPPER_CASE、そして名前は型が何かではなく値が何を意味するかを述べるもの。t より elapsed_ms、n2 より customer_count が優れています。
スコープがごく狭いところでは短い名前でも構いません - ループカウンタの i は誰にでも通じますし、for (int loop_iteration_index = 0; ...) など誰も望みません。
変数はどこに置かれるか
変数をどこで宣言するかが、誰がそれを見られるか、どれだけ長く続くかを決めます。これがスコープであり、短く言えばこうです。
変数は、その宣言からそれを含むブロックの閉じ波かっこまで見えています。グローバル変数はプログラム全体を通じて生き、どこからでも見えます。便利に聞こえますが、だからこそ問題を起こします。どの関数からも変更できるので、誰がやったのかを突き止めるにはファイル全体を探すことになるのです。ローカル変数を選び、値は引数として関数へ渡しましょう。
どこででも宣言する(C99 以降)
元のC89では、すべての宣言がブロック内のどの文よりも前に来なければなりませんでした。
int main(void) {
int i, sum = 0; /* 宣言はすべて先に */
printf("Starting\n");
for (i = 0; i < 10; i++) { sum += i; }
return 0;
}
C99 以降は、for のヘッダーの中も含め、その値が最初に必要になる場所で宣言できます。
こちらのほうが良い流儀です。宣言が最初の使用のすぐ隣にあり、i が関数の残りへ漏れ出すこともありません。-std=c17(あるいは C99 以降のどの設定でも)でコンパイルすれば、どこでも動きます。
よくある質問
C言語で変数はどう宣言しますか?
型、名前、セミコロンの順に書きます:int age;。同時に値を与えることもでき - int age = 30; - これを初期化と呼びます。ほとんどの場合、これこそ望ましいやり方です。
変数の宣言と初期化の違いは何ですか?
宣言は領域を確保して名前を付けます(int count;)。初期化は作られた瞬間に値を入れます(int count = 0;)。宣言しただけで初期化していないローカル変数には、そのアドレスにもともとあったバイト列が入っており、それを読むのは未定義動作です。
C言語の変数名の規則は何ですか?
使えるのは英字、数字、アンダースコアだけで、数字で始められず、int や for のようなキーワードにはできず、大文字小文字を区別するので total と Total は別の変数です。アンダースコアで始まる名前は予約されているので避けましょう。
C言語の関数の中ならどこでも変数を宣言できますか?
C99 以降ならできます - 最初に必要になる場所でそれぞれ宣言しましょう。元のC89標準ではすべての宣言がブロックの先頭に来なければならず、だから古いコードでは実際の処理の前に宣言の壁が並んでいるのです。