2002年度後期 IT教育基礎論演習B

第5回: プログラミング基礎(1) 〜 C言語(2)

■ 概要

   条件分岐や繰り返しなどの制御構造の概念を理解し、 手続きの複雑な流れを定義するためのプログラムの記述方法を習得する。 また、関数の再帰呼び出しを理解する。

■ 目標

■ 目次


■ 演習内容

□ はじめに〜制御構造とは

   制御構造には、大きく分けて条件分岐と繰り返しの2種類がある。 条件分岐とは、変数の値などを条件として、 複数の手続きの中から1つを選んで実行するものであり、 また、繰り返しとは、ある手続きを指定回数分だけ、 もしくはある条件が成り立っている間だけ、 繰り返し実行するものである。 C言語では、条件分岐や繰り返しなどの制御構造により、 複雑な手続きの流れを定義することができる。

□ 条件分岐

真偽値に基づく条件分岐: if 〜 then 〜 else
   ある条件が成立しているか否かで条件分岐を行うには、if文を使用する。 if文の書式は以下のようになる。

if文の書式
if ( <条件式> ) <手続き1> else <手続き2>

   このとき、<条件式>が成立していれば<手続き1>を実行し、 成立していなければ<手続き2>を実行する。 <条件式>には、変数の値が指定した値と等しいか、異なるか、 大きいか、小さいか、などを記述することができる。 また、複数の条件式の論理演算で結合した、条件式を記述することもできる。 <手続き1>および<手続き2>には、 式、もしくは"{"と"}"で囲んだブロックを記述する。 なお、もし、<条件式>が成立しなかった場合に何もしないのであれば、 else以降の青字の部分は省略することができる。
   例えば、標準入力から読み込んだ文字に応じて 標準出力に出力する内容を変えたい場合には、以下のようなプログラムとなる。

if文を使用したプログラム例
/* if-then-else-sample.c */

#include <stdio.h>

int main(int argc, char **argv)
{
  char a[255];

  printf("Is it OK? (y/n) ");
  scanf("%s", a);
  if ( *a == 'y' || *a == 'Y' )
    puts("Thank you.");
  else {
    puts("Oh, really?");
    puts("I see...");
  }
}

   このプログラムの実行結果は、以下のようになる。

if文を使用したプログラムの実行例(1)
$ ./if-then-else-sample
Is it OK? (y/n) y
Thank you.

if文を使用したプログラムの実行例(2)
$ ./if-then-else-sample
Is it OK? (y/n) n
Oh, really?
I see...

変数の値に基づく条件分岐: switch 〜 case
   変数の値などに応じて複数の分岐を行う場合には、 switch 〜 case文を使用する。 switch 〜 case文の書式は以下のようになる。

switch 〜 case文の書式
switch ( <式> ) {
  case <定数1>: < 式1-1>; < 式1-2>; ...;
  case <定数2>: < 式2-1>; < 式2-2>; ...;
    ...
  case <定数n>: < 式n-1>; < 式n-2>; ...;
  default:      < 式0-1>; < 式0-2>; ...;
}

これは、switchでしていされる式を評価した結果が caseで指定される定数と一致する箇所にジャンプすることを意味する。 もし、caseで指定された定数と一致しない場合には、 defaultで指定される箇所にジャンプする。
   ただし、単にcaseで指定した箇所にジャンプした場合、 このままでは一致した箇所以降の全てが実行対象となり、 指定された定数と一致していない部分も実行されることとなる。 これに対して、caseで指定される定数と一致する箇所のみを 実行したい場合、 ブロック中の手続きの実行を打ち切る命令であるbreak文を使用して、 以下のように記述するとよい。

よく利用されるswitch 〜 case文の書式
switch ( <式> ) {
  case <定数1>: < 式1-1>; < 式1-2>; ...; break;
  case <定数2>: < 式2-1>; < 式2-2>; ...; break;
    ...
  case <定数n>: < 式n-1>; < 式n-2>; ...; break;
  default:      < 式0-1>; < 式0-2>; ...;
}

   例えば、指定した年月日の曜日を表示するプログラムは、 switch 〜 caseを利用して以下のように記述することができる。

指定年月日からの曜日の算出
/* week.c */

#include <stdio.h>

int main(int argc, char **argv)
{
  int year, month, day, week;

  printf("year:  ");
  scanf("%d", &year);
  printf("month: ");
  scanf("%d", &month);
  printf("day:   ");
  scanf("%d", &day);

  if (month == 1 || month == 2) {
    year--;
    month += 12;
  }

  week = (year + year/4 - year/100 + year/400 + (13*month + 8)/5 + day) % 7;
  switch ( week ) {
  case 0:
    puts("Sunday");
    break;
  case 1:
    puts("Monday");
    break;
  case 2:
    puts("Tuesday");
    break;
  case 3:
    puts("Wednesday");
    break;
  case 4:
    puts("Thursday");
    break;
  case 5:
    puts("Friday");
    break;
  case 6:
    puts("Saturday");
    break;
  default:
    puts("not a day of week");
  }
}

   このプログラムの実行結果は、以下のようになる。

week.c の実行例
$ ./week
year:  2002
month: 11
day:   5
Tuesday

□ 繰り返し

指定回数分の繰り返し: for
   指定した回数分だけ、同じ手続きを繰り返す場合、for文を使用する。 for文の書式は以下のようになる。

for文の書式
for ( <初期条件>; <繰り返し条件>; <更新式> ) <手続き>

   例えば、1からnまでの総和を求めるプログラムは、 for文を利用して以下のように記述できる。

for文を利用した総和を求めるプログラム: sum.c
/* sum.c */

#include <stdio.h>

int main(int argc, char **argv)
{
  int sum = 0, n, i;

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

  for( i = 1; i <= n; i++ ) {
    sum += i;
  }

  printf("Summation from 1 to %d is %d\n", n, sum);
}

   このプログラムの実行結果は、以下のようになる。

sum.c の実行例
$ ./sum
n: 10
Summation from 1 to 10 is 55

真偽条件に基づく繰り返し(1): while
   ある条件が成立している間、同じ手続きを繰り返す場合、while文を使用する。 while文の書式は以下のようになる。

while文の書式
while ( <条件式> ) <手続き>

   1からnまでの総和を求めるプログラムをwhile文を利用して 以下のように記述することができる。

while文を利用した総和を求めるプログラム: sum-by-while.c
/* sum-by-while.c */

#include <stdio.h>

int main(int argc, char **argv)
{
  int sum = 0, n, i = 1;

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

  while ( i <= n ) {
    sum += i;
    i++;
  }

  printf("Summation from 1 to %d is %d\n", n, sum);
}

真偽条件に基づく繰り返し(2): do 〜 while
   while文が、先ず条件式を評価し、 条件が成立していれば指定された手続きを実行するのに対し、 do 〜 while文では、先ず、指定された手続きを実行した後に、条件式の評価を行う。 これは、最低1回は手続きが実行されることを意味する。 do 〜 while文の書式は以下のようになる。

while文の書式
do <手続き> while ( <条件式> )

   do 〜 while文を利用したプログラムの例を以下に示す。

do 〜 while文を利用したプログラム例: spoilt-child.c
/* spoilt-child.c */

#include <stdio.h>

int main(int argc, char **argv)
{
  char a[256];

  do {
    printf("Will you give me a candy? [y/n] ");
    scanf("%s", a);
  } while ( *a != 'y' && *a != 'Y' );

  puts("Thank you!!");
}

   このプログラムの実行結果は、以下のようになる。

spoilt-child.c の実行例
$ ./spoilt-child
Will you give me a candy? [y/n] no
Will you give me a candy? [y/n] no!
Will you give me a candy? [y/n] No!
Will you give me a candy? [y/n] yes...
Thank you!!

□ 関数の再帰呼び出し

   関数の定義の中で、その関数自身を利用することを関数の再帰呼び出しと呼ぶ。 例えば、1からnまでの総和を求める関数sum(n)は、 関数の再帰呼び出しを利用して以下のように帰納的に定義することができる。

sum(n)関数の帰納的な定義
sum(n) =
n
Σ i
i = 1
= {
n - 1
n + Σ i
i = 1
= n + sum(n-1)   (n > 1)
1   (n = 1)
 

   これをC言語の関数として定義する場合、以下のように記述することができる。

関数の再帰呼び出しを利用したsum()関数の帰納的定義
int sum(int n) {
  if ( n > 1 )
    return n + sum(n - 1);
  else
    return 1;
}

   ただし、この様に関数を定義したほうが見た目に判りやすい場合もあるが、 関数の再帰呼び出しはメモリ領域を多く消費するうえ、 実行速度も遅くなることが多いため、あまり推奨されない。 プログラムが判りづらくならない限り、繰り返し構造を利用すべきである (注: 再帰呼び出しを利用しないと定義できない関数もある)。

■ 演習課題

□ 演習課題1

   a=1, b=1, c=1であるとき、 以下の2つの論理式の真偽を自分の頭で求めよ。 また、プログラムを記述し、結果を比較せよ。

((a >= 0 || b == 1 ) && c > 2)
(a >= 0 || b == 1 && c > 2)

□ 演習課題2

   関数の再帰呼び出しを利用してnの階乗: n! を求める関数fact()を定義し、 5!の値を求めよ。
Last modified: Tue Nov 05 21:03:19 JST 2002