以前、「const」というキーワードは定数を表すということを書きました。
変数宣言の前に「const」を付けると変数の書き換えが出来なくなり、バグ予防になると説明しました。
関数の引数にも「const」を使うことが出来ます。
引数に「const」を付けることによって、書き換える必要のない変数は書き換えられなくなり、バグの混入を防ぐことが出来ます。
また、構造体などの引数に使うことによって速度を上げることも出来る優れものです。
まずは、変数に対して使ってみましょう。
<sample program 142-01>
#include <stdio.h> void Show(const int value); int main(void) { int data = 12; Show(data); return 0; } void Show(const int value) { printf("value = %d\n", value); } |
<実行結果>
value = 12 続行するには何かキーを押してください・・・
特に目新しいことは何も無いように見えます。
しかし、引数に「const」が付いていますので、関数内で変数valueの書き換えは出来ません。
試しにやってみましょう。
<sample program 142-02>
#include <stdio.h> void Show(const int value); int main(void) { int data = 12; Show(data); return 0; } void Show(const int value) { value *= 2; /* 中身の書き換え */ printf("value = %d\n", value); } |
コンパイルの時点でエラーが出ているのが分かります。
error C3892: 'value': const である変数へは割り当てることはできません
このように書き換えたくない引数に「const」を付けることで無用なバグを防ぐことが出来るのですが、このサンプルでは利点が分かりにくいです。
分かりやすいのは配列の引数に対して「const」を付けることです。
前に書きましたが、配列はアドレス渡しが基本です。
中身を書き換えたく無い時もアドレスを渡すので、中身を書き換えられる恐れがあります。
<sample program 142-03>
#include <stdio.h> #define DATA 3 void Show(int data[DATA]); int main(void) { int data[DATA] = { 1, 4, 7 }; Show(data); return 0; } void Show(int data[DATA]) { int i; data[0] = 3; /* 中身の書き換え */ for (i = 0; i < DATA; i++) { printf("%2d", data[i]); } printf("\n"); } |
<実行結果>
3 4 7 続行するには何かキーを押してください・・・
この、
data[0] = 3; |
が、予期していない書き換えだったとします。
今のままでは防げませんが、「const」を付けることによって防ぐことが出来ます。
<sample program 142-04>
#include <stdio.h> #define DATA 3 void Show(const int data[DATA]); int main(void) { int data[DATA] = { 1, 4, 7 }; Show(data); return 0; } void Show(const int data[DATA]) { int i; data[0] = 3; /* 中身の書き換え */ for (i = 0; i < DATA; i++) { printf("%2d", data[i]); } printf("\n"); } |
書き換えのところでエラーが出ています。
'data': const である変数へは割り当てることはできません
「こんなの見たら分かるよね?」と思われるかも知れませんが、大きなソフト開発になるとプログラムの量も膨大で、しかも何人ものメンバーが関数ごとにプログラムを担当します。
誰がどのようなプログラムを組んでいるか全てを把握することは出来ません。
バグが発生すると、発生個所の絞り込みに膨大な時間を費やすことになるのです。
「const」と書くだけでバグの予防が出来るのであれば使わない手はありません。
書き換え不要な引数には必ず「const」を付けるようにしましょう。
もう1つ「const」の使い道を書きます。
構造体を関数に渡す時に「値渡し」か「アドレス渡し」を使うことが出来ます。
元のデータを書き換えられたく無い場合は「値渡し」を選択すれば良いのですが、「値渡し」を細かく説明すると以下のようになります。
まずはサンプルプログラムを書きます。
プレイヤーのステータスを表示する関数を作ります。
分かりやすくするために、元データの構造体名と引数の構造体名を変えます。
<sample program 142-05>
#include <stdio.h> typedef struct{ int hp; int mp; } Player; void Status(Player argPlayer); int main(void) { Player player = { 100, 50 }; Status(player); return 0; } void Status(Player argPlayer) { printf("Status\n"); printf("HP = %d\n", argPlayer.hp); printf("MP = %d\n", argPlayer.mp); } |
<実行結果>
Status HP = 100 MP = 50 続行するには何かキーを押してください・・・
これを図にします。
プログラムの実行順に合わせて書いていきます。
まずはmain関数でPlayer構造体が作られ、初期値が設定されます。
main関数 +-------+ player hp | 100 | +-------+ mp | 50 | +-------+
次にStatus関数が呼び出され、argPlayerが作られます。
Status関数 +-------+ argPlayer hp | | +-------+ mp | | +-------+
main関数の構造体からStatus関数の構造体へ中身がコピーされます。
main関数 Status関数 +-------+ +-------+ player hp | 100 | → hp | 100 | +-------+ +-------+ mp | 50 | → mp | 50 | +-------+ +-------+
この時、当然ながらhpとmpの両方がコピーされます。
このプログラムを↓のように改良します。
<sample program 142-06>
#include <stdio.h> typedef struct{ int hp; int mp; } Player; void Status(Player *pPlayer); int main(void) { Player player = { 100, 50 }; Status(&player); return 0; } void Status(Player *pPlayer) { printf("Status\n"); printf("HP = %d\n", pPlayer->hp); printf("MP = %d\n", pPlayer->mp); } |
<実行結果>
Status HP = 100 MP = 50 続行するには何かキーを押してください・・・
同じ結果になりましたが、細かい部分では全く違います。
また図にします。
まずはmain関数でPlayer構造体が作られ、初期値が設定されます。
main関数 +-------+ player hp | 100 | +-------+ mp | 50 | +-------+
次にStatus関数が呼び出され、ポインタ変数pPlayerが作られます。
Status関数 +----------+ pPlayer | | +----------+
main関数の構造体playerのアドレスがStatus関数に渡されます。
main関数 +----------+ &player → |player番地| +----------+
※アドレスは表示していないので分かりません。
さっきのプログラムであれば、hpとmpの2つのデータをコピーしていたのですが、今回のプログラムではアドレスを1つ渡しただけです。
当然、コピーするメンバ変数が多ければ多いほど時間がかかります。
しかし、アドレスを渡せばどんなにメンバ変数の数が多くてもアドレス1つを渡せば済みます。
このように「アドレス渡し」を使うことによって時間を短縮することが出来るのです。
ただし、問題が残ります。
アドレスを渡すということは、予期せず構造体の中身を書き換えられる可能性があると言うことです。
そこで「const」の出番です。
「const」を使うことによって書き換えが出来なくなりますので、安心してアドレスを渡すことが出来ます。
<sample program 142-07>
#include <stdio.h> typedef struct{ int hp; int mp; } Player; void Status(const Player *pPlayer); int main(void) { Player player = { 100, 50 }; Status(&player); return 0; } void Status(const Player *pPlayer) { printf("Status\n"); printf("HP = %d\n", pPlayer->hp); printf("MP = %d\n", pPlayer->mp); } |
<実行結果>
Status HP = 100 MP = 50 続行するには何かキーを押してください・・・
結果は何も変わっていませんが、バグ予防が出来ています。
試しに書き換えてみましょう。
<sample program 142-08>
#include <stdio.h> typedef struct{ int hp; int mp; } Player; void Status(const Player *pPlayer); int main(void) { Player player = { 100, 50 }; Status(&player); return 0; } void Status(const Player *pPlayer) { pPlayer->hp = 20; /* 中身の書き換え */ printf("Status\n"); printf("HP = %d\n", pPlayer->hp); printf("MP = %d\n", pPlayer->mp); } |
書き換えのところでエラーになっています。
error C3490: 'hp' は const オブジェクトを通じてアクセスされているため変更できません
さらに、もう一つ説明すべきことがあります。
「const」が付いているので元データの中身を書き換えることは出来ないのですが、ポインタ変数そのものの中身は書き換えることが出来てしまします。
<sample program 142-09>
#include <stdio.h> typedef struct{ int hp; int mp; } Player; void Status(const Player *pPlayer); int main(void) { Player player = { 100, 50 }; Status(&player); return 0; } void Status(const Player *pPlayer) { pPlayer = NULL; /* 中身の書き換え */ printf("Status\n"); printf("HP = %d\n", pPlayer->hp); printf("MP = %d\n", pPlayer->mp); } |
実行は出来ますが、途中で止まってしまいます・・・
ポインタ変数に入っているアドレスを変えてしまうことは非常に危険な行為です。
大規模なソフト開発では何が起こるかわかりませんので、もしかするとこのようなバグも発生するかも知れません。
これも防ぐことが出来ます。
引数の「*」の位置に注意してください。
<sample program 142-10>
#include <stdio.h> typedef struct{ int hp; int mp; } Player; void Status(const Player* const pPlayer); int main(void) { Player player = { 100, 50 }; Status(&player); return 0; } void Status(const Player* const pPlayer) { pPlayer = NULL; /* 中身の書き換え */ printf("Status\n"); printf("HP = %d\n", pPlayer->hp); printf("MP = %d\n", pPlayer->mp); } |
コンパイル時にエラーが表示されています。
error C3892: 'pPlayer': const である変数へは割り当てることはできません
このように「const」を付ける個所によって、何を書き換えられなくなるのかが変わりますので、上手く使うようにしてください。