入門書でのおさらい16・for文、演習4-13,4-14,4-15

今日は94ページのfor文から始めたいと思います。

94ページ・for文

今日はいよいよfor文です

ついに for文 ですね!ここをマスターすると、繰り返し処理がぐっと書きやすくなります。

while 文では、「変数の準備」「条件」「カウントアップ」をバラバラの場所に書いていましたが、for 文はこれらを 1行にまとめて書ける のが最大の特徴です。

for文の基本のカタチ

for (初期化式; 条件式; 変化式) {
    // 繰り返したい処理
}
while文と比べてみよう

「1から5まで表示する」プログラムで比較すると、そのスッキリさが分かります。

構造while 文for 文
書き方int i = 1;
while (i <= 5) {
  printf("%d", i);
  i++;
}
for (int i = 1; i <= 5; i++) {
  printf("%d", i);
}
メリット条件だけ決めて回すのに便利「何回繰り返すか」が決まっている時に最強
動きの順番

for (①初期化; ②条件判定; ④変化式) という順番で動きます。

  1. ①初期化: 最初に1回だけ実行(int i = 1
  2. ②条件判定i <= 5 かチェック。OKなら中身へ
  3. ③中身の実行printf など
  4. ④変化式: 次のために i++ する。その後、②の判定に戻る

うん十年まえのfor文のお話・()の中で宣言できるようになったんだ・・・。

実はうん十年前にこのfor文を使いまくってたんだけど、当時は括弧の中でiを宣言せず、forの前に別でint i;と宣言していました。どうして括弧の中に入ることになったのですか?

それは素晴らしいご経験ですね!「int i; を外に出す」のは、かつての標準規格(C89/C90)の書き方です。

for 文の括弧内で宣言できるようになったのは、1999年に制定された C99 という規格からです。これには主に2つの大きな理由があります。

1. 変数の「寿命(スコープ)」を最小限にするため

外で宣言すると、そのループが終わった後も変数 i が生き残ってしまいます。

  • 外で宣言: ループが終わった後、別の場所で間違えて i を使ってしまうミスが起きやすい。
  • 括弧内で宣言: その for 文の中だけで有効な変数になるため、ループが終われば i は消えます。これにより、コードの安全性が高まりました。
2. コードの読みやすさと管理

「この変数はこのループのためだけのもの」という意図が明確になります。
また、同じ関数内で別の for 文を書くときも、いちいち別の名前を考えたり、値をリセットしたりせずに、また for (int i = 0; ...) と書けるので非常に楽になりました。

ちなみに、当時のクセで今も外に書く人はいますが、現在のモダンな開発現場では 「変数の寿命は短ければ短いほどいい」 という考え方が主流なので、括弧内で宣言するのが一般的になっています。

forって何回も使いたいからiが何回も使えるなんて便利になったのでぜひ括弧の中で宣言して使ってみたいなって思いました!

「使い捨てのカウンター」として i を何度も再利用できるのは、今の書き方の大きなメリットですね。昔は別のループを書くたびに「ここは j にしようか、それとも i を 0 に戻して使い回そうか…」なんて悩むこともありましたが、今は各 for 文で int i = 0 と宣言してしまえば、それぞれが独立して動いてくれます。

せっかくなので、i を使い捨てにする便利さ」を実感できるコードを書いてみましょう。

2つのループで i をそれぞれ宣言する例

#include <stdio.h>

int main() {
    // 1つ目のループ:3回挨拶
    for (int i = 1; i <= 3; i++) {
        printf("%d回目のこんにちは!\n", i);
    }

    printf("--- ループの合間 ---\n");

    // 2つ目のループ:カウントダウン
    // 前の i は消えているので、また i を宣言して使えます
    for (int i = 3; i >= 1; i--) {
        printf("あと %d...\n", i);
    }

    return 0;
}

この書き方なら、前のループの i が今いくつだったかを気にする必要がありません。スッキリしますよね。

forの多重ループでは・・・?

ちなみに多重ループでforループの中にforループを書く場合は、()の中で宣言したとしても外側forのカウント用の変数と中のカウント用の変数の文字は変えないといけないよね?

その通りです!変える必要があります。

もし同じ変数名(例えばどちらも i)にしてしまうと、内側のループで i を書き換えたときに、外側のループのカウントまで狂ってしまい、プログラムがめちゃくちゃになってしまいます。

よくある書き方

多重ループでは、アルファベット順に変数名を変えるのが慣習になっています。

  • 外側のループ:i
  • 内側のループ:j
  • さらに内側(3重):k

一番外のforの()の中で宣言した「i」は{}の中をでるまでは生きているんだね。

サンプルコード(九九の表のようなイメージ)

for (int i = 1; i <= 3; i++) {         // 外側(行)
    for (int j = 1; j <= 3; j++) {     // 内側(列)
        printf("i:%d, j:%d  ", i, j);
    }
    printf("\n"); // 内側のループが終わったら改行
}

まさに「スコープ(変数の有効範囲)」の本質を突いた理解です。

for (int i = 0; ...) のように括弧内で宣言された変数は、その for 文に続く { }(ブロック)が終わるまでしっかり生き残っています。

for (int i = 0; i < 3; i++) {  // ←ここから i の人生スタート
    
    // この { } の中ならどこでも i が使える
    for (int j = 0; j < 3; j++) {
        // 内側のループの中でも、外の i はまだ生きている!
        printf("i=%d, j=%d\n", i, j);
    } 
    
} // ←ここで i の人生終了(メモリから消える)

// ここで i を使おうとすると「iなんて知らないよ」とエラーになる

内側のループが回っている間も、外側の i は「俺は今1回目だぞ…」と値をキープして待機しています。そこで内側でも i という名前を使ってしまうと、待機していた外側の i と鉢合わせして喧嘩(衝突)してしまうわけですね

95ページの謎

forは前判定繰り返しなの?94ページにはforは前判定切り返しだと書かれています。

はい、その通りです!for 文は 「前判定」 の繰り返し処理です。 

while 文と同じで、「中身を実行する前に、まず条件をチェックする」 という動きをします。

復習:C言語の繰り返し3つのタイプ

せっかくなので、判定タイミングの違いでおさらいしてみましょう。

  1. for文(前判定)
    • 回数が決まっている時に便利。最初に判定。
  2. while文(前判定)
    • 条件が決まっている時に便利。最初に判定。
  3. do-while文(後判定)
    • 「とりあえず1回やってから判定」 するタイプ。これだけが仲間外れです。 

「条件に合わないなら、最初から何もさせたくない」というケースがほとんどなので、実務では前判定の for や while が圧倒的に多く使われます。

じゃあ解説書の95ページには以下のように後判定と書いてあるのですが、いったいどういうことなんでしょう??

後判定繰り返しを行うfor文は、カウンタ用変数の開始値/終了値/増分を、頭部に集約して簡潔に表現できる。

出典:柴田望洋『新・明解C言語 入門編』95ページ

その解説書の記述、少し混乱してしまいますよね。おそらくその一文は、「(while文のような)後判定の処理と同じようなことを、for文を使うと頭部に情報を集約してスマートに書ける」という文脈か、あるいは非常に特殊な解説をしている可能性があります。

ただ、C言語の言語仕様としての「for文」の性質は、間違いなく前判定(Pre-test)です。

書き間違いでしょうか?知ってる方は教えてください。ここは深追いせず次のページに進むことにします。

演習4-13

演習4-13、forの練習ですね。できそうな気がします!

#include<stdio.h>
int main(void){
    int no;
    printf("nの値:");
    scanf("%d",&no);

    int sum = 0;

    for(int i = 1; i <= no; i++){
        sum += i; 
    }
    printf("1から%dまでの総和は%dです\n",no, sum);
    return 0;
}

素晴らしいです!完璧に正解です。

特に以下のポイントがバッチリ押さえられています。

  • sum = 0 の初期化: これを忘れるとゴミ箱に数字を入れるような状態になりますが、しっかり0からスタートできています。
  • int i = 1 からのスタート: 「1から」という問題文の指示通りです。
  • i <= no の条件式no(入力された値)まで含めて足すために、正しく <= が使われています。
  • 複合代入演算子 sum += isum = sum + i と書くよりもスマートで、C言語らしい書き方ですね。

自分で組み立てて一発で動くと気持ちいいですよね!

やったー!では次々行きましょう!

演習4-14

1234567890をどう考えるかがみそだね。

#include<stdio.h>
int main(void){
    int no;
    printf("正の整数を入力してください:");
    scanf("%d",&no);

    for(int i = 1; i <= no; i++){
        printf("%d",i % 10);
    }
    putchar('\n');
    return 0;
}

完璧です!お見事です。

特に i % 10 という発想が素晴らしいですね。10になったときに余りが0になる性質をうまく使って、「1234567890」の繰り返しをスマートに実現できています。

また、最後に putchar('\n'); を使って改行を入れているのも、丁寧でとても良い書き方です。

ちなみに、0以下が入力されたときの対策は何もしてないのを作ってしまったのですが・・・

あえてシンプルに、「想定された入力」に対するロジックの正解を突き詰められたのですね!

実際、学習の段階ではまず「やりたい処理(メインロジック)」を正しく書けることが一番大切です。

ちなみに、このコードに「0」や「-5」を入れた場合、i <= no(1 <= 0 など)が最初から「偽」になるので、前判定のルール通り、ループの中身は一度も実行されずに改行だけして終わりますね。動作としても破綻していないので、今の段階ではこれで十分合格点です!

あ、本当だ!「i <= no」の条件で0やマイナスははじかれてるんだ。

そうなんです!先ほどお話しした「for文は前判定」という性質が、ここでバッチリ効いていますね。

「1から始まる i」が「0以下の no」と比較されるので、一番最初のチェックで「あ、条件を満たしてないな」と判断されて、ループの中身を華麗にスルーします。意図せずとも、前判定のおかげで安全な動きになっているわけです。

実体験として「前判定の挙動」を確認できましたね!

演習4-15

お、結構難しいかも。ちょっと作ってみます。

#include<stdio.h>
int main(void){
    int h1,h2;
    int a;
    printf("何cmから:");
    scanf("%d",&h1);
    printf("何cmまで:");
    scanf("%d",&h2);
    printf("何cmごと:");
    scanf("%d",&a);

    for(int i = h1; i <= h2; i += a){
        printf("%dcm\t%.2fkg\n",i,(double)(i-100) * 0.9);
    }

    return 0;
}

完璧です!素晴らしい!

i += a(増分) をしっかり使いこなして、指定された間隔でループを回せていますね。さらに、計算結果を正しく (double) でキャストして、%.2f で小数第2位まで表示させる部分も、以前の学習内容がしっかり定着しているのを感じます。

\t(タブ)を使って表示を整えているのも、表形式のプログラムとして非常に見やすくて良い工夫です。

今日学んだ for 文の「初期化・条件・更新」の3つの要素をフル活用した、まさに集大成のようなコードですね。

スペース入れても良かったんだけど「\t」はタブっていうのをうん十年前の記憶から出してきて使ってみました