ようへいの日々精進XP

よかろうもん

アルゴリズムとプログラミング 「第 3 章 ループの仕組み」の予習とまとめ

これは

放送大学教養学部の「アルゴリズムとプログラミング」という授業で使われる「アルゴリズムとプログラミング」という教材書籍を自分なりにまとめたものです.

第 3 章では, C 言語のループ処理に関連して for 文や while 文の基本的な内容について触れられています. 課題で gdb を使って繰り返し処理をステップ実行してみました.

尚, 本まとめについては, 以下の Github リポジトリで管理しており, 加筆修正はリポジトリのみ行います.

github.com

1. for ループ

1.1 for 文の形式

for 文は, 初期化 (initialization), 条件文 (condition), 反復文 (counter 又は loop), 本文 (body statement) を記述する.

for ( 初期文; 条件文; 反復文 )
  本文;

以下, サンプル.

/* code: ex3-1.c */
#include <stdio.h>

int main()
{
  int i;
  for (i = 0; i < 10; i++)
    printf ("%d\n", i);

  return 0;
}
  • このプログラムは 0 から 0 までの数字を表示する
  • 初期文は i = 0i という変数の値に 0 が代入されて初期化される
  • 条件文では i < 10 という式があり, 変数 i が 10 より小さい値ならば, printf 関数によって標準出力に変数 i の値が表示される
  • 反復文では i++ という式があり, 変数に値が 1 が加算される
  • for 文で複数の命令を繰り返したい場合には波括弧 {} を利用したブロック文を用いる

上記のコードを実行してみる.

$ gcc ex3-1.c -o ex3-1
$ ./ex3-1
0
1
2
3
4
5
6
7
8
9

以下, % を利用したモジュロ演算のサンプル.

/* code: ex3-2.c */
#include <stdio.h>

int main()
{
  int i;
  for (i = 0; i < 10; i++) {
    printf ("%d", i);
    if (0 != (i % 2))
      printf (" :odd\n");
    else
      printf (" :even\n");
  }

  return 0;
}

上記のコードを実行してみる.

$ gcc ex3-2.c -o ex3-2
$ ./ex3-2

0 :even
1 :odd
2 :even
3 :odd
4 :even
5 :odd
6 :even
7 :odd
8 :even
9 :odd

1.2 for 文の入れ子型ループ

以下のコードは for 文の中にさらに別の for 文のループを含む例. このようなループは入れ子型ループ (nested loop) と呼ばれる.

/* code: ex3-3.c */
#include <stdio.h>

int main()
{
  int i, j;
  for (i = 1; i < 10; i++) {
    for (j = 1; j < 10; j++) {
      printf ("%02d ", i * j);
    }
    printf ("\n");
  }
  return 0;
}

上記のコードを実行してみる.

$ gcc ex3-3.c -o ex3-3
$ ./ex3-3
01 02 03 04 05 06 07 08 09
02 04 06 08 10 12 14 16 18
03 06 09 12 15 18 21 24 27
04 08 12 16 20 24 28 32 36
05 10 15 20 25 30 35 40 45
06 12 18 24 30 36 42 48 54
07 14 21 28 35 42 49 56 63
08 16 24 32 40 48 56 64 72
09 18 27 36 45 54 63 72 81

外側のループでは for 文により変数 i が 1 〜 9 まで増加する, 同様に内側のループでは for 文によって変数 j が 1 〜 9 まで増加する為, 上記の結果は九九表のような表示結果となる.

1.3 for 文の無限ループ

以下のコードは for 文を使った無限ループ (infinite loop) の例. 初期文, 条件文, 反復文に何も記述されていない (又は, 条件文が常に真となる条件) 為, 本文が無限に実行される.

/* code: ex3-4.c */
#include <stdio.h>

int main()
{
  int i;
   
  i = 0;
  for (;;) {
    printf ("%d ", i);
    i++;
  }
  return 0;
}

上記のコードを実行してみる. (今回からコンパイル環境は gcc version 6.3.0 20170516 (Debian 6.3.0-18+deb9u1) でやっていく.)

$ gcc ex3-4.c -o ex3-4 -g3
$ ./ex3-4
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17..... 一瞬で大変なことになる.

放っておいても, ループ内でメモリを消費するようなコードであれば, いずれエラーで停止する.

2. while 文

2.1 while 文の形式

while 文は以下のような形式となる.

while ( 条件文 )
  本文;

以下, コード例. 変数 i に初期値として 0 が代入される. while 文によって条件文 i < 10 が真の間は本文の printf 関数が実行され, 0 から 9 までの値が標準出力で表示される.

/* code: ex3-5.c */
#include <stdio.h>

int main()
{
  int i;

  i = 0;
  while (i < 10) {
    printf ("%d ", i++);
  }
  printf ("\n");
  return 0;
}

このコードをコンパイルして実行してみる.

$ gcc ex3-5.c -o ex3-5 -g3
$ ./ex3-5
0 1 2 3 4 5 6 7 8 9

C 言語の場合, 整数である変数 i の値は, 宣言した場所で自動的に 0 で初期化される為, i = 0; 自体は不要な処理に見えるが, 初期の Pascal 等のプラグラミング言語では 0 等での値での変数初期化保証していない場合もあるので, 変数を利用する前に初期化をすることが望ましいとされている.

コード ex3-5.c は以下のように書き換えることも出来る.

/* code: ex3-6.c */
#include <stdio.h>

int main()
{
  int i;

  i = 0;
  while (i < 10) {
    printf ("%d ", i);
    i++;
  }
  printf ("\n");
  return 0;
}

このコードをコンパイルして実行してみる.

$ gcc ex3-6.c -o ex3-6 -g3
$ ./ex3-6
0 1 2 3 4 5 6 7 8 9

2.2 do-while 文の形式

do-while 文の形式は以下の通り.

do
  本文;
while ( 条件文 );

while 文と異なるのは, 本文が先に実行され, その後に条件文が評価されることで, 本番が少なくとも 1 回は実行される必要があるプログラムを作成する時に便利.

以下, コード例.

/* code: ex3-7.c */
#include <stdio.h>

int main()
{
  int i;

  i = 0;
  do {
    printf ("%d ", i);
    i++;
  } while (i < 10);
  printf ("\n");
  
  return 0;
}

このコードをコンパイルして実行してみる.

$ gcc ex3-7.c -o ex3-7 -g3
$ ./ex3-7
0 1 2 3 4 5 6 7 8 9

2.3 while 文の入れ子ループ

for 文同様に while 文でも入れ子ループは書くことは出来る. 以下のコードは ex3-3.c を while 文で書き換えたもの.

/* code: ex3-8.c */
#include <stdio.h>

int main()
{
  int i, j;
  
  i = 1;
  while (i < 10) {
    j = 1;
    while (j < 10) {
      printf ("%02d ", i * j);
      j++;
    }
    printf ("\n");
    i++;
  }
  return 0;
}

このコードをコンパイルして実行してみる.

$ gcc ex3-8.c -o ex3-8 -g3
$ ./ex3-8
01 02 03 04 05 06 07 08 09
02 04 06 08 10 12 14 16 18
03 06 09 12 15 18 21 24 27
04 08 12 16 20 24 28 32 36
05 10 15 20 25 30 35 40 45
06 12 18 24 30 36 42 48 54
07 14 21 28 35 42 49 56 63
08 16 24 32 40 48 56 64 72
09 18 27 36 45 54 63 72 81

2.4 while 文の無限ループ

以下のコードは while 文による無限ループの例. 条件文が 1 である為, 常に真となり無限ループとなる.

/* code: ex3-9.c */
#include <stdio.h>

int main()
{
  int i;
   
  i = 0;
  while (1) {
    printf ("%d ", i);
    i++;
  }
  return 0;
}

このコードをコンパイルして実行してみる.

$ gcc ex3-9.c -o ex3-9 -g3
$ ./ex3-9
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17..... 一瞬で大変なことになる.

2.5 break 文と continue 文

break 文を利用すると, break 文を使用した場所でループを終了させることが出来る. 以下, コード例.

/* code: ex3-10.c */
#include <stdio.h>

int main()
{
  int i;
   
  i = 0;
  while (1) {
    printf ("%d ", i);
    if (i ==5) {
      i = 0;
      break;
    }
    i++;
  }
  printf ("\n");
  return 0;
}

このコードをコンパイルして実行してみる.

$ gcc ex3-10.c -o ex3-10 -g3
$ ./ex3-10
0 1 2 3 4 5

このコードでは while 文により無限ループとなり変数 i が増加していくが, 変数 i の値が 5 になったところで, 変数 i に 0 が代入され break 文が実行される. これによって, while 文から脱してプログラムが終了する.

continue 文は, continue 文が実行された場所でループ処理が中断し, ループの条件文が評価され, 条件文が真であればループ本文の先頭の命令から実行が行われる. 以下, コード例.

/* code: ex3-11.c */
#include <stdio.h>

int main()
{
  int i;
   
  i = 0;
  while (1) {
    printf ("%d ", i);
    if (i == 5) {
      i = 0;
      continue;
    }
    i++;
  }
  printf ("\n");
  return 0;
}

このコードをコンパイルして実行してみる.

$ gcc ex3-11.c -o ex3-11 -g3
$ ./ex3-11
0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3... 0 1 2 3 4 5 が繰り返される

このコードでは while 文により無限ループとなり変数 i が増加していくが, 変数 i の値が 5 になったところで, 変数 i に 0 が代入されて continue 文が実行される. ここでループ処理が中断してループの条件文が評価される. 条件文は 1 で常に真であることから, 再びループ本文の先頭から命令が実行される為, 0 1 2 3 4 5 が繰り返し表示される.

3. for 文と while 文の変換

以下, for 文と while 文の形式を比較したもの.

// for 文
for ( 初期文; 条件文; 反復文) {
  本文;
}

// while 文
初期文;
while ( 条件文 ) {
  本文;
  反復文;
}

上記ように, 初期文, 条件文, 反復文の配置を変えることにより for 文, while 文の相互変換が可能となる.

一般的には, ループの処理に入る前に本文の命令を何回繰り返すかが明らかになっているような場合には for 文を, ループの終了条件が本文の命令で決定され, ループの終了の条件が予期しにくい場合には while 文が使われることが多い.

演習問題

問 3.1

以下のコード (コード 3.1) を変更し, 0 から 99 までの数字を表示するようにしなさい.

/* code: ex3-1.c */
#include <stdio.h>

int main()
{
  int i;
  for (i = 0; i < 10; i++)
    printf ("%d ", i);

  return 0;
}

以下, 解答例.

/* code: q3-1.c */
#include <stdio.h>

int main()
{
  int i;
  for (i = 0; i < 100; i++) {
    printf ("%d ", i);
  }

  return 0;
}

コンパイルして実行してみる.

$ gcc q3-1.c -o q3-1 -g3
$ ./q3-1
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

更にこれを while 文で書き換えてみる.

/* code: q3-1.c */
#include <stdio.h>

int main()
{
  int i;
  
  i = 0;
  while (i < 100) {
    printf ("%d ", i);
    i++;
  }

  return 0;
}

一応, これもコンパイルして実行してみる.

$ gcc q3-1.c -o q3-1 -g3
$ ./q3-1
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

問 3.2

問 3.1 の q3-1.c を変更して 0 から 9 までの数字を降順に表示するようにしなさい.

以下, 解答例.

/* code: q3-2.c */
#include <stdio.h>

int main()
{
  int i;
  for (i = 9; i >= 0; i--) {
    printf ("%d ", i);
  }

  return 0;
}

これをコンパイルして実行してみる.

$ gcc q3-2.c -o q3-2 -g3
$ ./q3-2
9 8 7 6 5 4 3 2 1 0

先程と同様に while 文でも書いてみる.

/* code: q3-2.c */
#include <stdio.h>

int main()
{
  int i;
  i = 9;
  while (i >= 0) {
    printf ("%d ", i);
    i--;
  }

  return 0;
}

一応, コンパイルして実行してみる.

$ gcc q3-2.c -o q3-2 -g3
$ ./q3-2
9 8 7 6 5 4 3 2 1 0

問 3.3

問 3.1 の q3-1.c を for 文を使ったコードから while 文を使ったコードに変更しなさい.

以下, 解答例.

/* code: q3-3.c */
#include <stdio.h>

int main()
{
  int i;
  
  i = 0;
  while (i < 100) {
    printf ("%d ", i);
    i++;
  }

  return 0;
}

問 3.4

以下のコードの実行した時の出力内容を答えなさい.

/* code: q3-4.c */
#include <stdio.h>

int main()
{
  int i, j, k;
  
  for (i = 0; i < 2; i++) {
    for (j = 0; j < 2; j++) {
      for (k = 0; k < 2; k++) {
        printf ("%d %d %d", i, j, k);
        printf ("\n");
      }
    }
  }
  return 0;
}

普通にコンパイルして実行してみる.

$ gcc q3-4.c -o q3-4 -g3
$ ./q3-4
0 0 0
0 0 1
0 1 0
0 1 1
1 0 0
1 0 1
1 1 0
1 1 1

普通にコンパイルして実行するのは少し学びが無いので gdb でステップ実行してみる.

$ gdb ./q3-4
...
Reading symbols from ./q3-4...done.
(gdb) b main
Breakpoint 1 at 0x708: file q3-4.c, line 8.
(gdb) run
Starting program: /work/q3-4

Breakpoint 1, main () at q3-4.c:8
8         for (i = 0; i < 2; i++) {
(gdb) n
9           for (j = 0; j < 2; j++) {
(gdb) n
10            for (k = 0; k < 2; k++) {
(gdb) n
11              printf ("%d %d %d", i, j, k);
(gdb) n
12              printf ("\n");
(gdb) n
0 0 0
10            for (k = 0; k < 2; k++) {
(gdb) n
11              printf ("%d %d %d", i, j, k);
(gdb) n
12              printf ("\n");
(gdb) n
0 0 1
10            for (k = 0; k < 2; k++) {
(gdb) n
9           for (j = 0; j < 2; j++) {
(gdb) n
10            for (k = 0; k < 2; k++) {
(gdb) n
11              printf ("%d %d %d", i, j, k);
(gdb) n
12              printf ("\n");
(gdb) n
0 1 0
10            for (k = 0; k < 2; k++) {
(gdb)
...

おお.

問 3.5

以下のコードの実行した時の出力内容を答えなさい.

/* code: q3-5.c */
#include <stdio.h>

int main()
{
  int i, j, k;
  
  for (i = 0; i < 2; i++) {
    for (j = 0; j < 2; j++) {
      for (k = 0; k < 2; k++) {
        printf ("%d ", i * j + k);
      }
    }
  }
  return 0;
}

普通にコンパイルして実行してみる.

$ gcc q3-5.c -o q3-5 -g3
$ ./q3-5
0 1 0 1 0 1 1 2

gdb でステップ実行してみる. (上記のコードに改行 ("\n") を追加している)

(gdb) b main
Breakpoint 1 at 0x708: file q3-5.c, line 8.
(gdb) run
Starting program: /work/q3-5

Breakpoint 1, main () at q3-5.c:8
8         for (i = 0; i < 2; i++) {
(gdb) n
9           for (j = 0; j < 2; j++) {
(gdb) n
10            for (k = 0; k < 2; k++) {
(gdb) n
11              printf ("%d ", i * j + k);
(gdb) n
12              printf ("\n");
(gdb) n
0
10            for (k = 0; k < 2; k++) {
(gdb) n
11              printf ("%d ", i * j + k);
(gdb) n
12              printf ("\n");
(gdb) n
1
10            for (k = 0; k < 2; k++) {
...

ふむふむ.

以上

  • for と while の相互変換, 雰囲気掴めた
  • gdb の使い方を勉強せねば...