授業で説明しない標準ライブラリ関数



Cでは標準ライブラリとして、多数の便利な関数が用意されています。 しかし授業では時間が限られているため、最小限に絞って説明しています。

ここでは、 授業で省略した関数の中で、 実際にプログラムを作成する際に有用なものをいくつか紹介します。 必要であれば、プログラミングコンテストなどで使ってみてください。

比較的難易度の高い話やOS等の知識が必要な話も混じっていますので、 2年生以上になって知識が増えてから、 あらためて読むのもよいかと思います。


乱数

使用するには
#include<stdlib.h>
が必要です。

int rand(void);
void srand(unsigned int seed);

ゲームなどでは敵の動きや迷路の生成、 カードのシャッフルなど、 ランダムな値(乱数)が欲しくなることがよくあります。

rand() は呼び出すたびに異なるランダムな値を返します。 つまり、呼び出すたびにサイコロを振って出た目を返すような関数です。 ただし、使う上でいくつか注意点があります。

rand() は0以上 RAND_MAX 以下の整数を返します。 大雑把には、 この範囲内の整数を等確率で返すと考えてかまいませんが、 環境によっては偏りがあるなど、あまり良い乱数ではない可能性があります。 通常のゲームで使う分にはほとんど問題ないと思いますが、 研究用のシミュレーションなどに使う場合は、 もっと精度の高い乱数アルゴリズムを使った方が良いかもしれません。 暗号生成などに使える高品質なものでは「メルセンヌツイスター」、 高速だがゲーム・シミュレーションなどでは十分高精度なものとして「xorshift」 などが有名です。 検索するとフリーの高精度乱数ライブラリも見つかりますし、 xorshiftは提案論文中の短いコードをコピーするだけで使えます。

RAND_MAX の値は #include<stdlib.h> 内で定義されており、環境により異なります。 たとえば、旧電算室環境(Solaris10)では32767ですが、 Linux 2.6では2147483647です。 したがって、 計算で乱数の最大値が必要なときは直接32767などと書かず、 必ず RAND_MAX を使って計算してください。

一般的な計算機には真にランダムな数を生成する機能はないため、 rand() は実際には、 答えが不規則に見える計算を行っているだけです。 したがって、 呼び出すたびに異なる値が返されますが、 プログラムを何度実行しても、 i 回目の値は必ず同じ値になります。

このままトランプゲームを作ると、 プログラム起動後一回目の手札はいつも同じになってしまい、 ゲームになりません。 srand() を呼び出すと、この乱数値のパターン(乱数系列)を変えることができます。 引数には0以上の整数を与えることができ、 引数毎に異なる乱数パターンになります。 ゲームの場合は srand() の引数自体を毎回変更する必要があります。 この引数をユーザに入力させたのではズルが可能になってしまうので、 現在時刻を返す関数 time() を使うのが定石です。 コード例を以下に示します。

#include <stdlib.h>
#include <time.h>

int main(void){
    srand(time(NULL));
    for (i = 0 ; i < 10 ; i++){
        printf("%d\n", rand() % 6);
    }
}

このプログラムは0〜5までの乱数を10回出力します。 最初に srand() に現在時刻を与えているので、 実行するたびに違う乱数が出てきます。 time() は秒単位なのでこのプログラムを連続して実行すると同じ乱数になりますが、 通常のゲームでは1秒以内に再起動することはないので問題ありません。

srand() は通常、プログラム起動時に一回だけ呼び出せば十分です。 rand() を使うたびに srand() を呼び直すと適切な乱数にならないだけでなく、 1秒以内に複数回呼んだ場合はすべて同じ値になるなど、 問題が発生します。 また、本当にランダムにしてしまうと実行のたびに動きが異なり、 デバッグが大変になります。 デバッグの際には srand(1); のように乱数系列を固定しておくと、 毎回同じ動作をするため問題点の追跡が楽です。

なお、このように剰余演算で0〜 N-1 の範囲の乱数を作る方法はお手軽ですが、 RAND_MAXN で割り切れないときは等確率の乱数にならないことに注意してください。 N が小さく、 精度を必要としないゲームなどでは無視できますが、 たとえば N が10000で RAND_MAX が32767であれば、 1が得られるのが1, 10001, 20001, 30001の4通りあるのに対し 5000が得られるのは5000, 15000, 25000, の3通りしかないため、 前者が後者よりかなり出やすくなります。


数学関数

使用するには
#include<math.h>
が必要です。また、コンパイル時にオプション -lm
を指定する必要があります。

三角関数

double sin(double x);
double cos(double x);
double tan(double x);
double atan(double x);
double atan2(double y, double x);

sin, cos関数は幾何学的な計算をするときに多用します。 たとえば、図形を2次元・3次元で回転する際には、 これらの関数を含む回転行列を用います。 高校数学では存在意義がわからず嫌いだった人も少なくないと思いますが、 3Dグラフィックやゲームなど、プログラミングでは使い道の多い関数です。

注意点として、引数xの値は単位がラジアンです。

ラジコン戦車のような 「旋回+向いている方向へ前進」 というスタイルの移動を行う (例: バイオハザード、FPS)には、 以下のようにして現在の向きから、x, y軸それぞれの移動量を計算します (座標系の取り方によっては、一部の符号を逆にする必要があります)。

#define SPEED 10;    // 前進速度
#define ROT_SPEED 5; // 旋回速度
#define PI 3.14159265

int d;   // 自機の向き
double r;
double x, y; // 自機の位置

while (){
    if (カーソル左が押された)
        d += ROT_SPEED;
    else if (カーソル右が押された)
        d -= ROT_SPEED;
    else if (カーソル右が押された) {
        r = d * PI / 180;  // 度からラジアンへの変換
        x += cos(r) * SPEED;
        y += sin(r) * SPEED;
    }
}

atan(逆正接、アークタンジェント)は高校の数学ではあまり使いませんが(習わない?)、 tanの逆関数で、 tanの値から、 その値になるような角度を返してくれます。 これはゲームの中で、 「自機がいる方向に向かって、敵機が弾を撃つ」などの動作をする際に使えます。

敵機から自機に向かう角度のタンジェントは 「y座標の差 / x座標の差」 で簡単に求まるので、 この値をatanに与えることで角度が得られます。 注意点は、 この返り値もラジアンであること、 y/x と (-y)/(-x) のタンジェントが同じ値であるため、 両者の区別(第1象限なのか第3象限なのか、など)を別途しなければならないこと、 xが0であってはならないこと(0除算になるから) です。

atan2を使うと、注意点のうち後の2つを回避できます。 上記の例であれば、「y座標の差」と「x座標の差」を共に引数として与えると、 角度を-π〜πの範囲で返してくれます。


環境変数

使用するには
#include<stdlib.h>
が必要です。

char *getenv(char *name);

Unix環境では、 環境変数と呼ばれるOS上の変数に、 ユーザやアプリケーションの情報が格納されています (xterm上でsetenv)と入力すると、 現在設定されている環境変数と値の一覧を見ることができます)。 環境変数名を引数にしてgetenvを呼び出すと、 その値を文字列として参照することができます。 主な環境変数を以下に挙げます。

標準ライブラリ関数なのでWindowsでも同じ関数が使えます。 ただし、 同じ名前の環境変数がなかったり標準で値が設定されなかったりするため、 UnixとWindowsで同じコードが正常動作するとは限りません。





最終更新: 2023年 4月 13日 木曜日 18:00:46 JST

御意見、御感想は ohno@arch.info.mie-u.ac.jp まで