コンテンツにスキップ

第4章 プログラムの流れの繰り返し(2)

4-3 for文

定型的な繰返しは、for 文によって実現すると、while 文よりも簡潔かつ読みやすくなる。本節では、for 文を学習する。

for文

本節で最初に考えるのは、List 4-12 である。これは、for 文(for statement)と呼ばれる文を使って、List 4-7(p.85)のカウントアップのプログラムを書きかえたものである。

List 4-12

// 読み込んだ正の整数値までカウントアップ(for文)

#include <stdio.h>

int main(void)
{
    int no;

    printf("正の整数を入力せよ:");
    scanf("%d", &no);

    for (int i = 0; i <= no; i++)
        printf("%d ", i);
    putchar('\n');    // 改行

    return 0;
}

実行例

正の整数を入力せよ:12  
0 1 2 3 4 5 6 7 8 9 10 11 12

参考:List 4-7

int i = 0;
while (i <= no)
    printf("%d ", i++);
printf("\n");

Fig.4-17 に示すのが、for 文の構文図である。for に続く ( ) の中は、2 個のセミコロン ; で区切られており、A部、B部、C部の三つで構成される。

【Fig.4-17】for文の構文図

Fig4-17

さて、本プログラムの for 文が、while 文を書きかえて作られていることからもわかるように、for 文が行うループは、while 文と同じく前判定繰返しである。

Fig.4-18 に示す for 文と while 文は、ほぼ同等である。少し書きかえるだけで、二つの文は相互に置換できる。

【Fig.4-18】for文とwhile文

Fig4-18

while 文と見比べると、for 文のプログラムの流れが、分かってくる。Fig.4-19 を見ながら理解していこう。

前処理ともいうべきAが、1回だけ評価・実行される(あるいは宣言された変数が作られる)。

繰返しの継続条件であるBの制御式の評価で得られたのが真(非0)であればループ本体の文が実行され、偽(0)であればループ本体は実行されない。

ループ本体の実行後は、後始末的な処理、あるいは次の繰返しのための準備として、Cが評価・実行された上で、制御式の判定Bに戻る。

【Fig.4-19】for文のプログラムの流れ

Fig4-19

本プログラムの for 文の繰返しは、次のように解釈すると分かりやすくなる。

変数 i を 0 から始めて、no と等しくなるまで、1 ずつ増やしながらループ本体を実行する。

最初に 0 で初期化された変数 i が、no 回インクリメントされることが分かるだろう。

なお、変数 i のように、繰返しの制御に使う変数は、カウンタ用変数と呼ばれる。 for 文では、カウンタ用変数の開始値/終了値/増分のすべてを、( ) の中に集約できる。 構文は複雑だが、慣れてしまえば、while 文よりも読みやすくなる。

重要

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

Tip

増分は、カウンタ用変数が繰返しのたびに「いくつ増えるか」である(本プログラムでは1である)。

for 文の概要が分かった。各部に関する細かい文法規則を学習していこう。

Column 4-1 なぜカウンタ用変数の名前は i や j なのか

Column 4-1

多くのプログラマが、繰返し文を制御するためのカウンタ用変数名として i や j を使う。 その歴史は、技術計算用のプログラミング言語FORTRANの初期の時代にまで遡る。この言語では変数は原則として実数である。しかし、名前の先頭文字が I, J, …, N の変数だけは自動的に整数とみなされていた。そのため、繰返しを制御するための変数としては I, J, …を使うのが最も簡単な方法だったのである。

A 前処理

この式は、繰返しが行われる前に 1 回だけ評価・実行される(あるいは、宣言された変数が作られる)。

なお、ここで宣言された変数は、その for 文の中だけで使える。そのため、異なる for 文で同一名の変数を使うときは、次のように、各 for 文ごとに宣言が必要である。

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

for (int i = no; i >= 0; i--)
    printf("%d ", i);
putchar('\n');

Example

ここに示したのは、List 4-12 と同様のカウントアップを行った後に、0 までカウントダウンを行うプログラムである("chap04/list0412a.c")。

もし仮に、A部で宣言された変数が、for 文を越えて適用する言語仕様であれば、上記のコードは、次のように記述することになる。

for (int i = 0; i < n; i++)    // iを0で初期化する宣言
    printf("%d ", i);
putchar('\n');

for (i = no; i >= 0; i--)    // iにnoを代入
    printf("%d ", i);
putchar('\n');

ここで、最初の for 文を削除したらどうなるだろう。変数 i の宣言が失われるため、2 番目の for 文の代入 i = no; も、宣言 int i = no; に変更しなければならない。

上下に並んだ for 文の見た目のバランスがとれ、変数を確実に宣言でき、プログラムの変更にも対応しやすくなっているのは、for 文ごとに変数を宣言する文法仕様のおかげである。

なお、A部で行うことがない、あるいは、変数の宣言の必要がないのであれば、この部分は省略可能である。

B 制御式

繰返しの継続条件を表す制御式である。この式が真であれば(評価によって得られた値が非0であれば)、ループ本体が実行される。

この式を省略すると、繰返しの判定は真(非0)とみなされる。そのため、break 文などを使わない限り、無条件すなわち永遠に繰返しを行う無限ループとなる。

C 後始末/次の繰返しのための準備

この式は、《後始末的な処理》または《次の繰返しのための準備》として、ループ本体の実行後に評価・実行される。何も行うことがなければ、この式も省略できる。

Example

次に示すのは、while 文による無限ループと、for 文による無限ループの実現例である(for 文のほうは、A部、B部、C部のすべてを省略している)。

// whileによる無限ループ
while (1)
    

// forによる無限ループ
for ( ; ; )
    

for 文による一定回数の繰返し

読み込んだ整数値の個数だけ * を連続して表示するプログラム(List 4-8: p.86)を、for 文で書きかえてみよう。List 4-13 に示すのが、そのプログラムである。

List 4-13

// 読み込んだ整数の個数だけ * を連続表示(for文)

#include <stdio.h>

int main(void)
{
    int no;

    printf("正の整数:");
    scanf("%d", &no);

    for (int i = 1; i <= no; i++)
        putchar('*');
    putchar('\n');

    return 0;
}

実行例

正の整数:15  
***************

参考:List 4-8

while (no-- > 0)
    putchar('*');
putchar('\n');

for 文は、( ) の中に繰返しの条件が集約されている。本プログラムの for 文は、

変数 i を 1 から始めて、no と等しくなるまで、1 ずつ増やしながらループ本体を実行する。

と読む。

それでは、変数 i の開始値を 1 ではなく 0 に変更しよう。次のようになる(制御式の <= を < に変更する:"chap04/list0413a.c")。

for (int i = 0; i < no; i++)
    putchar('*');

『n 回の繰返し』を行う for 文と while 文の定型的なパターンを Fig.4-20 にまとめている。

【Fig.4-20】n回の繰返しを行う for文と while文

Fig4-20

Note

for 文は、変数 i の値が変化して n は変化しない。while 文は、n の値そのものが変化する。

次は、List 4-14 のプログラムを理解しよう。最初に整数値を読み込み、その個数だけの整数を次々と読み込んで合計値と平均値を表示するプログラム(List 4-9: p.88)を、for 文で書き直したものである。

List 4-14

// 指示された個数だけ整数を読み込んで合計値と平均値を表示

#include <stdio.h>

int main(void)
{
    int num;

    printf("整数は何個:");
    scanf("%d", &num);

    int sum = 0;        // 合計値
    for (int i = 1; i <= num; i++) {
        int tmp;
        printf("No.%d : ", i);
        scanf("%d", &tmp);
        sum += tmp;
    }

    printf("合計値:%d\n", sum);
    printf("平均値:%.2f\n", (double)sum / num);

    return 0;
}

実行例

整数は何個:6  
No.1: 65  
No.2: 23  
No.3: 47  
No.4: 9  
No.5: 153  
No.6: 777  
合計値:1074  
平均値:179.00

参考:List 4-9

while (i < num) {
    int tmp;
    printf("No.%d: ", ++i);
    scanf("%d", &tmp);
    sum += tmp;
}

for 文では、変数 i の値を 1 から num までインクリメントすることによって、num 回の繰返しを行っている。

Note

List 4-9 では、変数 i を 0 から始め、『printf("No.%d: ", ++i);』と、変数 i のインクリメントを行った上での表示を行っていた。本プログラムでは、このインクリメントは除去されている(変数 i の開始値が 1 に変更されて、そのインクリメントが for 文のC部で行われているからである)。

Example

【演習 4-13】 1 から n までの総和を求めるプログラムを作成せよ。n の値はキーボードから読み込むこと。

【実行例】

nの値:5  
1からnまでの総和は15です。

Example

【演習 4-14】 右に示すように、12345678901234567890 を繰り返し表示するプログラムを作成せよ。読み込まれた整数値の個数だけ数字を表示すること。

【実行例】

正の整数を入力せよ:25  
1234567890123456789012345

Example

【演習 4-15】 右に示すように、身長と標準体重(p.39)の対応表を表示するプログラムを作成せよ。なお、表示する身長の範囲(開始値、終了値、増分)は整数値として読み込んで、求めた標準体重は小数部を2桁表示すること。

【実行例】

何cmから:155  
何cmまで:190  
何cmごと:5  
155cm  49.50kg  
160cm  54.00kg  
... 以下省略 ...

読込みを《中断》できるようにしよう。具体的には、-9999 を読み込んだら、合計と平均を表示するように仕様変更する。それが、List 4-15 のプログラムである。

List 4-15

// 指示された個数だけ整数を読み込んで合計値と平均値を表示(中断あり)

#include <stdio.h>

int main(void)
{
    int num;

    printf("整数は何個:");
    scanf("%d", &num);
    printf("終了は-9999\n");

    int i;
    int sum = 0;        // 合計値
    for (i = 0; i < num; i++) {
        int tmp;
        printf("No.%d: ", i + 1);
        scanf("%d", &tmp);
        if (tmp == -9999) break;
        sum += tmp;
    }

    printf("合計値:%d\n", sum);
    printf("平均値:%.2f\n", (double)sum / i);

    return 0;
}

実行例

整数は何個:6  
終了は-9999  
No.1:65  
No.2:23  
No.3:47  
No.4:-9999  
合計値:135  
平均値:45.00

数多くの点が変更されている。

① for 文の繰返しの制御のために使われていた変数 i を、キーボードから読み込んだ整数値の個数を覚える用途としても使うように変更している。for 文終了後にも、変数 i の値が必要になるため、ここで(for 文より前で)宣言している。

② 変数 i の宣言が、値の代入に変更されている(①で宣言された変数 i に対して 1 ではなく 0 を代入している)。

③ 変数 i の値に 1 を加えた値を表示するように変更されている。

④ 読み込んだ値が-9999 であれば、break 文を実行することで、for 文の繰返しを中断する(このときの i の値は、読込み済みの整数の個数と一致する)。

⑤ 平均を求める除算では、本来読み込むはずの個数であった num ではなく、実際に読み込んだ個数である i で割る。

重要

for 文終了後も値が必要となるカウンタ用変数は、for 文の中ではなく、for 文の前で宣言しなければならない。

偶数の列挙

整数値を読み込んで、その整数値以下の正の偶数 2, 4, …を表示するプログラムを作ろう。

List 4-16 が、そのプログラムである。

for 文のC部 i += 2 で使っている += は、右オペランドの値を左オペランドに加える複合代入演算子(p.80)である。

変数 i に 2 を加えるのだから、最初に 2 で初期化された変数 i は、繰返しのたびに二つずつ増える。

List 4-16

// 読み込んだ整数値以下の偶数を表示

#include <stdio.h>

int main(void)
{
    int n;

    printf("整数値:");
    scanf("%d", &n);

    for (int i = 2; i <= n; i += 2)
        printf("%d ", i);
    putchar('\n');

    return 0;
}

実行例

整数値:15  
2 4 6 8 10 12 14

約数の列挙

次は、整数値を読み込んで、その整数値のすべての約数を表示するプログラムを作る。

List 4-17 が、そのプログラムである。

for 文では、変数 i の値を 1 から n までインクリメントしている。

n を i で割った剰余が 0 であれば(n が i で割り切れたら)、i は n の約数であると判定できるから、その値を表示する。

List 4-17

// 読み込んだ整数値の全約数を表示

#include <stdio.h>

int main(void)
{
    int n;

    printf("整数値:");
    scanf("%d", &n);

    for (int i = 1; i <= n; i++)
        if (n % i == 0)
            printf("%d ", i);
    putchar('\n');

    return 0;
}

実行例

整数値:12  
1 2 3 4 6 12

Example

【演習 4-16】 整数値を読み込んで、その整数以下の奇数を表示するプログラムを作成せよ。

【実行例】

整数値:15  
1 3 5 7 9 11 13 15

Example

【演習 4-17】 右に示すように、1 から n までの整数値の 2 乗値を表示するプログラムを作成せよ。

【実行例】

nの値:3  
1の2乗は1  
2の2乗は4  
3の2乗は9

Example

【演習 4-18】 整数値を読み込んで、その個数だけ '*' を表示するプログラムを作成せよ。ただし、5 個表示するごとに改行すること。

【実行例】

何個を表示しますか:12  
*****  
*****  
**

式文と空文

次のコードを考える。n 個の '+' が表示されると感じるのではないだろうか。

for (int i = 1; i <= n; i++);
    putchar('+');

ところが、n がどのような値であっても、表示される '+' は 1 個だけである。

そうなる原因は、i++) の後ろに置かれた ; にある。これは、空文(null statement)と呼ばれる文である。その空文は、実行しても何も行われない。

すなわち、上のコードは、次のように解釈されるのである。

for (int i = 1; i <= n; i++)    // for文:ループ本体をn回実行
    ;                           // ループ本体は空文(何も行わない文)
putchar('+');                    // for文終了後に 1 回だけ実行される文

もちろん、for 文だけでなく while 文でも、このようなミスを犯さないように気をつける必要がある。

重要

for 文や while 文の ( ) の後ろに、誤って空文を置かないように注意しよう。

第 1 章で学習したように、文の末尾には原則としてセミコロン ; が必要である。

たとえば、『a = b』という代入式の後ろに ; を置いて『a = b;』とすると文になる。

このように、式の後ろにセミコロンを置いた文が、式文(expression statement)である(Fig.4-21)。

【Fig.4-21】式文の構文図

Fig4-21

この構文図は、式が省略可能であることを示している。すなわち、式がないセミコロン ; だけでも、立派な《式文》であり、それが空文の正体である。

繰返し文

本章で学習した do 文、while 文、for 文は、いずれもプログラムの流れを繰り返すための文だから、これらをまとめて繰返し文(iteration statement)と呼ぶ。

Example

【演習 4-19】 読み込んだ整数値の全約数を表示する List 4-17 を書きかえて、約数の表示が終了した後に、約数の個数を表示するプログラムを作成せよ。

【実行例】

整数値:4  
1  
2  
4  
約数は3個です。

4-4 多重ループ

繰返し文のループ本体の中に繰返し文が含まれていると、2 重、3 重、…の繰返しが行える。 このような繰返しが、本節で学習する多重ループである。

2重ループ

これまでのプログラムの繰返しは、単純な構造だった。実は、繰返しの中で繰返しを行うことができ、そのような繰返しは、入れ子の深さに応じて、2重ループ、3重ループ、…と呼ばれる。もちろん、その総称は多重ループである。

2重ループを使って、九九の表を表示するプログラムを作ろう。List 4-18 に示すのが、そのプログラムである。

List 4-18

// 九九の表を表示

#include <stdio.h>

int main(void)
{
    for (int i = 1; i <= 9; i++) {
        for (int j = 1; j <= 9; j++)
            printf("%3d", i * j);
        putchar('\n');    // 改行
    }

    return 0;
}

実行結果

  1  2  3  4  5  6  7  8  9  
  2  4  6  8 10 12 14 16 18  
  3  6  9 12 15 18 21 24 27  
  4  8 12 16 20 24 28 32 36  
  5 10 15 20 25 30 35 40 45  
  6 12 18 24 30 36 42 48 54  
  7 14 21 28 35 42 49 56 63  
  8 16 24 32 40 48 56 64 72  
  9 18 27 36 45 54 63 72 81  

この繰返しで、変数 i と j がどのように変化するのかを示したのが Fig.4-22 である。この図を見ながら理解していこう。

【Fig.4-22】変数の値の変化

Fig4-22

外側の for 文では、変数 i の値を 1 から 9 までインクリメントする。

これは、表の 1 行目、2 行目、…、9 行目に対応している。すなわち、表の縦方向の 9 回の繰返しである。

その各行で実行される内側の for 文は、j の値を 1 から 9 までインクリメントする。これは、各行における横方向の 9 回の繰返しである。

なお、内側の for 文で 9 個の数値を表示した後は、putchar 関数を使って \n を出力することで改行する。

2重ループで、次のような処理を行っていることが分かった。

  • i が 1 のとき: j を 1⇒9 とインクリメントしながら 1 * j を表示して改行。
  • i が 2 のとき: j を 1⇒9 とインクリメントしながら 2 * j を表示して改行。
  • i が 3 のとき: j を 1⇒9 とインクリメントしながら 3 * j を表示して改行。 … 中略 …
  • i が 9 のとき: j を 1⇒9 とインクリメントしながら 9 * j を表示して改行。

これで、1×1 から 9×9 までの、合計 81 個の数が九九の表として出力される。

Tip

書式指定 %3d によって、各値の出力を「(少なくとも) 3 桁」で行っていることに注意しよう。

多重ループにおける break 文の働き

本プログラムの 2 重ループを、次のように書きかえてみよう("chap04/list0418a.c")。 プログラムを実行すると、40 以下の値のみが表示される。

for (int i = 1; i <= 9; i++) {
    for (int j = 1; j <= 9; j++) {
        int seki = i * j;
        if (seki > 40)
            break;
        printf("%3d", seki);
    }
    putchar('\n');    // 改行
}

実行結果

  1  2  3  4  5  6  7  8  9  
  2  4  6  8 10 12 14 16 18  
  3  6  9 12 15 18 21 24 27  
  4  8 12 16 20 24 28 32 36  
  5 10 15 20 25 30 35 40  
  6 12 18 24 30 36  
  7 14 21 28 35  
  8 16 24 32 40  
  9 18 27 36  

赤い部分は break 文である。繰返し文中で break 文が実行されると、プログラムの流れが繰返し文を抜け出ることは、p.93 で学習した。

多重ループ内で break 文が実行されたときに抜け出るのは、その break 文を直接用いているほうの繰返し文である(この場合、変数 j で制御されている内側の for 文である)。外側の繰返し文(変数 i で制御されている for 文)までをも一気に抜け出ることはない。

本プログラムの break 文は、i と j の積が 40 を超えたときに、内側の for 文を強制的に抜け出して、現在出力中の行の表示を中断しているわけである。

Column 4-2 for文のループ本体での continue 文

Column 4-2

break 文と対照的な continue 文を実行すると、繰返しの残り部分がスキップされることを、while 文を例に、p.93 で学習した。

continue 文が、for 文のループ本体で実行された場合も同様であり、繰返しの残り部分がスキップされる。なお、残り部分がスキップされるといっても、for 文のC部は、ちゃんと実行される(この部分はスキップされない)。

なお、多重ループ中で continue 文が実行された場合、スキップの対象は、その continue 文を直接囲んでいる繰返し文である。

図形の描画

2重ループで記号文字を並べると、三角形や四角形などの各種図形の表示が行える。 List 4-19 に示すのは、縦横に * を並べて長方形を表示するプログラムである。

List 4-19

// 長方形を描画

#include <stdio.h>

int main(void)
{
    int height, width;

    puts("長方形を表示します。");
    printf("高さ:");    scanf("%d", &height);
    printf("横幅:");    scanf("%d", &width);

    for (int i = 1; i <= height; i++) {        // 長方形はheight行
        for (int j = 1; j <= width; j++)        // 各行にwidth個の'*'を表示
            putchar('*');
        putchar('\n');                        // 改行
    }

    return 0;
}

実行例

長方形を表示します。  
高さ:3  
横幅:5  
*****  
*****  
*****

height が 3 で width が 5 のときの長方形の表示を例に、変数 i と j の変化の様子を示したのが Fig.4-23 である。

【Fig.4-23】長方形描画における変数の変化

Fig4-23

height 行の各行に width 個の * を並べることで、長方形の表示を行っている。

次は、直角二等辺三角形の表示にチャレンジする。作るのは、次の二つのプログラムである。

  • List 4-20:左下側が直角の二等辺三角形を表示。
  • List 4-21:右下側が直角の二等辺三角形を表示。

各プログラムでの表示における変数 i と j の変化の様子を示したのが、Fig.4-24 である。いずれも、変数 len に読み込まれた段数(短辺)の三角形を表示する。

List 4-21 のプログラムは、少し複雑である。for 文の中に、2 個の for 文が入っている。 それぞれの for 文は、次のことを行う。

  • 緑色の for 文 … 空白文字 ' ' を表示するための繰返し(表示は len - i 個)。
  • 水色の for 文 … 記号文字 '*' を表示するための繰返し(表示は i 個)。

List 4-20

// 左下が直角の直角二等辺三角形を表示

#include <stdio.h>

int main(void)
{
    int len;

    puts("左下直角二等辺三角形を表示します。");
    printf("短辺:");
    scanf("%d", &len);

    for (int i = 1; i <= len; i++) {       // i行(i = 1, 2, ... len)
        for (int j = 1; j <= i; j++)       // 各行にi個の'*'を表示
            putchar('*');
        putchar('\n');                     // 改行
    }

    return 0;
}

実行例

左下直角二等辺三角形を表示します。  
短辺:5  
*  
**  
***  
****  
*****

List 4-21

// 右下が直角の直角二等辺三角形を表示

#include <stdio.h>

int main(void)
{
    int len;

    puts("右下直角二等辺三角形を表示します。");
    printf("短辺:");
    scanf("%d", &len);

    for (int i = 1; i <= len; i++) {           // i行(i = 1, 2, ... len)
        for (int j = 1; j <= len - i; j++)     // 各行にlen-i個の' 'を表示
            putchar(' ');
        for (int j = 1; j <= i; j++)          // 各行にi個の'*'を表示
            putchar('*');
        putchar('\n');                        // 改行
    }

    return 0;
}

実行例

右下直角二等辺三角形を表示します。  
短辺:5  
    *  
   **  
  ***  
 ****  
*****

【Fig.4-24】直角二等辺三角形描画における変数の値の変化

Fig4-24

多重ループ

これまでの多重ループは、for 文の中に for 文が入るものばかりだった。繰返し文は、do 文、while 文、for 文だから、これらはいずれも自由に入れ子にできる。

そのようなプログラム例を List 4-22 に示す。

List 4-22

// 読み込んだ整数の個数だけ * を連続表示(好きなだけ繰り返す)

#include <stdio.h>

int main(void)
{
    int retry;

    do {
        int no;
        do {
            printf("正の整数を入力せよ:");
            scanf("%d", &no);
            if (no <= 0)
                puts("\a正でない数を入力しないように。");
        } while (no <= 0);
        // noは0以上となっている

        for (int i = 1; i <= no; i++)
            putchar('*');
        putchar('\n');

        printf("もう一度? [Yes…0/No…9] :");
        scanf("%d", &retry);
    } while (retry == 0);

    return 0;
}

実行例

正の整数を入力せよ:-5  
正でない数を入力しないように。  
正の整数を入力せよ:17  
*****************  
もう一度? [Yes…0/No…9] :0  
正の整数を入力せよ:5  
*****  
もう一度? [Yes…0/No…9] :9

本プログラムは、do 文の中に、do 文と for 文が入っている構造である。これまで作成してきたいくつかのプログラムを組み合わせたものである。よく読んで理解しよう。

Column 4-3 名前と識別子

Column 4-3

識別子は、他と「識別」できるものでなければならない。未来世界を描いたSF映画では、すべての人間にID番号が割り振られていたりする。この番号は、個人特有のものであり、他の人間が同じ番号をもつということは絶対にありえない。

いわゆる名前も、各個人に与えられるものだが、他の人間が同じ名前を使わないという保証はない。すなわち、同姓同名の可能性がある。

プログラムの変数名に、同姓同名のものがあると不都合が生じる。したがって、専門用語としては識別子のほうがピッタリする。

Example

【演習 4-20】 右に示すように、縦横のタイトルが付いた九九の表を表示するプログラムを作成せよ。

Example

【演習 4-21】 右に示すように、読み込んだ整数を辺の長さとしてもつ正方形を表示するプログラムを作成せよ。

【実行例】

正方形を作ります。  
何段ですか:3  
***  
***  
***

Example

【演習 4-22】 List 4-19(p.104)のプログラムを書きかえて、横長の長方形を表示するプログラムを作成せよ。 ※二つの辺の長さを読み込んで、小さいほうを行数として、大きいほうを列数とすること。

【実行例】

横長の長方形を作ります。  
一辺(その1):7  
一辺(その2):3  
*******  
*******  
*******

Example

【演習 4-23】 List 4-20 および List 4-21(いずれも p.105)を書きかえて、左上側および右上側が直角となる直角二等辺三角形を表示するプログラムを作成せよ(それぞれ個別のプログラムとして作成すること)。

Example

【演習 4-24】 右に示すように、読み込んだ整数の段数をもつピラミッドを表示するプログラムを作成せよ。 ヒント:第i行目には(i - 1) 2 + 1 個の '' 記号を表示することになる。

【実行例】

ピラミッドを作ります。  
何段ですか:3  
  *  
 ***  
*****

Example

【演習 4-25】 右に示すように、読み込んだ整数の段数をもつ下向き数字ピラミッドを表示するプログラムを作成せよ。 第i行目には i % 10 によって得られる数字を表示すること。

【実行例】

下向き数字ピラミッドを作ります。  
何段ですか:3  
11111  
222  
3

4-5 プログラムの要素と書式

本節では、プログラムを構成する各要素(キーワードや演算子など)と、プログラム表記の書式について学習する。

キーワード

if や else のような語句には、特別な意味が与えられている。このような語句はキーワード(keyword)と呼ばれ、変数名などに利用することはできない。

キーワードは、Table 4-5 に示す 37 個がある。

Table 4-5 キーワード

auto     break    case     char      const     continue   default    do
double   else     enum     extern    float     for        goto       if
inline   int      long     register  restrict  return     short      signed
sizeof   static   struct   switch    typedef   union      unsigned   void
volatile while    _Bool    _Complex  _Imaginary

演算子

これまで、+ や - など、数多くの演算子(operator)を学習してきた。すべての演算子の一覧表は、p.221 に示している。

注意

複数文字で構成される>= や+= などの演算子の途中に空白を入れることはできない(すなわち、> = や+ = などとすることはできない)。

識別子

識別子(identifier)とは、変数、関数(第 6 章)、構造体(第 12 章)などの名前のことである(Column 4-3:p.106)。

その構文は、Fig.4-25 のようになっている。すなわち、

  • 先頭文字は必ず非数字
  • 2文字目以降は非数字または数字である。

【Fig.4-25】識別子・非数字・数字の構文図

Fig4-23

なお、ここでの非数字とは、大文字および小文字のアルファベットに下線_を加えたものである。 大文字と小文字は区別されるため、ABC、abc、aBc は別の識別子として扱われる。

Tip

識別子の文字として、非数字と数字以外に、国際文字名(universal character name)と呼ばれる文字も利用できる。

次に示すのは、識別子として適切な(変数名や関数名などに利用できる)例である。

○ x1   a   __y   abc_def   max_of_group   xyz   Ax3   If   iF   IF   if3

識別子として誤った例を示す。

× if   123   98pc   abc$   abc$xyz   abc@def

注意

下線で始まる識別子(たとえば_x や_comp)と、アルファベットの大文字が 1 字だけの識別子は、処理系が内部的に利用する可能性があるため、識別子として利用してはいけない。

区切り子

識別子やキーワードなど、各語句のあいだには、基本的には空白が必要である。たとえば、『case 2:』の case と 2 をくっつけて『case2:』とすることはできない。

ただし、区切り子(delimiter)が置かれていれば、その前後の空白は不要である。そのため、区切り子 ( ) を使えば、空白を入れずに『case(2):』と記述できる。

主要な区切り子を Table 4-6 に示す。

Table 4-6 主要な区切り子

[   ]   (   )   {   }   *   ,   :   =   ;   ...   #

定数と文字列リテラル

文字定数、整数定数、浮動小数点定数、文字列リテラルなども、プログラムを構成する要素の一つである。

自由形式

List 4-23 は、九九の表を表示する List 4-18(p.102)のプログラムと、本質的には同じものであり、実行すると同じ結果が得られる。

List 4-23

/* 九九の表を
      表示
      */
#include <stdio.h>

int main(
                    void) {

for(int i=
            1;i<=9;i
++)  { for(int j=1;j
<=9;j
++)  printf("%3d",
i *
j); putchar('\n');
// 改行
}return    0
; }

実行結果

  1  2  3  4  5  6  7  8  9  
  2  4  6  8 10 12 14 16 18  
  3  6  9 12 15 18 21 24 27  
  4  8 12 16 20 24 28 32 36  
  5 10 15 20 25 30 35 40 45  
  6 12 18 24 30 36 42 48 54  
  7 14 21 28 35 42 49 56 63  
  8 16 24 32 40 48 56 64 72  
  9 18 27 36 45 54 63 72 81  

C言語では、原則として、自由な位置にプログラムを記述できる。一部のプログラミング言語のように、プログラムの各行を、特定の桁位置を先頭に記述せねばならない、あるいは、一つの文を一つの行に収めなければならないといった制限はない。

すなわち、自由形式(free formatted)の記述が許される。

ここに示したプログラムは、思いっきり自由に(?)記述した例だといえるだろう。もっとも、いくら自由であるとはいっても、それなりの制限はある。

① 単語の途中に空白類文字を入れてはいけない

int や return などのキーワード、n1 や a2 などの識別子、+= や ++ などの演算子は、一つの「単語」である。これらの途中に空白類文字(空白、タブ、改行など)を入れて、

× ret
   urn

などとしてはいけない。

② 前処理指令の途中で改行してはいけない

原則としては自由形式である C 言語も、#include などのように先頭が # 文字である前処理指令は、特別扱いである。これらは原則として、1 行で記述する必要があるので、次のようなものは不可である。

× #include
   <stdio.h>

③ 文字列リテラルの途中や文字定数の途中で改行してはいけない

二重引用符で囲んだ文字列リテラル "..." も、一種の単語である。次のように、途中に改行文字などを入れてはいけない。

× puts("昔々あるところにお爺さんとお婆さんが住んでいました。
       お爺さんはお婆さんをこよなく愛していました");

隣接した文字列リテラルの連結

空白類文字および注釈をはさんで隣接している文字列リテラルは、ひとまとめのものとみなされる。たとえば、"ABC" "DEF" は、連結されて "ABCDEF" となる。

この規則を応用すると、長い文字列リテラルを読みやすく記述できる。上のコードは、次のように記述できる。

puts("昔々あるところにお爺さんとお婆さんが住んでいました。"    // 次行に続く
     "お爺さんはお婆さんをこよなく愛していました。");

インデント

オリジナルの九九のプログラムの主要部分を抜粋したのが、Fig.4-26 である。プログラム中の各行が、4 桁ごとに右に深くなっていることが分かるだろう。複合文 { } は、まとまった宣言と文をくくったものであり、いわば日本語での《段落》のようなものである。

【Fig.4-26】ソースプログラム中のインデント]

Fig4-26

段落中の記述を、数桁ずつ右にずらして書くと、プログラムの構造がつかみやすくなる。そのため余白のことをインデント(段付け/字下げ)といい、インデントを用いて記述することをインデンテーションと呼ぶ。

本書のプログラムは、4 桁ごとのインデントを与えて表記している。

Tip

インデントは、タブキーとスペースキーのいずれでもタイプできる。ただし、エディタやその設定によっては、タブをタイプした文字と、保存したソースファイル上の文字とが一致しない(保存時に置換される)ことがある。

まとめ

  • do 文、while 文、for 文の総称が、繰返し文である。いずれの繰返し文においても、制御式を評価した値が真(非0)であればループ本体が実行される。なお、繰返し文のループ本体に繰返し文が含まれてもよい。そのような構造の繰返し文は、多重ループである。

  • 後判定繰返しは、do 文で実現できる。ループ本体は少なくとも 1 回は必ず実行される。ループ本体は、単一の文であってもブロックとしたほうが読みやすくなる。

  • 前判定繰返しは、while 文と for 文で実現できる。ループ本体は 1 回も実行されない可能性がある。単一のカウンタ用変数で制御する繰返しは、for 文を使えば簡潔に実現できる。

  • 繰返し文中の break 文は、その繰返し文の実行を中断させる。繰返し文中の continue 文は、その繰返し文内のループ本体の残り部分の実行をスキップさせる。

  • 増分演算子 ++ と減分演算子 -- はオペランドの値を、インクリメント(一つ増やす)/デクリメント(一つ減らす)する。後置(前置)の増分演算子/減分演算子を適用した式を評価して得られるのは、インクリメント/デクリメントを行う前(後)の値である。

  • 式の後ろに ; を置いた文が式文であり、式が省略された ; だけの式文が空文である。

  • 複合文に特有の変数は、その複合文の中で宣言して利用する。

  • 二つの条件の否定をとって、論理積・論理和を入れかえた式の否定は、もとの条件と同じである。これは、ド・モルガンの法則として知られている。

  • 単一の文字は、単一引用符 ' で文字を囲む '*' 形式の文字定数で表現できる。単一文字の表示は、putchar 関数で行える。

  • 複合代入演算子は、演算と代入の両方を行う演算子である。演算と代入を二つの演算子で行うのに比べると、簡潔に記述できることや、左オペランドの評価が 1 回しか行われないことなどの特徴がある。

  • if や else のような特別な意味が与えられた語句はキーワードであり、変数や関数などに与える名前は識別子である。

  • 区切り子は、キーワードや識別子など単語の区切りとなる記号である。

  • 空白類文字(空白、タブ、改行など)と注釈をはさんで隣接した文字列リテラルは、自動的に連結される。

  • C 言語のプログラムは、自由形式記述である。適切なインデントを与えて、読みやすくすべきである。

Fig4-27

Fig4-28

演習問題

  1. ピラミッド表示
  2. ダイヤモンドパターン表示
  3. 複雑な数字パターン表示
  4. 約数の個数計算

演習問題5-1:ピラミッド表示

問題の説明

ユーザから入力された高さに基づいて、「」記号でピラミッドを表示するプログラムを作成してください。ピラミッドは中央に位置し、最上段は1つの「」で、下に向かって各行2つずつ増えていきます。

期待される結果

入力例1:

ピラミッドの高さを入力してください: 3
出力例1:
  *
 ***
*****

入力例2:

ピラミッドの高さを入力してください: 5
出力例2:
    *
   ***
  *****
 *******
*********

ヒント

  1. 2重ループを使用して、空白と「*」記号を適切に配置します
  2. 各行の「」の数は、行番号iに対して「2i-1」という計算式で求められます
  3. 各行の前に表示する空白の数は、「height-i」個です
  4. putchar関数を使うと、1文字ずつ効率的に出力できます

演習問題5-2:ダイヤモンドパターン表示

問題の説明

ユーザから奇数の高さを入力してもらい、「*」記号でダイヤモンド形のパターンを表示するプログラムを作成してください。ダイヤモンドは中央に位置し、上半分はピラミッド形状で、下半分は逆ピラミッド形状になります。

期待される結果

入力例1:

ダイヤモンドの高さを入力してください(奇数): 5
出力例1:
  *
 ***
*****
 ***
  *

入力例2:

ダイヤモンドの高さを入力してください(奇数): 7
出力例2:
   *
  ***
 *****
*******
 *****
  ***
   *

ヒント

  • ダイヤモンドパターンは、ピラミッドとその逆パターンを組み合わせたものです
  • 入力が偶数の場合は、奇数に調整すると綺麗なダイヤモンドが作れます
  • パターンを分析するには、次の手順で考えてみましょう:
  • まず高さの中央行を特定します(例:高さ7なら4行目が中央)
  • 中央行を境に、上半分と下半分で計算式が変わることに注意してください
  • 各行の表示は「空白」と「*」の2種類の文字で構成されています
  • 各行の「」の数は、上半分では「2i-1」、下半分では「2*(height-i+1)-1」となります
  • 各行の空白の数は、上半分では「mid-i」、下半分では「i-mid」となります

  • 紙に図を描いて、例えば高さ5のダイヤモンドを手作業で書いてみると理解しやすくなります:

    行番号 | 空白数 | *の数 | 表示
    ------+-------+------+------
      1   |   2   |   1  |  "  *  "
      2   |   1   |   3  |  " *** "
      3   |   0   |   5  |  "*****"
      4   |   1   |   3  |  " *** "
      5   |   2   |   1  |  "  *  "
    

  • 特に重要なのは、中央行(mid)の計算で、これは「height/2+1」で求められます

  • 条件分岐(if-else)を使って、上半分と下半分で異なる計算式を適用する方法を考えましょう
  • プログラムの修正・改良として、高さが必ず奇数になるように入力を調整する処理を加えておくと良いでしょう

演習問題5-3:複雑な数字パターン表示

問題の説明

ユーザから行数を入力してもらい、以下のような特殊な数字パターンを表示するプログラムを作成してください。パターンは各行が中央に位置し、各行の数字は行番号から始まり、中央で最大値に達した後、減少します。

期待される結果

入力例1:

行数を入力してください: 3
出力例1:
  1
 232
34543

入力例2:

行数を入力してください: 5
出力例2:
    1
   232
  34543
 4567654
567898765

ヒント

  • このパターンは3つのパートに分けて考えると実装しやすいです:
  • 空白部分: 各行の前に配置する空白
  • 左側の増加する数字: 行番号から始まり中央値まで増加
  • 右側の減少する数字: 中央値から行番号まで減少

  • 具体的なパターンの分析:

  • 各行の左側の数字は、行番号(i)から始まり、(2*i-1)まで増加します
  • 各行の右側の数字は、(2*i-2)から行番号(i)まで減少します
  • 行番号が増えるごとに、空白は1つずつ減り、数字の個数は2つずつ増えます

  • 例えば、行数が3の場合のパターンを分解すると:

    行番号 | 空白数 | 左側の数字 | 右側の数字 | 表示
    ------+-------+----------+----------+------
      1   |   2   |     1    |    なし   |  "  1  "
      2   |   1   |    2,3    |     2    |  " 232 "
      3   |   0   |   3,4,5   |   4,3    |  "34543"
    

  • 重要なポイント:

  • 各行の空白数は「rows-i」で計算できます
  • 数字が10以上になる場合は「%10」演算子を使って1桁に制限します(例:10→0, 11→1)
  • 左側の数字を表示するループと右側の数字を表示するループを別々に実装します
  • 各ループの境界条件(開始値と終了値)を正確に設定することが重要です

  • デバッグのコツ:

  • まず空白だけを表示するプログラムを作り、次に数字を追加していくと段階的に開発できます
  • 小さな入力値(例:行数=3)でテストしてから、より大きな値でテストしましょう
  • 紙に描いて規則性を見つけることで、複雑なパターンも理解しやすくなります

演習問題5-4:約数の個数計算

問題の説明

1から100までの整数の中で、約数の個数が最も多い数とその約数を表示するプログラムを作成してください。約数とは、ある数を余りなく割り切ることができる数のことです。

期待される結果

出力例:

約数の個数計算(1-100)
計算中...
1から100までの整数で最も約数が多いのは 96 で、12個の約数があります。
96の約数: 1 2 3 4 6 8 12 16 24 32 48 96

ヒント

  • 約数の個数を求める基本的な考え方:
  • 1からその数自身までの各整数で割り算を行い、余りが0のものをカウントします
  • たとえば、12の約数は1, 2, 3, 4, 6, 12の6個です(12を各数で割った余りが0)

  • 最大値の追跡方法:

  • 変数max_divisorsに現在見つかっている最大の約数の個数を保存します
  • 変数number_with_maxに最大約数を持つ数値を保存します
  • 新しい数の約数を数えるたびに、現在の最大値と比較し、必要に応じて更新します

    if (current_count > max_divisors) {
        max_divisors = current_count;
        number_with_max = current_number;
    }
    

  • 効率的な約数計算のテクニック:

  • 基本的な方法:1からnumまで順に割り算して余りが0かチェック(時間複雑度O(n))
  • 改良された方法:1から√numまでループを回し、iが約数ならnum/iも約数として数える(時間複雑度O(√n))

    int count = 0;
    for (int i = 1; i * i <= num; i++) {
        if (num % i == 0) {
            count++; // iは約数
            if (i != num / i) // 重複を避ける(完全平方数の場合)
                count++; // num/iも約数
        }
    }
    

  • 実装上の工夫:

  • 約数を表示する際は、もう一度同じ条件でループを回す必要があります
  • 1と100の間で最も約数が多い数は複数ある可能性があります(同点の場合は最大の数を選択する)