講義第9回:C の基礎 --- グラフィックスと繰り返し、判断

今日はグラフィックスの機能を使い、画面にいろいろ絵(主に線画)を書いて みる。さらに、繰り返し処理などで複雑な図形を書くことを考える。

なお、2回前に出したレポートは、まだ提出状況をしていない。

グラフィックス

この講義で使っている PASCAL では、あらかじめ準備されている様々な手続き、 関数を使うことで画面に絵を書くことができる。関数とは、普通の数学関数の ように、数を返してくれるものである。手続きとは、例えば「指定した座標を 中心として指定した半径で円を書く」という一連の仕事をやってくれるもので ある。

/* graphic_sample
 *
 * (C) J. Makino Version 1.0 Jan 11, 1997
 */
#include <stdio.h>
#include </home/makino/text/xgraph.h>
#include <math.h>

int gd, gm;
int    xcen, ycen;
char dumstr[100];
main()
{
  clrscr();
  gd = detect();
  initgraph(gd,gm,"");
  cleardevice();
  xcen = getmaxx() / 2;        
  ycen = getmaxy() / 2;        
  circle(xcen,ycen, 100);       
  circle(xcen,ycen, 150);       
  line(xcen-150,ycen-150,xcen+150,ycen-150); 
  line(xcen+150,ycen-150,xcen+150,ycen+150); 
  line(xcen+150,ycen+150,xcen-150,ycen+150); 
  line(xcen-150,ycen+150,xcen-150,ycen-150);
  outtextxy(100,100,"This is test");
  gets(dumstr); 
  gets(dumstr); 
  closegraph();
}

この例では、最初の #include </home ... というのは、グラフィックス関数を使 うということを宣言している。 実行部の先頭の2行は、実際に絵を書くための 準備をする手続きを呼んでいる。これを呼ぶと新しいウインドウができる。

このウインドウは、 800 x 800 の点からできている。座標系は、 慣習により画面の左上が (0,0), 右下が (799,799) となっている。

このグラフィックの手続きは、もともとパソコンを持っている人のことを配慮してNEC や IBM のパソコンでつかわれている Turbo C や Turbo PASCAL (商品名)というコンパ イラと(大体)同じように使えるようになっている。しかし、NEC や IBM の パソコンでは、画面の大きさが違う。このため、画面の大きさを教えてくれる 関数が準備されていて、それが getmaxx などである。これを使って、 画面の中心を決め、そこを中心に円と正方形を書いている。

グラフィックス手続き、関数の詳細については、 簡単なまとめを ECC で提供しているグラフィックス命令を C 言語で使う方法として つくっておいた

上のプログラムでは、circle と line が実際に円を書く、あるいは直線を引 く手続きである。従って、これらを追加したり、指定する数(引数)を変えた りすれば画面に出る絵が変わる。なお、手続きや関数で引数を2個以上必要と する場合(例えば circle では x 座標、 y 座標、 半径の3個)3個の数(あ るいは変数、式)をコンマで区切って並べる。並べたものの何番目がどれか(た とえばx、y 座標 と半径のうち)は、あらかじめ決まっている。このような方 式を、位置パラメータといい、現在のほとんどの計算機言語で使われている。

このやり方の代わりに、例えば circle(x_pos=xcen, y_pos=ycen, radius=100) というように指定することも考えられる。この方がわかりやす いが、プログラムが繁雑になるのを嫌うためかこの形式が使える計算機言語は 少ない。

なお、このプログラムを打ち込んで、そのままコンパイルしようとしてもうま くできない。これは、グラフィックスを使う関数がどこにあるか指定されてい ないからである。これは以下のように指定する。

gcc graphics_sample.c -o graphics_sample -lXtc -lX11
ここで、最後の2つの -l 何とかというのが、グラフィックスを使うた めのおまじないである。コマンドやファイル名には大文字と小文字の 区別がある。例えば -lxtc とか -lXTc とかではうまく動かない ので注意して欲しい。

これらのおまじないの意味については、 ld コマンドのマニュアルページを見 てもらえばいいが、要するに、 -lxxx と書くと、どこかにある libxxx.a と いう名前のファイルに、プログラムのなかで使われてはいるが定義されていな い関数がを探しにいくというものである。

反復と判断

さて、計算機のプログラムで絵を書くといっても、線を1本引くのにいちいち line ..... と書くのであれば tgif でも使うか、そもそも紙に 手で書いた方が早い。プログラムを書くといいことは、例えば線を引く位置を 変えながらたくさん引くとかいうのができることである。
/* graphics_loop
 *
 * (C) J. Makino Version 1.0 Jan 11, 1997
 */
#include 
#include 
#include 

int gd, gm;
int xsize, ysize;
int i, n;
double  dx, dy;
char dumstr[100];
main()
{    
  printf("How many lines? ");
  scanf("%d",&n);
  gd = detect();
  initgraph(gd,gm,"");
  cleardevice();
  xsize = getmaxx() ;            
  ysize = getmaxy() ;
  dx = xsize/n;
  dy = ysize/n;
  for (i = 0 ;i<n+1;i++){
    line(0,(int)(i*dy), (int)(i*dx),ysize);
  }
  gets(dumstr); 
  gets(dumstr); 
  closegraph();
}

for (変数 = 最初の値; 変数 < 最後の値 + 1; 変数 ++ ) 文; は、まず変数に最初の値を入れて文を実行し、次に変数を1増やしてまた文を 実行し、以下同様に繰り返して変数が最後の値になったらおしまいにするとい うことになる。このような繰り返し処理をループ処理という。上の場合では始 点、終点が違う線分を指定した本数だけ引いてくれる。

C 以外の多くのプログラム言語では、例えば for i:=0 to n do 文; (Pascal の場合)というように、「変数を1増やしては同じことを繰り返す」 という上に書いた通りのことをするための特別な書き方(構文)があるが、 C の forを使った構文はもっとフレキシブルなものである。

for(文1;条件文;文2){ 文3; 文4; ... }というふうに書いてあると、実際に 起きることは、

というようなことで、別に「変数に最初の値を入れて文を実行し、次に変数を 1増やしてまた文を実行し、」ということしかできないわけではない。例えば
int i;
for(i=0; i<262144; i *= 2){
   printf("i = %d\n");
}
と書けば、変数iの値を繰り返しごとに2倍にすることになる。

なお、ここで、 i++; とか i *= 2; とかいうものが出 て来たが、これは C 言語に特有の書き方で、基本的には i++ は i = i + 1と 同じ意味だし、 i*= 2 は i = i * 2 と同じである。また、 i -- という表現 も使える。

一般に、あらゆる演算(加減乗除の他に、論理演算なども)について、

i 演算記号=j
と書くのは
i  = i  演算記号 j
と書くのと同じと思ってもらっていい。

あと、 実数型の値に対して (int) x という風に書いているところ があるが、これは、実数の値を整数に変換してから使うという意味である。前 回にやったように、計算機の中での表現方式が違うので、同じ値でも実数と整 数では計算機の中でのデータとしては全く異なっている。このために、これを つけないで書くと、何だか良くわからないことをしてくれることになる。

なお、これは C 言語に特有の欠点であって、計算機言語がみんなそういう面 倒なものだというわけではない。こういう問題に対応する方法には基本的には 2つあって、一つは、「コンパイラが人が間違えていないかどうか確認する」 という方法である。 PascalやC++ ではこの方法が採用されている。また、Cで も、この方法でコンパイラがチェックするように指定することもできる。これ は後で説明する。もう一つの方法は、実行する時に変数の型(ここでは実数か 整数か)を調べて、欲しいものと違ったらその場で変換するという方法である。 これを採用している代表的な言語は Lisp である。

もう少し高度な図形を書いてみよう。

/* graphics_loop2
 *
 * (C) J. Makino Version 1.0 Jan 11, 1997
 */
#include <stdio.h>
#include </home/makino/text/xgraph.h>
#include <math.h>

int gd, gm;
int xcen, ycen, x, y;
int i, n;
double  dx, dy;
char dumstr[100];
main()
{    
  printf("How many points? ");
  scanf("%d",&n);
  printf("Enter dx, dy:");
  scanf("%lf%lf", &dx, &dy);
  gd = detect();
  initgraph(gd,gm,"");
  xcen = getmaxx() / 2;            
  ycen = getmaxy() / 2;
  for(i = 0; i < n; i++){
    x = 200*cos(i*dx)+xcen;
    y = 200*sin(i*dy)+ycen;
    if (i == 0){
      moveto(x,y);
    }else{
      lineto(x,y);
    }
  }
  gets(dumstr); 
  gets(dumstr); 
  closegraph();
}
これはリサージュ図形(x方向とy方向が周期の違う単振動をする時にできる図 形)を描く。このプログラムでは、上のプログラムに比べて2つ新しいことを している。一つは i の値によって違うことをす るために if ... then ... else ... というものを使っているこ と、もう一つは line の代わりに moveto lineto を使っていることである。

if (条件) 文1;
else 文2;

という構造は、 条件が成り立っていれば文1を、そうでなければ文2を実行せよという意味にな る。文2がない(条件が成り立っている時はなにかするがそうでなければ何も しない)ときには

if (条件) 文1;

だけでいい。なお、 for の中に書いた、 { 文; 文; ... } というまとまりのことを複文といい、一般に文が書けるところには複文も書け る。このため、 if (...) { .... }else { .... } というような風にすれば条件によって違ういくつかの処理をまとめてできる。

条件は、数値同士の比較式(大小、等 しい)と、複数の比較式からできる論理式などが書ける。

具体的には、

a >  b                 aがbより大きければ真
a >= b                 aがbより小さくなければ真
a <  b                 aがbより小さければ真
a <= b                 aがbより大きくなければ真
a == b                 aがbと等しければ真
!条件                  条件が偽なら真(否定)
(条件1 ) && (条件2)    両方真なら真(論理積、and)
(条件1 ) || (条件2)    どちらかが真なら真(論理和、or)
というくらいがこれから出てくることがあるであろう。

さて、繰り返し処理には for の他にもう一つの方法がある。それは、 「ある条件が成り立っているうちは繰り返す」というものである。

while (条件){
    文;
    ......
    文;
}
という構造は、 begin endにかこまれた処理を条件が成り立っ ているあいだ繰り返せということになる。
/* graphics_loop3
 *
 * (C) J. Makino Version 1.0 Jan 11, 1997
 */
#include <stdio.h>
#include </home/makino/text/xgraph.h>
#include <math.h>

int gd, gm;
int xcen, ycen;
int i, ix, iy;
double  xmax, xmin, dx,x,y;
char dumstr[100];
main()
{    
  printf("xmax, dx ? ");
  scanf("%lf%lf",&xmax, &dx);
  xmin = -xmax;
  gd = detect();
  initgraph(gd,gm,"");
  xcen = getmaxx() / 2;            
  ycen = getmaxy() / 2;            
  x = xmin;
  i= 0;
  while (x <= xmax){
    y = exp(-x*x) * sin(10*x);
    ix =xcen + x*xcen/xmax;
    iy= ycen - y*ycen/xmax;
    if (i == 0){
      moveto(ix,iy);
    } else {
      lineto(ix,iy);
    }
    x = x + dx;
    i = i + 1;
  }
  gets(dumstr); 
  gets(dumstr); 
  closegraph();
}

画面のプリントアウト

グラフィックス画面は gcopy というコマンドを実行するとプリンター に送られる。これは、絵が出ているうちに行なう必要があるので、左上の小さ いウインドウで入力する。

プログラムが動かない?

苦労して入力した(まあ、Netscape の画面から cut&pasteしたり、ファイル に落してから編集したっていいんだけどね)プログラムが動かないのには無数 の理由がありうるが、いくつかを以下に書いておく。
  • gcc とやったところで、例えば
           collect2: ld returned 4 exit status
           ld: -lXTc: No such file or directory
           
    とでる。実行ファイルが作られない。

    これは、本来 -lXtc とするべきところを -lXTc と書いているので、 libXtc.a ではなく libXTc.a を探しにいって、そんなのは見つからないといっている。

  • 正しくコンパイルできたが、実行すると、グラフィックスのウインドウの枠線だけがでたあと、
    Badaccess .....
    
    のようなよくわからないエラーメッセージがでて、そのあとなにも画面にでないでプログラムが終ってしまうことがある。これは、普通は、netscape や xpaint などのプログラムを同時に使っている時に起きる。(mosaic では起きないようである)。

    これが起きたら、とりあえず netscape を終了して、もう一度グラフィックスのプログラムを実行してみること。

次週予告

今週は、あらかじめグラフィックス用に準備された手続きを使ってみたが、次週は手続きを自分で作ってみることにする。