ようへいの日々精進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 の使い方を勉強せねば...

2018 年 10 月 17 日 (水)

ジョギング

  • 山王公園往復
  • 懸垂 5 回
  • 公園に着くやいなや腹痛に襲われ, 公園のトイレに駆け込むもトイレットペーパーの配備が無くてかなり焦った (注意が必要)
  • 結局, 近くのローソンに駆け込み事なきを得た (今度, ここのローソンで買い物しよう)

日課

  • おやすみ

fukuoka.rb #110

  • 久しぶりの参加
  • グルーブノーツさんに来ている海外からのインターン生も一緒に参加ということで, 自己紹介英語で...

寒い感じになったが, やっぱり英語は必要だよなって. ジョークをサラッと英語で言えるようになりたい.

で, もくもくの成果としては, 以前に rspec-ftp を弄らせてもらったので, その勢いで infrataster のプラグインを作ってみた.

rspec-ftp とはちょっとアプローチが違うけど, FTP のコマンド操作とその結果をテスト出来するアプローチで, こちらの方が汎用性高そうな気がするので, テストを書いたりしてちゃんと育てていこうと思う.

2018 年 10 月 16 日 (火)

ジョギング

  • 山王公園往復
  • 右足, やっぱり変な感じがある
  • 懸垂 3 回 + 2 回

日課

  • (腕立て x 50 + 腹筋 x 50) x 3

パスタ

  • 久しぶりに作った
  • トマトピューレを使ってみたけど仕上がりがパサパサで残念な感じになった

2018 年 10 月 15 日 (月)

ジョギング

  • 山王公園往復
  • 右足, やっぱり変な感じがある
  • 懸垂 5 回

日課

  • (腕立て x 50 + 腹筋 x 50) x 3

マッサージ

  • お昼休みを利用して近所のマッサージ屋さんへ
  • 右腰から右足をちゃんと施術してもらいたかったけど, 思ったほど解れていない感じ...残念.

今日のトゥウィート

YANGMAN ではなくて, YOUNGMAN だったのに...ツイートして少し経ってから気づいた...orz

2018 年 10 月 14 日 (日)

ジョギング

  • 山王公園往復
  • 右腰, 右足, やっぱり変な感じがある
  • 懸垂 3 + 2 + 2 回, 連続で 5 回が出来るようになりたい

日課

  • お休み

モスバーガー

  • 久しぶりにモスバーガーを食べた
  • ポテイトのセットでめっちゃ美味しかったし, ハンバーガーの食べ方も上手くなったはず

ダイニングチェアが届いた

シャレオツ(?)なダイニングチェアを導入した。

シャレオツな感じで気に入っている.

2018 年 10 月 13 日 (土)

ジョギング

  • おやすみ

日課

  • おやすみ

ミニツーリング

ミニディーラー企画のツーリング大会に参加した. 目的地は阿蘇. 初めてツーリングに参加してみたけど, 駐車場に同じような車がズラーッと並ぶ様は圧巻で興奮した.

ミニだらけ。#mini #minicooper #ミニだらけ

ツーリング大会は現地解散で, その後, そのままミニで大観峰まで登った.

大観峰で大感動。#mini #ミニで見に来た

天気も良くて空も澄んでいたので大感動しかなかった.

2018 年 10 月 12 日 (金)

ジョギング

  • 山王公園往復
  • 右足, やっぱり変な感じがある
  • 懸垂 5 回

日課

  • (腕立て x 50 + 腹筋 x 50) x 3

夕飯は麻婆鍋を作った

丸美屋の麻婆豆腐の素を使った鍋. そこそこ美味しかったけど, パンチのある辛さと旨味がイマイチだった.

今日のトゥウィート

JAWS-UG 福岡もくもく会, 13 回目を迎えた. 100 回目はいつになるだろう.

2018 年 10 月 11 日 (木)

ジョギング

  • 山王公園往復
  • 懸垂 5 回イケた

日課

  • おやすみ

JAWS-UG 福岡もくもく会

  • みんなで集まってワイワイ技術的な話が出来るのは楽しい
  • SAP の勉強, かなり難しい...

夕飯は

とんとんで湯豆腐を食べた.

湯豆腐食べてる。

2018 年 10 月 10 日 (水)

ジョギング

  • 山王公園往復
  • 懸垂 4 回の壁が超えられない

日課

  • (腕立て x 50 + 腹筋 x 50) x 3

今朝の出来事

同じマンション住む幼い兄弟が自宅鍵を無くしたというので一緒に探してあげた. 自宅の鍵が無くなるという致命的な状況にも関わらず, 「俺と弟, そしておっちゃん (間違いなく自分のことだと思う) がいればきっと見つかるよね」って, 常にポジティブだったのが印象的だったし, なんだか大人の自分が勇気づけられた気がした. いろいろおもしろいシチュエーションもあったけど, これはまたの機会に.

マルチななんとか

お昼すぎから外出してカフェで作業. どうしても横の人たちの声が耳に入ってきて煩いなあと思っていたら, その内容がマルチなアレなアレの話っぽくて世の中怖いなあと思った次第. また, お金はコツコツ貯めて行きたいと思う.

2018 年 10 月 09 日 (火)

ジョギング

  • 山王公園往復
  • 体調がすごく悪いので 30 分くらいで引き上げた

日課

  • おやすみ

SRE 本輪読会

  • 隔週でやっている SRE 本輪読会に参加
  • 福岡の色んな企業のエンジニアが集まるので交わされる会話一つ一つが勉強になる

ビーフシチュー

  • 奥さんお得意のビーフシチューを夕飯に
  • なんかいつもよりも美味しかった

今日のトゥウィート

輪読会の後, 独りで「とんとん」に. とろろ納豆と地鶏のたたきを食べる.

どうしたんだろう. 体中がずっと痛い.