繰り返し構造(while文,do-while文,for文)

C言語初心者によくある間違い

C言語プログラミングの初心者(勉強を始めたばかり)が良く陥る間違い例を以下に列挙しておきます.

printf()関数の「書式文字列」のダブルクォート(")の閉じ忘れ

printf()関数のかっこ内の一つ目の引数は書式文字列と呼ばれるが,これは最初と最後をダブルクォート(")で括らねばならない.この最後のダブルクォートを括りわすれている間違いが良くある.

間違いの例

printf("Input Number > ); // )の前に " を忘れている!

文末のセミコロン(;)の付け忘れ

C言語の文末には原則としてセミコロン(;)を付ける.(付かないのは中括弧( { } )の後だけです.)

次のように,ついセミコロンを付けわすれると,たとえウッカリ間違いであってもCコンパイラは容赦なく「エラー」として扱う.

if (num <= 0) 
    num = num + 1    // 文の末尾に ; を忘れている!
firstNum = firstNum + num; 

全角文字(2バイト文字)が混在

C言語のプログラムに出現する記号や変数名などは,全て半角文字(1バイト文字)でなければなりません.2バイト文字を使って良いのはprintf()関数を使って日本語メッセージを画面表示させる場合の書式文字列だけだと思っておきましょう.

しかし,Emacsでソースプログラムを作成している時に間違って全角文字(2バイト文字)が入ってしまっている場合がある.(ちなみに,日本語文字における1バイト文字,2バイト文字に関する内容は,既に学習しました.こちらのページの「日本語文字に関する注意事項」を参照してください.) このような場合,「一見,正しいプログラムに見える」ので非常に見つけ難い厄介な間違いになることがあります.

その他,初心者が悩みそうな間違いの例を龍谷大学の松木平 淳太先生が纏めておられますので,参考にさせていただきましょう.
エラーが出たときの対処法

練習1

上記のような2バイト文字の混在による間違いの例として

を置いてあるので,これを各自の作業ディレクトリにコピーしてコンパイルしてみよう.

コンパイルエラーのメッセージがが表示されるので,何処が間違ってるか慎重に探して正しく動作するように修正してみよ.

プリプロセッサ命令

C言語プログラム中で#記号で始まる #include <....> や #define ..... といった箇所をプリプロセッサ命令と呼ぶ(詳細は4.2節を参照).

このプリプロセッサ命令のうち#define命令は,これをプログラム中で用いると,

#define ADD 1

と書かれた部分が存在した場合,それ以後のプログラム中に出現する ADD と書かれた個所はコンパイル前に全て 1 に置き換えた上でコンパイル処理が実行される.この考え方を「マクロ定義」と呼ぶ.

一般に,プログラム中で「定数 (constant)」を使う場合,ソースプログラム中に数値を書き込むのは望ましくない.そのような数値をマジックナンバーと呼び,後からプログラムを読み返した際にプログラムの流れを理解する妨げになるとか,定数を修正する場合に不便や間違いが起きやすいからである.例えば,半径rの円の面積を求めて変数に代入するような場合,

area = 3.14159*r*r; 

といった具合に円周率という定数をプログラムを記述することは,基本的に避けるべきであり,例えば,次のようにマクロ定義を用いて表現すべきである.

#define PI 3.14159

main()
{
  .....
  .....
  area = PI*r*r;
  .....
  .....
}

このように書いておくと,Cコンパイラは機械語プログラムへの翻訳処理を開始する前に,

main()
{
  .....
  .....
  area = 3.14159*r*r;
  .....
  .....
}

という具合に,#define命令で定義された文字列に該当する箇所を置き換える事前処理を行い,その後に実際のコンパイル処理を行う.

このようにプリプロセッサ命令によるマクロ定義によって,「プログラム中にマジックナンバーが出現してしまい,後日にプログラムを修正する際にマジックナンバーの数値の意味が思い出せない」といった事態を避けることが出来る.

繰り返し処理

計算機が最も得意とするのは,同じような処理を間違えずに繰り返し実行することである.

この「繰り返し」を実現する構文として「繰り返し構造」があり,条件分岐と並んでプログラミング言語において重要な構文である.(詳細は教科書第5章を参照すること.)

while文

while文は条件が満たされている間,一連の処理を繰り返し実行するものである.その構文は次の通り.

while( 条件式 )
    文;

または

while( 条件式 ) {
    文1;
    文2;
    文3;
}

練習2

pp.79例5.3及びpp.81例5.4を参考にして,整数値nをキ−ボ−ドから入力して,nの二乗, (n-1)の二乗, (n-2)の二乗,.....,2の二乗, 1の二乗を計算し,元の数値と二乗された数値の両方を表示するプログラムをwhile文を利用して作成してみよ(ファイル名while.c).また,最初に入力する整数として負数を入力するとどのように動作するか確認せよ.

(ヒント: 変数numberに最初に代入する値をscanf文で読み込み,その値を1づつ減らし,そのたびに変数と計算結果を画面表示して,その処理をnumberが1以上の間は繰り返す.)

実行例:

整数値を入力して下さい -> 5
5 ^ 2 = 25
4 ^ 2 = 16
3 ^ 2 = 9
2 ^ 2 = 4
1 ^ 2 = 1

do-while文

do-while文はある条件が満たされている間,処理を繰り返す.構文は以下の通り.

do {
  文1;
  文2;
  文3;
} while( 条件式 );

練習3

練習2と同じ動きをするプログラムをdo-while文を用いて作成して実行してみよ(ファイル名do_while.c).また,最初に入力する整数として負数を入力するとどうなるか確認せよ.

while文とdo-while文の相違

while文とdo-while文の違いは,while文は条件が最初から成立しない場合,繰り返しは一度も実行されない(前判定繰り返しと呼ぶ)のに対して,do-while文は条件が最初から成立していなくとも少なくとも一度は処理が実行される(後判定繰り返し).

for文

for文は,繰り返し処理のために非常によく用いられる構文である.for文の構文は次の通り.

for (式1; 式2; 式3)
  繰り返し実行したい文;

または

for (式1; 式2; 式3) {
  繰り返し実行したい文1;
  繰り返し実行したい文2;
  繰り返し実行したい文3;
}

式1は繰り返しを実行する最初に実行され,式2は繰り返しを続けるかどうかの条件式であるのが殆どである.式3は繰り返しを行う毎に実行される.

一般にfor文は,繰り返し処理中に値が変更されて繰り返しを制御するカウンタ変数と呼ばれる変数を用いて書かれる.例えば,1以上100未満の奇数の二乗を画面表示する処理は次のように書ける.この例ではカウンタ変数はnumberとなる.

int number; // カウンタ変数
int odd; // 奇数の計算結果
int answer; // 二乗計算結果

for (number = 1; number < 51; number++) {
   odd = number * 2 - 1; // 奇数を計算
   answer = odd * odd;
   printf("%d ^ 2 = %d¥n", odd, answer);
}

尚,number++ と  number = number +1 は同じ意味を持つことに注意すること(詳細は教科書pp.137を参照).

繰り返し処理を理解するヒント: 
手元にメモを用意して,プログラムに出てきた変数を図としてメモ書きしなさい.
プログラムの動きを頭の中でイメージする手助けになるように,その図に変数の値の状態をメモ書きするようにしなさい.
そして,プログラムの流れを追跡する途中に変数の値が変わったら,その図に記入した値を消しゴムで消して書き直しなさい.
このように,少し複雑なプログラムの動きを理解するには 自分自身がプログラムを実行するコンピュータになったつもりで,変数の状態をメモ書きしながら考えると分かりやすくなるはずです.

練習4

上記の記述,及び,教科書pp.84にある例5.6を参考にして,for文を用いて2以上100以下の偶数の二乗を計算してそれぞれ表示するプログラムevensquare.cを作成せよ.

練習5

自然数nを入力すると,それに応じて以下のように"KSU"という文字列をn行表示するプログラムksu.cを用いて作成せよ.

以下の実行例ではキ−ボ−ドから入力した数値は赤色で示す.

実行例:
回数を入力して下さい > 1
KSU

実行例:
回数を入力して下さい > 4
KSU
KSU
KSU
KSU

ヒント: for文を使って,カウンタ変数を増やしながら繰り返しを行い,カウンタ変数が入力された数値に一致するまで処理を繰り返せば良い.(何を「繰り返す」のでしょうか? 考えてみましょう.)

プログラムの間違いを見つける(デバッグ)

プログラムに潜んだ間違い(バグ, bug)を探しだす作業をデバッグ(debug)と呼び,発見した間違いを修正することをバグ・フィックス(bug fix)と呼ぶ.

極く基礎的なC言語の初歩を学んでいる段階では,プログラムが自分の思った通りに動かない理由の多くが,プログラムに出現する変数にが正しい値が代入されていない事で大半だろう.また,初心者に限らず,デバッグの基本は「変数に正しい値が代入されているか?」「変な値になっていないか?」を確認する事である.

最も原始的だが意外に有効なデバッグ方法は,プログラム中にprintf()関数を要所々々に書き込んでおいて,実行中の変数の値を画面表示させるようにすることである.例えば,

for(i = 0, i < 10, i++) {
    ....
    ....
    printf("DEBUG: count = %d¥n", count); 
    ....
    ....
}

という具合にprintf()関数を入れておけば,for文の繰り返し処理の中で変数countの値が考えた通りに変化しているかどうかを追跡して確認することができる(プログラムが少し複雑になってくると,「変数の値をメモ書き」では,少し辛い).

尚,ここでは,デバック用メッセージであることが分かるように,"DEBUG: "という目印が表示される工夫も合わせて入れている.このようなデバッグ目的のprintf()関数を面倒くさいと考えずに,積極的,かつ,適切に入れることで,むしろ早く間違いを発見できることが多い.

ちなみに,プログラミングの上達が遅い者には,このちょっとした手間をかけることを惜しんで,「一気に完全なプログラム」を作ろうと試みて失敗している場合がよく見られる.W杯サッカー日本代表の言葉ではないが,「ヘタクソは泥臭く地道にやる」のが成功に繋がるのだ.
また,プログラムの動作を良く理解しないまま,「文字面だけコピー&ペースト」しても例題は動かせるかも知れませんが,「課題」は達成出来ません.例題やサンプルなど他人が作ったプログラムは「考え方を理解した上で,その考え方だけを借りて真似」するようにしましょう.スポーツの練習と同じですね.

間違いが修正できた(又はできたと思った)場合でも,このデバッグ用printf()関数の部分はソースプログラムから慌てて削除しないで,プログラムが完璧になったと確認できるまでは,次のようにコメントにしておくと良い(プログラムの一部分をコメント行になるように意図的に修正することを「コメントアウト(comment out)」と呼ぶ).

for(i = 0, i < 10, i++) {
    ....
    ....
    // printf("DEBUG: count = %d¥n", count); 
    /* 又は printf("DEBUG: count = %d¥n", count); */
    ....
    ....
}

このようにコメントアウトしてprintf()関数を残しておくと,デバッグ中に再び変数countの変化を追跡する必要が生じた時,コメント記号(//や/*と*/)を取り去るだけで直ちに変数の追跡表示が再開できる.追跡の必要が無くなれば,またコメントアウトすれば良い.

このデバッグ用printf()関数の部分を削除するのは,プログラムが完全に動作することを確認できて完成に至った後に行うのが賢いやり方である.また,デバッグ用コメント行は残しておいても,提出プログラムの成績評価には基本的に影響しないから残しておいても構わない.提出前に「自分の美的センス」に従って削除するなり残すなりを判断しよう.

但し,提出締切寸前に慌ててデバッグメッセージ部分を削除しようとし,勢い余ってデバッグメッセージではない箇所まで削除してしまって,プログラムが動作しなくなる.....といった事故もたまにある.何事も余裕を持って行うべし.

練習6

上記の練習で作成したksu.cを修正して,プログラムで用いている各変数の値がデバッグメッセージとして表示されるようにしてみよ.また,そのメッセージ表示部分をコメントアウトしてデバッグメッセージが表示されないように修正して,動作を確認せよ.

練習7

自然数nを入力すると,それに応じて以下のように"KSU"という文字をその回数だけ1行に表示するプログラムksu_line.cを作成せよ.

ヒント:"KSU "を必要な回数だけ出力し,その後で改行文字 ¥n だけをprintf()関数で出力すれば良い.

実行例:
回数を入力して下さい > 2
KSU KSU 

実行例:
回数を入力して下さい > 5
KSU KSU KSU KSU KSU

break文について

既にswitch-case文の説明箇所で break 文という制御文は出てきました.

while文やfor文などの繰り返し構造の中でbreak文が使われると,その意味はそのbreak文が出現した位置で実行されている途中の繰り返し処理を打ち切るという意味になります.(但し,繰り返しが入れ子になっている場合は,原則として,そのbreak文を含む直近の繰り返しが打ち切られます.)

for文の入れ子構造

for文は繰り返す処理内容の中に,さらにfor文を用いた処理を含めることが出来る.これを「for文の入れ子(ネスト)」と呼ぶ.

尚,「入れ子」の語源は,形状が同じで大きさが少しづつ違っていて片づける時に重ねて場所を取らないように作られたカゴが古来より「入れ子」と称されており,この見掛けがfor文などが多重に使われているソースプログラムの様子と似通っている事に由来する.

(楽天市場 http://item.rakuten.co.jp/peewee/interior_basket028/ より引用)

練習8

以下に教科書pp.89例5.8のソースプログラムを置いているので,手元でコンパイルして動作を確認してみよ.

また,上記のソースプログラムには,教科書には記載されてない「デバッグメッセージ」をコメントして付け加えてある.このデバッグメッセージを有効にして,どのような画面表示になるか確認し,例5.8の動きを理解せよ.

練習9

for文の入れ子構造を必ず使って,"KSU"という文字を1行に5個,入力された回数分の行数だけ表示するプログラムksu_lines.cを作成せよ.(ヒント:"KSU "の出力を一行で5回繰り返す処理を,キーボードから読み込んだ数値の回数だけ繰り返せばよい.上記の2つの練習を組み合わせて考えてみよ.)


		
実行例:
回数を入力して下さい > 2
KSU KSU KSU KSU KSU 
KSU KSU KSU KSU KSU 

実行例:
回数を入力して下さい > 4
KSU KSU KSU KSU KSU 
KSU KSU KSU KSU KSU 
KSU KSU KSU KSU KSU 
KSU KSU KSU KSU KSU

練習10

自然数nを入力すると,1行にn個のKSUという文字をn行分表示するプログラムksu_cube.cを作成せよ.

実行例:
回数を入力して下さい > 3
KSU KSU KSU 
KSU KSU KSU 
KSU KSU KSU 

実行例:
回数を入力して下さい > 6
KSU KSU KSU KSU KSU KSU 
KSU KSU KSU KSU KSU KSU 
KSU KSU KSU KSU KSU KSU 
KSU KSU KSU KSU KSU KSU 
KSU KSU KSU KSU KSU KSU 
KSU KSU KSU KSU KSU KSU

if-else文での条件として等価条件の勘違いについて

if-else文で用いる条件式として何かの値が等しいという等価条件を表現したい場合, == 記号を使わねばなりませんが間違って = 記号を使ってしまっているのに気がついてない人が相当数いるようです.

その場合,C言語の文法間違いでは無いのでコンパイルは出来てしまい,一見,正しく動くように見えますが,残念ながら想定した通りにはならず奇妙に思える動作をします.

練習11

if-else文における等価条件に関する挙動を確認するための例が以下に置いてある.これを手元の作業ディレクトリにコピーして,コンパイルした上で動作させてみて,どこが奇妙なのか確認せよ.

注意! 変数の初期化忘れによる奇妙な振るまい

C言語では,プログラム中で宣言した変数について,その宣言直後の中身(変数の値)については「不定(決まってない)」という言語仕様(約束)になっています.

これにより,宣言しただけで一度も何か値を代入しないままに変数の値を使ってしまうようなコードを書くと,コンパイルも出来て一応は動くのだが,その変数の値が状況(動かすタイミング,機種の違い,同時に使っている他のソフトウエアのメモリ使用状態など)で様々に変化しており,プログラムが思ったように動かない(つまり,バグ)という現象が起きる事があります.

練習12

変数の初期化忘れによるプログラムの異常な挙動を確認するためのサンプルソースプログラムが以下に置いてある.各自,コンパイル・実行して確認し,さらにソースプログラム中のコメントに記載されている内容に従って修正を加えて,異常が起こらないようにしてみよ.

尚,全く同じプログラムをNetBoot環境で動作させた場合,ローカルブートで動作させた場合などでプログラムの挙動が変わるかも知れません.各自,試してみてください.

教訓としては,C言語では変数宣言した後で,最低一度は代入文を実行して何かの値が代入されている事をしっかり確認するように習慣づけしておくと良いでしょう.

提出課題

課題その1

上記の練習10で作成した「自然数nを入力すると,1行にn個のKSUという文字をn行分表示するプログラム」であるksu_cube.cを作成し,moodle学習支援システムで提出せよ.

レポート名: program/cube

提出期限: 教員の指示による

課題その2

自然数nを入力すると,その数値に応じてKSUという文字をピラミッド型に表示するプログラムpyramid.cを作成し,moodle学習支援システムで提出せよ.

レポ−ト名:program/pyramid

提出期限:教員の指示による

実行例:
回数を入力して下さい > 3
KSU 
KSU KSU 
KSU KSU KSU 

実行例:
回数を入力して下さい > 5
KSU 
KSU KSU 
KSU KSU KSU 
KSU KSU KSU KSU 
KSU KSU KSU KSU KSU

次回へ向けて

教科書「新・C言語のススメ」の第6章「多数データの取り扱い」を良く読んで理解しておきましょう.もちろん,可能な限り例題プログラムも予め動作させてみて,その考え方を頭の中に整理しておくこと.

基礎プログラミング演習Iの表紙ページへ戻る