キーワード const は、C++ の変数(オブジェクト)が定数であることを示すものです。「オブジェクトの内容を変更できない」というアクセス制御を行うときに使うわけです。簡単ですが、ポインタと絡むと const を書く位置によって意味が変わるので、このあたり、まとめておきましょう。後半では、クラスと共に使用する場合についても解説します。
const を使う、最も簡単な場合とは、例えば、変更しない整数の定数を定義するような場合でしょう。次の例を見てください。変数 iL1、iL2 は、定数となり、値を変更できません。
int main() { // int 型変数と const int iL0 = 200; const int iL1 = 200; // @ int const iL2 = 200; // A、 @とAは、同じ意味になる。 { // 値を変更しようと試みる。 iL0 = 300; // OK iL1 = 300; // NG、左辺は const オブジェクト iL2 = 300; // NG、左辺は const オブジェクト } return 0; }
書き方としては、上記のように const と int を逆にしても同様の意味となります。
ここでいうべきことがあるとすれば、const で定義された変数は、定数なので、値を変更できませんが、最初の定義のときにだけは、値を設定することができる、ということでしょうか。まぁ、当たり前で、そうでないと、使えないわけですが、クラスの const なメンバ変数の場合には、どうやって初期化するかということは、自明というわけではありませんね。これについては、後述します。
もう一つ言うべきは、上記で定義した iL1、iL2 は、const でない iL0 と同様に、関数 main() の中だけで有効であるということです。
C 言語には const がないので、定数を使う場合 #define を使って、次のように記述するという方法ををよく使います。この場合、その有効範囲は、ファイル全体となるので、main() の外でも使える(使えてしまう)わけです。const による定数定義とは、スコープが異なるということですね。
#define ISIZE (3) int main() { ISIZE; // main() の中でアクセス可能 } int iSize = ISIZE; // main() の外でもアクセス可能
上記のように定数を #define で記述するという方法は、C++ でも有効です。特に複数のファイルにまたがるグローバルなスコープの定数を記述するには適していると思います。
ナオ : 定数は全て const を使って定義しなくっちゃならない、というわけでもないわけネ。
店主 : ンだズ。
ナオ : アンタは、どこのお人じゃ。ところで C 言語には const がないってホント? あたしの持ってる C 言語の本には const のことが書いてあるけど。
店主 : おう、そうか。若い人は知らないんだね。デニスリッチーが C 言語を作った当初は、C 言語には const がなかったのさ。ANSI の規格では、付け加わっているけどね。
ナオ : そう、分かったよ、お年寄り。
店主 : 年寄りじゃないっての。
次に、const キーワードにポインタが絡む場合について解説します。次の例を見てください。赤い const はポインタの指す変数が定数であることを指定しており、緑の const はポインタそのものが定数であることを指定しています。* の前後どちらに const を書くかで、意味が変わるわけですね。
int main() { // int* 型変数と const int iA = 123; // ポインタを初期化する。 const int* piK0 = &iA; int* const piK1 = &iA; const int* const piK2 = &iA; { // ポインタが指す変数を書き換えようと試みる。 *piK0 = 456; // NG、左辺は const オブジェクト *piK1 = 456; // OK *piK2 = 456; // NG、左辺は const オブジェクト } int iB = 789; { // ポインタそのものの値を書き換えようと試みる。 piK0 = &iB; // OK piK1 = &iB; // NG、左辺は const オブジェクト piK2 = &iB; // NG、左辺は const オブジェクト } return 0; }
クラスの const なメンバ変数を使うこともできます。次の例を見てください。
class CManiac { private: const int m_iSize; // 変数を定義し、 public: CManiac() : m_iSize(3) {} // 初期化子並びで初期化する。 };
通常の const な変数と異なるのは、初期化をコンストラクタの初期化子並びで行うということです。まっ、ただそれだけです。
int などの C++ に組み込まれている変数型は const になり得るわけですが、自前で定義するクラスのオブジェクトを const にしようとする場合には、クラスの定義をそれなりに注意して作成する必要があります。
まず、クラスオブジェクトを使う側のコードを見てください。
int main() { const CManiac maniac; // const な CManiac オブジェクトを定義する。 maniac.GetiSize(); // メンバ関数を使う。 return 0; }
CManiac というクラスには、何らかのサイズを返す GetiSize() という関数が定義されているようです。上記のコードが通るようなクラス CManiac の定義の仕方ということですが、const がないものとすれば、次のような定義が考えられます。
class CManiac { private: int m_iSize; // メンバ変数 public: CManiac() : m_iSize(0) {} int GetiSize() { return m_iSize; } // メンバ変数を読む関数 };
メンバ関数 GetiSize() では、メンバ変数を読んでいるだけで、変更はしていないので const なオブジェクトで使われても問題ないように思われるのですが、コンパイラは GetiSize() がメンバ変数を変更しているかもしれないと判断し、コンパイルエラーとなってしまいます。そこで、これを避けるには、次のように、メンバ関数の定義に const キーワードをつけて、コンパイラに「この関数はメンバ変数を変更しない」ということを知らせるようにします。
class CManiac { private: int m_iSize; // メンバ変数 public: CManiac() : m_iSize(0) {} int GetiSize() const { return m_iSize; } // const をつけたメンバ変数を読む関数 };
これで、めでたく、コンパイルが通り、期待通りの動作をするようになります。このように、クラスオブジェクトを const とするには、const なクラスオブジェクトで使用するメンバ関数の定義に const を付けるようにします。
クラスオブジェクトが const であるときとそうでないときに、同じ名前のメンバ関数が必要な場合、当然、それらの動作は異なることになりますが、メンバ関数をオーバーロードすることができます。次の例を見てください。
class CManiac { private: int m_iSize; // メンバ変数 public: CManiac() : m_iSize(0) {} int GetiSize() { return m_iSize; } // クラスオブジェクトが const で無い場合に使われるメンバ関数 int GetiSize() const { return m_iSize; } // クラスオブジェクトが const の場合に使われるメンバ関数 };
上記では、const の場合とそうでない場合で、GetiSize() 関数の動作は同じとしましたが、異なるように定義することも可能です。どちらの関数が使われるかは、オブジェクトが const であるかどうかによって自動的に判断されます。当然ですが、const をつけた方のメンバ関数の中では、メンバ変数を変更することはできません。逆に、const をつけない方のメンバ関数では、メンバ変数を変更することができます。プログラマの自由というわけですね。
ナオ : クラスオブジェクトが const な場合と、そうでない場合で、全く動作が違うようなメンバ関数を定義することもできそうネ。
店主 : もちろん、可能じゃよ。グワハッハー。
ナオ : キャラ変わってますけど。
店主 : ダハハ、ダハハ^2、ダハハ^3、...、while(i) { ダハハ^i++; }
ナオ : すみません。どっか、行っちゃいました。ちょっとこう言うとこ、あるのよネ。