定数 | Programming Place Plus C言語編 第10章

トップページC言語編

このページの概要

以下は目次です。


定数

定数 (constant) とは、変数と異なり、ソースコード上に記述した値のまま変化することがないもののことです。これまでにも、0 とか 10 のような整数定数 (integer constant) が登場しています。-1 のような符号付きのものは、符号を除いた部分が定数で、符号は演算子です(第2章)。

また、1.1 のような小数点を含むものを、浮動小数点定数 (floating constant) と呼びます。

'a' のように '' で囲まれた文字は、文字定数 (character constant) と呼び、"Hello" のように "" で囲まれた文字列は、文字列リテラル (string literal) と呼びました。後者は規格上、定数ではなくリテラル (literal) という用語が当てられていますが、言わんとすることは同じと考えて構いません。

【上級】定数にはこのほかに、列挙定数があります(第50章)。

定数にも型があって、値や、記述のしかたによって決定されます。今のところ、記述のしかたによる違いについては触れないことにしますが、それを気にしなければ、整数定数と文字定数は int型、浮動小数点定数は double型、文字列定数は char の配列型(要素数は実際の文字数と同じ)になります。文字定数が char型でないことに注意してください。

【上級】たとえば、10L のような記述のしかたによって、long int型以上の大きさの整数型になります。

定数は使えるが、変数は使えないという場面がいくつかあります。現時点で代表的なのは、配列を宣言するときに指定する要素数です。

int main(void)
{
    int size = 10;
    char s[size];  // エラー
    char s[10];    // OK
}

定数式

コンパイル時に結果を確定できるような式は、定数式 (const expression) と呼ばれます。つまりは、定数だけで構成されている式ということで、3 + 5 だとか 5.5 * 2 といったものです。

定数式の中にある演算はコンパイル時に評価され、定数式全体は結果の値に置き換わります。たとえば、int a = 3 + 5; と書いたとすると、3 + 5 の部分はコンパイル時にすでに計算されており、int a = 8; に置き換わっているということです。そのため、定数式中の計算が複雑であっても、プログラムの実行速度は落ちません(代わりにコンパイルの時間が長くなっているはずです)。

このように、定数式は定数に置き換わるので、定数を使える箇所ではいつでも定数式が使えます。

int main(void)
{
    char s[10 * 2];    // OK
}

この例の場合、定数式を評価した結果が整数定数にならなければなりません。

定数式は、コンパイル時に結果を得る必要があるため、関数呼び出しを含むことができません1

int get_size();

int main(void)
{
    char s[get_size() + 1];    // エラー。定数式ではない
}

【上級】そのほか、インクリメント・デクリメントや、コンマ演算子を含むことができません。1

【上級】例外として、sizeof演算子に与える定数式の中では、これらが含まれていても構いません1。オペランドが可変長配列(第25章)である場合を除いて、sizeof演算子に与えた式の評価は行われないためです2

【C++プログラマー】C言語には constexpr関数のようなものはありません。

記号定数

定数をそのまま記述すると、あとからソースコードを読み返したときなどに、意味を理解しづらくなることがあります。単に 100 のように書かれていると、どんな意味で 100 なのかを把握しづらいためです。このような意味を読み取りづらい謎の値のことをマジックナンバー (magic number) と呼びます。

そこで、定数に名前を付ける方法があります。名前が付けられた定数のことを、記号定数 (symbolic constant) と呼びます。

いくつか使える手段はありますが、ここではオブジェクト形式マクロ (object-like macro) を紹介します。

#define マクロ名 定数

「マクロ名」のところに名前を記述し、「定数」のところに任意の定数を記述します。こうすることで、以降、「マクロ名」を定数の代わりとして使用できます。なお、強制されているわけではありませんが、一般的に「マクロ名」は大文字で表記します。

この記述は少し特殊で、#define「マクロ名」「定数」のそれぞれのあいだで改行してはならず、1行で書きます。

この記述よりも後続の位置であればどこからでも使用できます。main関数の定義よりも上で書くことが多いです。

#include <stdio.h>

#define PROGRAM_VERSION  1
#define PROGRAM_TITLE    "Hello, World Program"

int main(void)
{
    printf("%s ver.%d\n", PROGRAM_TITLE, PROGRAM_VERSION);
    printf("\n");

    puts("Hello, World");
}

実行結果:

Hello, World Program ver.1

Hello, World

こうして記号定数にすれば理解しやすくなります。また、同じ意味の同じ定数をソースコード上に多数使っている場合に、あとから値を変更する作業も簡単で間違いにくくなります。#define の「定数」のところだけを変更すれば済むためです。

マジックナンバーを避けようとするあまり、次のようなマクロを書こうとするかもしれません。

#define FIVE 5

定数5 だから、FIVE という名前を付けたということですが、これは無意味であるばかりか、むしろ悪化させています。問題が2つあります。

まず、「意味を明確に」はしていません。「5」が数字の 5 であることは十分に明確ですから、「FIVE」にしたらより明確になるということはありません。

また、「あとから値を変更する作業をやりやすくする」こともありません。仮に「FIVE」の置換結果が「7」であるべきだと後で気づいたとしたらどうでしょう? #define FIVE 7 に直すべきなのでしょうか? 「FIVE」という名前なのに、置換結果が「7」なのは明らかにおかしく、かえって混乱させるだけです。

const型修飾子

もう1つ、定数とよく似た機能があって、変数の値を書き換えられなくする方法があります。こちらはあくまでも変数であって、定数ではないのですが、初期値のまま値を固定できるため、ソースコードを分かりやすくする効果があります。

次のように、変数を宣言するときに const というキーワードを付け足します。

const 型名 変数名 = 初期値;

const は「型名」を修飾しています。つまり、「型名」の指定にさらなる意味を加えていることになります。もし「型名」が int であれば、この変数の型は const修飾された int型(よく単純化して const int型)と呼ばれます。

このような使い方をするキーワードはほかにもあって、まとめて型修飾子 (type qualifier) と呼びます。const であれば const型修飾子 (const type qualifier) と呼ばれます。const型修飾子は、その型を持った変数の値を変更する行為を禁じます。

#include <stdio.h>

int main(void)
{
    const int x = 10;
    int y = 20;

    y = x;   // OK
    x = y;   // エラー
    x = 10;  // エラー
    x += 3;  // エラー
}

変数x は const修飾されているので、値を変更する行為は禁じられています。こうした値の変更を発見すると、普通、コンパイルエラーとして報告されます。

また、fgets関数や sscanf関数からの結果を受け取る変数も、const修飾されていてはいけません。ただ、こちらについてはコンパイルエラーとはならず、警告で済まされる可能性があります。

#include <stdio.h>

int main(void)
{
    const char s[40];
    fgets(s, sizeof(s), stdin);  // 危険

    const int x = 10;
    sscanf(s, "%d", &x);         // 危険
}

変数s は const修飾されているので、値を変更してはいけないはずですが、fgets関数が受け取った文字列をここに入れようとしています。同様に、sscanf関数は const修飾された変数x に値を入れようとしています。たとえコンパイルエラーにならなくても、const修飾されている変数の値を変更する行為は避けなければいけません。

const修飾された変数はあくまで変数なので、定数でなければならない箇所では使えません。次のコードはコンパイルエラーになります。

int main(void)
{
    const int size = 10;
    char s[size];    // エラー。size は定数ではない
}

【C++プログラマー】C++ の const は(実行時の)定数の意味になるため、このコードはコンパイルできます。

const型修飾子は、ソースコードを分かりやすくし、バグに気付きやすくするための助けになります。初期値から値を変更するべきでない変数には、いつも const型修飾子を付ける習慣は悪くありません。


練習問題

問題① 次の中から、定数式でないものをすべて選んでください。num は int型の変数、f は戻り値が int型の関数、c は const修飾された int型の変数、X は #define X 10 と定義された記号定数とします。


解答ページはこちら

参考リンク


更新履歴



前の章へ (第9章 関数)

次の章へ (第11章 処理の流れを分岐させる)

C言語編のトップページへ

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る