複雑なプログラムを簡単に設計・実装できるようになるために, 「構造化プログラミング」の考え方について理解しよう.
教科書 pp.67-73 を参考にしながら,作業を進めて行こう.
一般に,次の3種類の制御構造 だけを組み合わせれば, どんなに複雑な手続きでも記述できる:
構造化プログラミングと呼ばれるプログラム作成の流儀では, これら3つの制御構造だけを利用してソースコードを記述する. 構造化されたソースコードでは,全体像がわかり易くなるので, この授業でも,基本的には,この方法論を採用する.
なお,他の制御構造として,跳躍(ジャンプ,goto)もある. 処理の全体像がわかりづらくなってしまう原因になり得るので, 本当に必要な場合以外,これを使ってはならない.
制御構造を直観的に理解するための道具として, Fig.1 のような NS チャート(Nassi-Schneiderman chart)を紹介しておく.
Fig.1 は,次のようなソースコードと対応している:
// 連接 処理A 処理B 処理C 処理D |
// 選択 if ( 条件 ) { 処理A 処理B 処理C } else { 処理A' 処理B' 処理C' } |
// 反復 for (i = 0; i < n; i++) { 処理A 処理B 処理C } |
以下,今回は「選択構造」について勉強しよう.
選択構造での分岐条件や反復構造での反復条件を指定するために, 次のような条件式(または関係式)を利用する:
各条件式について,括弧 ( ) で(しつこく)囲んでおくことを推奨する. たとえば,次のように:
if (((x == 0) && (y == 0)) || (z == 0)) ...
これは,演算の優先順位を明確化するための措置.
詳しくは,教科書 pp.51-52 を参照.
真/偽,Yes/No,1/0,ON/OFF のような二者択一で処理を場合分けするには, if 文を利用する. 一般形は次の通り:
if ( 条件式1 ) { 条件式1が成立した場合の処理 } else if ( 条件式2 ) { その他の場合で条件式2が成立した場合の処理 } else if ( 条件式3 ) { ... } else { その他の場合の処理 }
また,このコードに対応する NS チャートは,Fig.2 のようになる.
コードがちょっと複雑に見えるが, 次のような省略も可能なので安心してよい:
もっとも単純な例:
if ( 条件式 ) 文;
if ( 条件式 ) { 文1; } else { 文2; }
悪い例:
if ( 条件式 ) 文1; 文2;
これは文法的にはまちがっていないが, 動作的には意図した通りなのか? 混乱を避けるため,次のどちらかの形式で記述すること!!
良い例:if ( 条件式 ) 文1; 文2;
if ( 条件式 ) { 文1; 文2; }
では,if 文の簡単な使用例として,List 1 を試してみよう.
// 1 から 20 まで数えます. // ただし,3 の倍数のときだけ関西弁になります. main() { int i; for (i = 1; i <= 20; i++) { printf("%d", i); // 数を表示 if (i%3 == 0) { // 3の倍数のとき printf("でっせ.\n"); // 関西弁 } else { // その他のとき printf("です.\n"); // 標準語 } } }
なお,記号「%」は剰余(割り算の余り)の演算子である. もし,i/n の剰余が 0 の場合,i は n の倍数ということになる.
次に,問題を少しだけ複雑にする. List 1 を改造し,List 2a を作ってみよう.
// 1 から 20 まで数えます. // ただし,3 の倍数のときだけ関西弁になります. // しかも 4 の倍数のときには自信がなくなります. ... if (i%3 ==0) { if (i%4 == 0) { // 4の倍数のとき printf("でっしゃろか?\n"); // 関西弁で自信なく } else { // その他のとき printf("でっせ.\n"); // 関西弁 } } else { if (i%4 == 0) { // 4の倍数のとき printf("だったかな?\n"); // 標準語で自信なく } else { // その他のとき printf("です.\n"); // 標準語 } } ...
これでは,if や else が多すぎる気がしないか? 同じ if (i%4 == 0) を 2 回も書いているし... というわけで,List 2b のように書き換えてみよう.
...
if ((i%3 ==0) && (i%4 == 0)) { // if (i%12 == 0) でも同じ
printf("でっしゃろか?\n");
} else if (i%3 == 0) {
printf("でっせ.\n");
} else if (i%4 == 0) {
printf("だったかな?\n");
} else {
printf("です.\n");
}
...
動作は変わらないが, コードは短くなった気がする. しかし,まだ場合分けが複雑な気がしないか?
if 文には, 同値な複数通りの書き方があり得る. もっとも良い書き方を選りすぐろう.
特に,else や else if が多く現れると, 全体の見通しが悪くなりがちだ. 使わずに済むときには,else を使わないこと.
また,条件式を充分に吟味(ぎんみ)しよう. たとえば,考えていた条件とは逆の条件にした方がわかり易くなるかもしれない. つまり,「〜である場合,〜する」だけではなく, その逆に,「〜ではない場合,〜する」についても考えてみよう. そして,ソースコード的にわかり易くなる方を採用しよう.
たとえば:
if (x != 0) {
...
} else {
return; // x≠0 なら終了
}
ではなく,次のように書くべき:
if (x == 0) return; // x=0 なら終了
...
条件を逆にすることで,コードを短くできた.
さらに,論理演算子に頼りすぎるのも悪い兆候だ. たとえば:
if ((条件A) && (条件B)) { 処理A 処理B } else if (条件A) { 処理A } else if (条件B) { 処理B }
ではなく,次のようにすべき:
if (条件A) { 処理A } if (条件B) { 処理B }
要するに,プログラミング作業に入る前に, 問題を充分に分析しておこう.
複雑な処理をそのままコーディングすることは誰にでもできる. 複雑なものを単純化するのが「プロの技」だ. 「動けば良い」のは素人レベル.
選択肢がたくさんあるような多重分岐の場合には, 次のように,switch 文を使うと便利だ:
switch ( 式 ) { case 値1 : 式が値1に等しい場合の処理 break; case 値2 : 式が値2に等しい場合の処理 break; ... default : その他の場合の処理 break; }
Fig.3 は,switch 文の NS チャートである.
なお,switch 文の利用に当たっては, 次のような注意が必要:
詳しく,教科書 pp.71-73 を参照.
List 2a および 2b の問題について, List 3 のように,コードをさらに書き換えてみよう.
... int kansai; // 関西弁フラグ int jishin; // 自信フラグ ... kansai = (i%3 == 0) ? 1 : 0; jishin = (i%4 == 0) ? 0 : 1; switch (kansai*0x10 + jishin) { case 0x00: printf("だったかな?\n"); break; case 0x01: printf("です.\n"); break; case 0x10: printf("でっしゃろか?\n"); break; case 0x11: printf("でっせ.\n"); break; default: break; } ...
コードは長くなってしまったが, 場合分けの処理が単純化されている. 動作は変わっていない.
なお,フラグ(flag;旗)とは, 0 か 1 かどちらかの値をとる変数のことである. 条件の判定結果を保存しておくために利用する. また,0xXX は, 数値 XX が 16 進数表記であることを示している. (たとえば,10進数の 16 を 16 進数で表わすと 0x10 となる.)
以上,同じ問題に対して, 複数の異なるコーディング方法があることが理解できただろうか? さまざまな方法を考え, 状況に応じて最適な方法を見分け, 使い分けて行こう.
動作例:
$ ./maxmin number of data > 5 5 integers > 11 31 -7 0 -12 max = 31 min = -12
負の数も考慮すること.
int a = 0; if (a == 0) printf("Bingo!\n"); printf("%d\n", (a == 0)); // 条件式の値を確認 printf("%d\n", a); // 変数の値を確認
条件式「a == 0」の部分を 以下のように変更するとどうなるか?
そして,これらの実験結果から, if 文の動作原理や条件式の意味について考察せよ.
最大値だけを求めるプログラムの NS チャートの一例を Fig.A に示す.
練習問題では,最小値も求める必要がある. 結果表示も省略されている.
また,このチャートのままでは,コードに無駄があるかもしれない. (同じ処理「最大値=データ」を2回も書いてしまっている.) 各自で工夫しよう.
「処理の場合分け」には if 文だが, 「値の場合分け」の場合には 三項演算式の方が便利だ.
たとえば,こんな if 文:
if (x < 0) { y = -x; } else { y = x; }
これを三項演算式で書けば:
y = (x < 0) ? -x : x;
このようにコンパクトに記述できる. 教科書 pp.63-64 を参照.