2 質問事項の説明

前回のプログラムの実習の時間での質問事項をまとめておく。

2.1 乱数の発生

問題の2.3 配列[練習 2]で使われている乱数の部分が分かりません。
乱数とは、バラバラな数列のことを言う。とくに、自然数がめちゃくちゃに現れるよう なものを自然乱数という。0以上無限大までの全ての自然数を用いた自然乱数が考えられ るが、実際上は最大の自然数を決め、その範囲で考えることが多い。我々が使用している システムのC言語の場合、0〜2147483647の範囲2の 乱数を発生させることができる。

C言語のプログラムでは、rand()関数を使って、乱数を発生させる。例えば、次のよ うにすると、rand()関数が呼び出される度に、それが乱数を返し、配列a[i]に 格納される。
  for(i=0; i<ndata; i++){
    a[i]=rand();
  }

コンピューターは正確に言われたとおり(プログラムのとおり)に計算を行うことは、諸君 もよく知っているはずである。そのため、コンピューターはめちゃくちゃな順序で数が並 んでいる乱数を発生させることは苦手である。先ほどのrand()関数は、ある初期値 3を使って、計算により乱数を決めている。 同じ初期値を使って、rand()関数を呼び出すと、同じ数列が発生するこのになる。 これでは、乱数とは言い難いので、初期値を毎回変更するのがよい。

初期値も値が毎回異なる整数を決める必要があるが、現在の暦時刻を返すtime()関 数を用いるのが一般的である。初期値の設定は、srand()関数に引数(符号無し整数) を渡すことにより可能である。次のようにすれば、毎回異なる初期値を決めることがで きる。
srand((unsigned int)time(NULL));
ただし、1秒以内であれば、timeは同じ値となり、同じ初期値となり、同じ 乱数となることに注意が必要である。(unsigned int)は、キャストと呼ばれる強制 型変換で、引き続く値の型を変換している。time()関数の引数は暦時刻で、暦時刻 がポインターで格納される。暦時刻を格納する必要がないときには、NULLと空ポイ ンターを指定する。

以上から、乱数を発生させるためには、rand()srand()time()関数が 必要であることが分かった。これらの関数を使うためには、関数の宣言が書かれているヘッ ダーファイルが必要である。rand()srand()にはstdlib.hが、 time()にはtime.hが必要となる。以上をまとめると、配列a[i]に1024個の乱数 を発生させるためには、次のように書く。
#include <stdlib.h>
#include <time.h>

int main(void){
  int a[1024], i;

  srand((unsigned int)time(NULL));

  for(i=0; i<1024; i++){
    a[i]=rand();
  }

  return 0;
}

2.2 平方根

問題の2.6 関数[練習 1]で使う平方根( $ \sqrt{\quad}$)の計算方法が分かりません。

数学で使う平方根は、関数を表すことは十分知っていると思う。$ \sqrt{x}$と書けば、 $ x$の1/2乗を計算する。したがって、C言語では通常の関数のように平方根を書くことに なるが、 $ \sqrt{\quad}$という記号を使うことは出来ない。C言語の関数名は、アルファ ベットの小文字(az)か大文字(AZ)、あるいはアンダースコア (_)を使って表現しなくてはならないからである。このようなことから、数学の $ \sqrt{\quad}$の代わりに、sqrt()関数4を使う。 $ y=\sqrt{x}$を書きたければ、次のようにするのである。
y=sqrt(x);
もちろん、これは数学関数であるので、ヘッダーファイルのmath.hにその宣言が書かれ ている。そのため、プログラムの最初の方に
#include <math.h>
と記述し、コンパイル時には、gcc -lm -o hoge fuga.cのように、-lmという オプションを付ける。

2.3 マクロ定義#define

問題の2.2 関数[練習 5]で使われている#define TEST 100000の意味が分かりません。

#defineはプリプロセッサーと呼ばれるもので、2.7 構造体[練習 1]や2.8 ポイン ター[練習 2]等で使っている。プリプロセッサーというのは、コンパイルに先立って、 ソースファイルのテキストを編集するものである。#includeもそのひとつである。

ここでは、#defineを文字列置換として使っている。例えば、練習問題の解答プロ グラムで使っている
#define TEST 100000
のような場合の動作は次の通りである。このプリプロセッサーが書かれている後の文では、 TESTという文字列は全て、100000に置き換わる。コンパイルに先立って、この置き 換えの動作が実施されるので、文字列TEST100000と同じ扱いになる。

この文字列置換は便利な機能で、一回で多くの文字列を置き換えてくれる。例えば、ここ では100000までの素数を求めるのであるが、200000までと問題が変更された場合、プロ グラムの変更は1箇所で済む。#define TEST 200000とすればよいのである。これ は、ソースファイル全てにわたって実施されるので、関数内でのみ有効というわけでは ない。

通常、重要な数がプログラム中にそのまま記述されるのは望ましくない。その数が変更 になると、複数の文を直す必要が生じ、バグの元である。そこで、#defineを使っ て、重要な数は1箇所で記述するのがセオリーである。

また、#defineの直後に書かれる文字は、マクロ名と呼ばれ大文字で書くことが慣 例となっている。C言語の関数や変数名は小文字で書かれることが慣例となっており、そ れがむやみに置き換わらないようにするための配慮である。

2.4 データの終わりを示すEOF

問題の2.4 ファイル入出力[練習 2]で使われているEOFの意味が分かりません。

この練習問題では、ファイルのデータの読み込みと出力の部分は、次のように書かれている。
  while(fscanf(fp, "%d%lf%lf%lf", &theta, &s, &c, &t) != EOF){
    printf("%d\t%f\t%f\t%f\n", theta, s, c, t);      /* 表示 */
  }
このこの部分の動作は、次のようになっている。
  1. fscanf()関数で書式に従いファイルからデータを読む。
  2. fscanf()関数の戻り値がEOFと異なるならば、
    • printf()関数により、書式に従いデータを出力する
    • 再度、2に戻る(fscanf()関数を実行する)。
    を実行する。もし、戻り値がEOFならば、whileのループを抜ける。

fscanf()関数の戻り値がEOFになったならば、読み込みと出力の動作の繰り返 し文から抜ける構造となっている。fscanf()関数の戻り値は、入力項目数である。 このプログラムの場合は、データがある場合は4が返される。

EOFという戻り値は、fscanf()関数で読み込むデータが無いときの値である。 全てデータを読み終わって、ファイルの終わりにきたときにEOFが返されるのである。データ数 が不明で、ファイルの終わりまでそれを読み込みたい場合、EOFを使うのが常套手 段である。

EOFは、End Of Fileからきており、その値は-1となっていることが多い。

2.5 キャスト

問題の2.2 制御文[練習 5]で使われている(int)の意味が分かりません。

これは、キャスト(強制型変換)と呼ばれる演算子である。この練習問題のプログラム中 では、次のように使われている。
test_max=(int)sqrt(TEST);
ここで、変数testは整数型であり、TEST#defineにより100000である。 したがって、 $ \sqrt{100000}$を計算し、それを整数化(切り捨て)した後、整数変数 test_maxに代入することになる。sqrt(TEST)の戻り値は倍精度実 数であるので、強制型変換演算子(int)により、整数化している。

このように式5の値の型を変 えたい場合にキャストという強制型変換を使う。これは、次のように書けばよい。
(型名)式

2.6 文字読み込み

問題の2.5 文字処理[練習 1]で使われているfgetsの動作が分かりません。

fgets()関数は、ファイルから文字数を指定して、文字列を読み込む関数である。そ の書式は、以下の通りである。
fgets(配列名, 配列の大きさ, ファイル)
戻り値は、読み込みに成功したら入力文字の先頭アドレスを格納しているポインター(配列名)を、失敗したらNULLを返す。

練習のプログラムでは、次のように使われている。
  fgets(moji, 32, stdin);
moji[32]で宣言されている配列に、ファイルstdinからデータを読み込むよう になっている。ファイルstdinは特別なファイルで、標準入力(STanDard IN)で、通 常はキーボードを表す。

scanf()関数を使わない理由は、空白(スペース)を含めた文字列を配列に格納した いからである。scanf()関数の場合、空白は文字列の区切りとして取り扱われるの で、それを配列に入れることは出来ない。

キーボードから空白を含めた文字列を入力するだけならば、gets()という関数もあ るが、これは使わないのが慣例である。この関数は、配列で確保された以上のデータを 入力するとプログラムが暴走する可能性がある。

ホームページ: Yamamoto's laboratory
著者: 山本昌志
Yamamoto Masashi
平成17年9月22日


no counter