情報基礎A 「Cプログラミング」(ステップ4)

このステップの目標

0.準備

CTRL-Cで強制終了

反復処理のプログラムに間違いがあると、期待に反して「いつまで経ってもプログラムが終了しない」ことがある。ソフトが「固まった」状態だ。そんな状態から脱し、処理を強制的に中断するには、端末エミュレーター上でキーボードの下段にあるコントロール(Ctrl)キーを押したまま、アルファベットのcを押す(コントロールC, CTRL-C)。実習に入る前に、プロンプトが出ている状態で、CTRL-Cの押し方を確認しておくこと。

1. 反復処理

コンピュータは、プログラムに記述された膨大な手続きを、あっと言う間に処理できるから役に立つわけだが、その膨大な手続き(プログラム)のすべてのステップの全てを人間が書き下さなければならないとしたら、本末転倒だ。プログラムを記述する手間や時間のほうが、プログラムの実行時間を、はるかに上回ってしまうからだ。 コンピュータが役に立つには、少ないプログラムの記述量で、膨大な処理ステップをこなすことができる、そんな仕掛けが不可欠なのだ。

そのために必要となるのが、繰り返し処理、あるいは反復処理だ。反復をマスターしなければ、プログラミングを学ぶ意味が無い。

数の足し上げプログラム

電卓を使った計算方法に倣って、1から10まで足し算を行うプログラムを書いてみよう。そうすると,きっとこんな感じのCのプログラムになるはずだ。

#include <stdio.h>
main( )
{
    int sum ;                                  電卓で計算すると・・・
    sum = 0 ;                                    MC
    sum = sum + 1 ;                              1 M+
    sum = sum + 2 ;                              2 M+
    sum = sum + 3 ;                              3 M+
    // 途中略
    sum = sum + 10 ;                             10 M+
    printf("1から10までの合計: %d\n",sum) ;      MR 
}

最初に変数sumを0にセットしておいて,sum=sum+1;sumに1がセットされ,次のsum=sum+2;で(sumには1がセットされているのだから)sumに3がセットされ・・・,という風にしていけば,正しい結果が得られそうだ。

C言語ではsum=sum+1; を縮めて、sum++; 書くことができる(ついでながら、sum=sum-1;なら sum--; )が、ここでは、愚直な記法に徹したい。

sum = sum + 1 ; のような書き方(両辺に同じ変数が現れる)は「定型文」として頭に入れておきたい。「現在のsumの値に1を加えた値を,あらためてsumにセットする」わけだから,この計算をおこなうと,sumの値は1だけ増えることになる。

上記のプログラムで, 「sum = sum + (数値)」の(数値)のところが1,2,3・・・10のように規則的に変化していることに気づいてほしい。

ここで、新たに変数nを使って上のプログラムを

#include <stdio.h>
main( )
{
    int n,sum ;      
    sum = 0 ;  
    n=1 ;
    sum = sum + n ;  
    n=n+1 ;
    sum = sum + n ;
    n=n+1 ;
    sum = sum + n ; 
    // 途中略
    sum = sum + n ; 
    printf("1から10までの合計: %d\n",sum) ;
}

と書き直してみたらどうだろう。そうすると,「 sum = sum + n ; n=n+1 ; 」をひたすら繰り返す構造になっているのが分かる。この計算をC言語流に表現したのが,次の例題4だ。

例題4 (ex4.c)

#include <stdio.h>
main( )
{
     int n, sum ;
     sum=0 ;
     for (n=1 ; n<=10 ; n=n+1) {
        sum = sum + n ;
     }
     printf("1から10までの合計: %d\n",sum) ;
}

まずは動作を確認するため、このプログラム(ex4.c)をコンパイルして、実行してみよう。

icon-teacher FOR文による反復処理の記述

for文

C言語には,「反復」を表現するいくつかの方法が用意されているが、 中でも最もよく使われる、for文を使った方法をここで学ぶことにする。 for文は、これまで登場したCの文よりも、いささか複雑な構造をしており、4つのパートで構成される。

そして、これらを次のように配置する:

for( 反復の前に一度だけ実行する文 ; 繰り返しの継続条件 ; 反復の都度実行する文) {

    繰り返す内容 ;
    
}
c-4-iteration-for-loop-sum

ここで、for文の丸括弧の中のセミコロンは各パーツの区切りを示すために必ず必要。また、 「繰り返す内容」の箇所には、(if文の場合と同様に)、{文1;文2;文3;・・・;} のように「やりたいことを列挙する。

例題4のforループの部分を流れ図にしたのが左図である。基本形との対応関係は:
 反復の前に一度だけ実行される文:n=1 ;
 繰り返しの継続条件: n<=10
 1回分の反復の度に行う処理: n=n+1 ;
 繰り返す内容: { sum=sum+n ; }
図から、プログラムの流れがループ構造になっている様子がわかる。

ここで、非常に大切なのは、for文のパターンに従って、文法的に正しく記述できるだけでなく、 処理の各ステップで各変数の値がどのように変化してゆくのかをきちんと頭の中でシミュレーションができるということだ。 その例を別ページに示したので、参照のこと。

icon-pc 練習:奇数の和と階乗の計算

例題4をひな形にして、

  1. 1から100までの奇数のみの合計を計算するプログラムを作成せよ。
  2. 1から10をかけ合わせた結果(10の階乗)計算するプログラムを作成せよ。
icon-hint ヒント

奇数だけの合計を求めるには、(1)forループの変数nを1から始めて2ずつ増やすようにするか,(2) forのところはそのままで,ループの中でnの値をチェックしながら,nが奇数の時だけsumに加える、ふたつのやり方がありそうだ。後者を採用した場合,整数 n が奇数かどうかを判断するには、if文を使って、

if ( n%2 != 0 ) { ・・・} 

という書き方を使えばよい。n%2は「nを2で割った余り」。であるから、このif文は「nの値を2で割った余りが0に等しくない場合は・・・を実行せよ」ということになる。

icon-pc 練習:閏年の一覧表示

2000年から2100年の間の閏年のリストを表示するプログラムを、以下の雛形から出発して、作成してみなさい。 閏年かどうかの判定方法はひとつ前のステップを参照のこと。

#include <stdio.h>
main( )
{
  int year ;
  for (year=?? ; ?? ; ??) {
    if ( ???????? ) {
       printf("%d\n",year) ;
    }
  }
}

2. for文を使いこなす

icon-teacher forを使った「定型文」

Cの構文の中で、おそらくfor文がいちばん複雑ではないかと思う。 けれども、いくつかの典型的なパターンに従って使う場合がほとんどなので、 それらをしっかりと押さえておけば、大抵の問題に対処できるだろう。

カウントダウン
無限ループ
実数のループ変数
総和の計算

書き方のパターン  コメント
for (i= から ; i <= まで ; i=i + ずつ) {
   反復内容
} 
上の例でもたびたび登場したパターン(iは整数型と想定)。
i=0からn回反復する場合は、for(i=0; i<=n-1; n=n+1)となる。
もちろん for(i=0; i<n; n=n+1)でも同じ。
for (i= から ; i >= まで ; i=i - ずつ) {
   反復内容
} 
「から」に大きな数を入れて、そこから「カウントダウン」
させるには、「・・・ずつ」の箇所を引き算にすればよい。
反復の継続条件を表す不等式の向きが逆になることにも注意。
for (;;) {
  ・・・ ; 
  if (条件) break ; 
  ・・・ ;
}
for文の「3点セット」の箇所を空にすると、これは
「無限ループ」を表す。文字通り、いつまで経っても
繰り返しつづけるプログラムだ。プログラムはいつかは
終了しないと困るので、反復内容の中にif文を置いて、
そこでループを終了させることができる。break;文が
実行されると、ただちにループを終了して、次のステップ
(for文の次)に処理が移行する。
float x ;
for (x= から ; x < を超えないで ; x=x + ずつ) {
   反復内容
} 
実数をあるステップで「刻みながら」反復する場合
継続条件の等号に用心。x = x + dx ; をN回繰り返した結果が、
誤差のために、x + N*dx に等しくなるとは限らないからだ。
s=0 ;
for (n= から ; n <= まで ; n=n+1) {
  n番目の値の計算・・・;
  s = s + n番目の値 ;
} 
総和を計算する場合は、いつもこのパターン。
総和を入れるための変数(左ではs)の初期化も忘れぬように。

icon-pc 練習:ゼータ関数による円周率の計算

リーマンのゼータ関数(Riemann zeta function)

c-4-eq-zeta-func

は、数学の問題の各所に関係した大変に興味深い対象である。例えば、ζ(2)の値は、以下のように、円周率と関係の あることがわかっている:

c-4-eq-zeta2

C言語の「総和の計算」のパターンに、ゼータ関数を当てはめることで、ζ(2)の値を数値的に評価してみなさい。

icon-hint ヒント

注:N項目から無限大項目までの「足し残し分」の見積もりとして
c-4-eq-zeta2-remaining
と考えると、N項目で和を打ち切った場合に想定される誤差を(ある程度)予想できるだろう。

#include <math.h>しておくと、M_PIという記号に円周率がセットされる。

定義のとおりに無限和が計算できるわけがないので、総和の計算はどこか大きなnで(例えば1000で)打ち切る(左の注も参照)。

総和の各項である 1/(n*n)は、整数として計算すると、0になってしまう。それを避けるには、1.0/(n*n) と記述すればよい。

#include <stdio.h>
#include <math.h>
main( )
{
  int n ;
  float s,zeta2 ;
  s=???
  for (n=?? ; ?? ; ??) {
     ?????? ;
  }
  
  zeta2 = M_PI * M_PI / 6.0 ;
  
  printf("総和によって求めた zeta(2)= %f\n",s) ;
  printf("数学的な結果 =%f\n",zeta2) ;  
}

icon-pc 発展:連分数の計算

以下は、黄金比(Golden ratio)と呼ばれる「有理数から最も遠い無理数」を連分数表示したものである。

c-4-eq-cont-frac

for文を使ったプログラムによって、この式のとおりに計算し、φの値を評価しなさい。

icon-hint ヒント

正解は、(1+sqrt(5)) / 2 = 1.618033988...である。

計算にあたっては、「下」から攻めてみるのが良さそうだ。nステップ目の計算値をφ(n)とすると、次のステップの値は

Φ(n+1) ← 1 + 1/Φ(n)

となるはず(下図参照)。すると、この計算を繰り返せば、答えに到達できるだろう。

c-4-eq-cont-frac2

このように、「現在の値」を使って「次の値」を計算する、という手順の繰り返しは、反復処理の典型的なパターンだ。

さらなる発展として、腕に覚えのある者は、フィボナッチ数列(Fibonacci sequence)

c-4-eq-fibonacci

を計算し、連続する二つの値の比 fn+1 / fnが、ある数に漸近してゆく様子を「観察」してみなさい。 この数列を計算するには「二つ前」の値まで記憶しておかなければならない点に注意。


tfield-icon亀場で練習:正6角形の描画

タートルグラフィックスのページの前半部分をよく読み、反復処理を用いて、(半径100の円に内接する大きさで)正6角形を描画するプログラムを作成しなさい。