情報基礎A 「Cプログラミング」(ステップ3)
このステップの目標
- 分岐構造とプログラムの流れを的確に把握できる
- if文を使って、分岐のあるフローを記述できる
- C言語の条件式を正しく記述できる
1. 選択処理と分岐のあるプログラム構造
二次方程式を解くプログラム(ex3.c)
二次方程式 $a x^2 + b x + c = 0$の 係数 a,b,c を与えると、判別式を評価し、実数解を表示するプログラムの例を以下に示す:
例題3(ex3.c)
この例題は数学関数(sqrt()
)を用いているので,
TurtleEditを使わない(Linuxコマンドで操作する)場合には,
コンパイルするときに(前回のヘロンの公式の計算プログラムと同様に)、
cc ex3.c -lm
のようにcc
コマンドの最後に -lm
(スペース・ハイフン・エル・エム)オプションを付けること
#include <stdio.h> #include <math.h> main( ) { float a, b, c, d, s1, s2 ; printf("係数 a b c を入力してください:") ; scanf("%f%f%f",&a,&b,&c) ; d = b*b - 4*a*c ; if (d<0) { printf("実数解はありません\n") ; } else { s1 = (-b + sqrt(d))/(2*a) ; s2 = (-b - sqrt(d))/(2*a) ; printf("答え: %f %f\n",s1,s2) ; } }
プログラムを実行すると、二次方程式の係数の値を尋ねられるので、三つの数値を順にタイプしてEnterキーを押すと、実数解が(もしあれば)表示される。
if文を使った条件分岐(選択処理)
if文
条件によって,処理(計算)の内容を切り替えたい場合はちょくちょく登場する。 そんな場合にはif 文を使う。
if文には以下のふたつの基本形がある。
- if ( 条件式 ) 文1;
- if ( 条件式 ) 文1; else 文2;
例題3は,ちょっとややこしく見えるけれども、実は、基本形2を使った例である。
文1; や 文2; の個所は、複文(ブロック)でも構わない。ブロックとは { 何々;何々;・・・;} という風に、複数の文を{ }でひとつにまとめたものだった。ブロックはそれ全体がひとつの文のようにして扱われる(処理のひとまとまり、あるいは「段落」のようなもの)。であるから
if ( 条件式 ) { 何々; 何々 ; ・・・ } else { 何々; 何々 ; ・・・ }
という書き方も、基本形2の一種というわけだ。 それぞれの基本形を流れ図で示すと以下のようになる;
条件式
一方、条件式のところは、式の値によって、処理の流れをスイッチするための条件を、下表のようにして記述する。 表中で、a,bのところには、変数だけでなく、数式を記述することもできる(さらに詳しい記述法は、このステップの後半で学ぶ)。
書き方 | 内容 |
---|---|
( a == b ) |
aとbが等しければ・・・ 等号(=) 2つであることに注意 等号ひとつ(=)は「代入」と解釈され、全く意味が異なる。 |
( a < b ) |
bが大きければ・・・ |
( a > b ) |
aが大きければ・・・ |
( a <= b ) |
bがaより大きいか,等しければ・・・ " =< " はNG |
( a >= b ) |
aがbより大きいか,等しければ・・・ " => " はNG |
( a != b ) |
aとbが等しくなければ・・・ |
例題3のその他の見所
- bの2乗は
b*b
と書く(Cではb^2
やb**2
のような書き方は出来ない) - 平方根は sqrt(式) で表現(sqrtはSQuare RooTの略)。これを使うときは、プログラムの先頭部に
#include <math.h>
を忘れずに書いておく。 (-b + sqrt(d))/(2*a)
と(-b + sqrt(d))/2*a
は結果が異なる。数式中の括弧の使い方に注意。
if文の組み合わせによる複雑な分岐処理
例題3のプログラムは、係数aに0を入れると(一次方程式の場合は)、解の公式の分母が0になってしまうので、正しく計算を行うことができない。 そんな場合でも正しく処理するには、aが0の場合と、それ以外の場合で、処理方法を変えなければならない。具体的には、
もしaが0ならば、一次方程式の解を求める そうでなければ(aが0以外ならば)、以下の処理を行う: もし判別式が負値ならば、実数解なし そうでなければ、解の公式で二つの実数解を求める
といった処理の流れになるはずだ。これをCのプログラムに「翻訳」すると、
if (a==0) { s1 = -c/b ; printf("答え: %f\n",s1) ; } else { if (d<0) { printf("実数解はありません\n") ; } else { s1 = (-b + sqrt(d))/(2*a) ; s2 = (-b - sqrt(d))/(2*a) ; printf("答え: %f %f\n",s1,s2) ; } }
となる。if文の基本形2のelseの「内部」に、さらに基本形2のif文が挿入されている点に注目。
字下げ
この例のように、複雑な条件処理をしようとすると、基本形の「何々」中に、さらにif文が「入れ子」構造(「ネスティング構造」とも言う)になる(ならざるを得ない)場合が多い。
if分を記述するときには、基本形の各パーツ毎に字下げを行うことで、どのような分岐構造になっているか、ビジュアル的にも大変わかりやすくなる
(そうしないと、それを書いた本人でさえ、プログラムの流れが判読不能に陥るだろう)。
C言語では、予約語(int, float, if, else
等)、変数名、関数名(main, printf
等),等以外の場所なら、どこにスペースや改行を置いても、
動作には全く影響しないルールになっている。
見かけ上もわかりやすく記述できるかどうかは、プログラム作成者の大切なセンスのひとつだ。
練習:虚数解にも対応できるプログラム
さらにd<0 (虚数解)の場合に、"実部 + 虚部 i" のように計算結果が表示されるよう、プログラムを拡張してみなさい。
ヒント
実部と虚部をそれぞれ別個に計算しておいて、printf("%f + %f i \n", real1,imag1) ;
のように表示すれば良さそうだ。
その際、必要な変数(メモリー)は適宜宣言すること。
発展:三次方程式
さらなる発展として、三次方程式の解の公式(カルダーノの公式等)を使って、
三次方程式を解く(とりあえずは、実数解のみ)プログラムに挑戦してみるのも面白いだろう。
式の途中で、三乗根の計算が必要になるが、Cでは、実数変数x
の三乗根はpow(x,1.0/3.0)
と表現すればよい。
もちろんx
のところが数式でも構わない。
2.複雑な分岐条件の記述
上の節では、if文の全体的な構成にフォーカスを当てたが、if (条件式) {何々;}の (条件式) のパートの書き方にも、色々なバリエーションが可能だ。
書き方の例 | コメント |
if (a+b > c+d) ・・・ |
数式の計算結果同士の比較の例 |
if ( k%2 ) ・・・ |
ここでkはint型と仮定。k%2は、kを2で割った余り。 kが偶数の場合はk%2は0を、奇数の場合はk%2は1を与える。 C言語の条件式では、整数式の結果が0の場合は「偽」を、 それ以外(例えば1)の場合は「真」と解釈される。 つまり、この例は、「もしkが奇数ならば・・・」のC言語流の書き方 |
if ( a*a + b*b == c*c ) ・・・ |
a,b,cがint型の場合には、見てのとおりの条件式。 a,b,cがfloat型の場合(授業で説明はしていないがdouble型の場合も)、この条件式は 意図した通りに働かない可能性が高い。というのは、コンピュータは実数の 近似値を計算しているだけなので、数学的には等号が成立する場合でも、 誤差によって、計算機の内部では両辺が「完全に」等しい保証がないからだ。 例えば、 float x=2; if (sqrt(x)*sqrt(x)==x)・・・は「偽」になってしまう。 |
複雑な条件式
論理積(かつ)、論理和(または)、否定(でない)
込み入った条件を記述しようとすると、以下のように、論理積(「かつ」)、論理和(「または」)、論理否定(「・・でない」)の組み合わせ が必要になることも多い。ここでは、以下の3つの基本的なパターンをきっちりと押さえておこう:
条件 | 書き方の例 | コメント |
A かつ B |
( a > b && b > c ) |
& が二つで「かつ(and)」を表す。左の例を (a > b > c )と書いてはいけない! |
A または B |
( a > b || a > c ) |
| が二つで「または(or)」を表す。 |
A ではない |
( !(a > b) ) |
! は、その右側の条件式の否定。 |
練習:三角形の面積計算
ステップ2の練習課題で、 三角形の3辺の長さa,b,cから、その面積Sを計算するプログラムを作成した。 そのときは、あまり気にしなかったけれども、a,b,cの組をあまりいいかげんに設定すると、そもそも 三角形が構成できないので、面積を計算すること自体に意味が無くなってしまうだろう(そして、ヘロンの公式の根号の中が負になってしまう)。 そこで、入力したa,b,cが三角形を構成できない場合には「三角形が構成できません」と表示するように、 面積計算のプログラムを拡張してみなさい。
ヒント
三角形が構成できる条件は、三角不等式 $a+b>c,\; b+c > a,\; c+a>b$ が成り立つこと。 一般に、(その意図のある無しに関わらず)間違ったデータを入力された場合を想定して、 あらかじめエラー回避を行う処理を仕込んでおく配慮が大切だ。
発展練習:うるう年の判定
参考:暦の計算の基本事項
ある年(西暦)がうるう年(leap year)かどうかを判断するプログラムを作成しなさい。
ヒント
現在採用されているグレゴリオ歴で、うるう年は以下のように定められている:
- 西暦年が4で割り切れ、かつ、100で割り切れないような年はうるう年である
- 西暦年が400で割り切れる年4で割り切れる年はうるう年である
- 上記のいずれにも該当しない年は平年である(うるう年ではない)
剰余
4で割り切れるかどうかを判断するには・・・
if ( year%4 == 0) { ・・・ }
といった具合に、整数の剰余を計算する演算子%を使えばよろしい。たとえば
8%4
は0
を与え、 9%4
は1
、 10%4
は2を与える。
以下に、出発点となるひな形を示しておく:
#include <stdio.h> main() { int year ; printf("year ?") ; scanf("%d",&year) ; if.... ????? ????? }
発展:曜日の計算
暦と日付の計算の説明を読んで、西暦年月日(y, m, d)を入力すると、 その日の曜日を出力するプログラムを作成しなさい。
亀場で練習:三角形の描画(チェック機能付き)
3辺の長さa,b,cを与えると、三角形が構成可能な場合は、辺がちょうどその長さになっている三角形を亀場に描き、 三角形が構成できない場合は、左図のように長さがそれぞれa,b,cの三本の線分を並べて描くプログラムを作成しなさい。
ヒント:
線分の色を変えるには、PD()でペンを下ろす前にCOL()関数を呼び出す。詳しくはこちらのページを参照のこと。