C プログラミングガイド

Yutaka Yasuda
Kyoto Sangyo University

1. はじめに

ここでは GrWin を利用した簡単な C 言語によるプログラミングに必要な文法などをまとめておくことにします。

ここでの説明は網羅的なものではなく、部分を取り上げて曖昧に説明しています。これは本文の読者のプログラミングの知識のハードルを高くしすぎないためです。必要に応じて他の書籍を参照するようにして、不十分なところをおぎなって下さい。

読者がプログラミングに慣れて行くにしたがって自分でマニュアル、書籍、ネットワークなどから情報を常に取り込んで理解を深めていくことが大切です。

1.1 C 言語

今回取り上げるプログラミング言語は C です。C 言語はもともと OS (オペレーティングシステム)を作るために開発されたものですが、現在では多くの分野のソフトウェア開発に利用されています。

ここでとりあげるような簡単なプログラミングの実習には、手続き型と呼ばれるコンピュータに対して動作指示を手順通りに列挙していく方法でのプログラミング言語ならば何でもかまわないのですが、今回は C を選びました。主たる理由は、簡単なものを含めて導入向けの書籍が多いことと、非常に簡単に画面上に絵を描くことができる環境を用意することができたためです。C 言語にはもともと絵を描くような機能は用意されていませんので、今回は GrWin というライブラリを利用しています。(GrWin については後述)

2. 基本的な構造

2.1 全体構造

まず簡単なサンプルプログラムを以下に示します。

/* サンプルプログラム
   2003.12.6 Yuichiro Enokida. */
#include <stdio.h>
int main() {
  printf("sample printout\n");
  return 0;
}

このプログラムは画面にただ一行「sample printout」と表示するだけのものです。
ためしに実行してみましょう。実行にはコンパイルが必要です。以下の例では GWGCC コマンドを利用してコンパイルし、sample.exe という実行形式のファイルを作って、sample.exe として実行しています。

% GWGCC sample
% sample.exe
sample printout
% 

C 言語のプログラムにはいくつかの決まった構造があります。上のプログラムをざっとながめて、いくらかの構造にわけて考えて下さい。

  1. /**/ に囲まれた部分
  2. #include
  3. main() とそれに続く {
  4. そこから最後の } まで。

以下それぞれについて説明していきます。

/* ... */ コメント

/**/ に囲まれた部分は、プログラムの動作に一切影響を与えないメモ書きです。覚え書きのようなものを入れておく(コメント、などと言います)場合が多いですが、使い方は自由です。
複数行にまたがっても、間に行区切りを示す;(セミコロン)が入っても構いません。サンプルでも二行になっていますね。

例えば書きかけのプログラムを /**/ で囲んでおいて、プログラムに残したままテストランするとき、動作確認をするためのコードを入れておく、など。

include

#include は C 言語における「おきまり」だと思って下さい。関数(後述)などを利用するための準備作業をしているのですが、どういったときにどういったものを include する必要があるのかは、いまは考えないでいいでしょう。 [1]

main と return

つづく int main() は、main という名前の関数の記述がはじまったことを示しています。このプログラムが実行されると、まずここから、つまり main 関数の { } で囲まれたブロックの処理が行われます。

return を実行すると、現在実行中の関数(この例では main() )の処理を終了し、呼び出しもとに戻っていきます。つまりプログラム全体の終了となります。後に続く 0 は終了コードとなるものですが、今は気にしないで下さい。

つまりこのサンプルプログラムは、main()ではじまってprintf()関数をひとつ呼び出し、すぐreturnで処理終了するというものです。

(関数については関数の項を参照)


2.2 予約語

これまでの説明にあったように、C 言語には int、return など決まった意味のある文字列と、そうでない文字列があります。C 言語であらかじめ意味づけられている名前は予約語と呼ばれ、変数名(後述)や関数名(後述)には使えません。

予約語一覧(アルファベット順)

auto      break     case      char      const
continue  default   do        double    else
enum      extern    float     for       goto
if        int       long      register  return
short     signed    sizeof    static    struct
switch    typedef   union     unsigned  void
volatile  while

これ以外にも main() 関数や printf() 関数のように、あらかじめ使われている名前は他に多くあるのですが、ここでは示しません。徐々に覚えていくと良いでしょう。

2.3 文、行、ブロック

; による文の区切り

C 言語では行は見た目の改行とは関係なく、; (セミコロン)を一文の区切りと考えます。つまり以下の記述はどれも同じと解釈され、問題なく処理されます。

パターン 1.
  a=100;
  b=100;

パターン 2.
  a=100; b=100;

パターン 3.
  a=
    100; b=
  100;

プログラマは逆に記述としてわかりやすい書き方、つまり行頭の空白のあけ方、行の分け方、つなぎ方を意識しながら書くと良いでしょう。

例えば以下のプログラム、なんとなく前の方が読みやすく(プログラムの流れがわかりやすく)ないですか?

なんとなくわかりやすいパターン
    main()
    {
      int i;
      for(i=0; i<10; i++) {
        printf("line %d\n",i);
      }
    }

なんとなくわかりにくいパターン
    main() { int i; for(i=0;
    i<10; i++) { printf
    ("line %d\n",i); }}

(いまは余り具体的な違いが感じられなくても、なんとなく感じることがあれば十分です。その感覚を大事にして下さい。)

{ } によるブロック

{ } (中カッコ)は一連の処理の単位を表現しており、ブロックなどと呼んでいます。たとえば今まで示してきたサンプルプログラムでも、main()関数はここからここまでである、ということを示すために{ } が使われています。

2.4 数値、文字、文字列、型

C 言語では数値と文字と文字列は区別して表記する必要があります。具体的には以下のようになります。

数値(整数)0, 100, -123
数値(実数)0.0, 100.0, 0.56789, -123.45
文字'a', 'x'
文字列"sample string", "a"

C における数値には整数と実数があります。また文字といったら一文字だけで、'abc'はあり得ません。文字はまた長さ 1 の文字列とも区別されています。つまり'a'と、"a"は別のものです。

こうした区別を「型」(かた)と呼んでいます。C は型のあるプログラミング言語なのです。(型については変数の項も参照。)

3. 変数と計算式

3.1 変数への値の代入

count という名前の変数に 100 という値を設定するには以下のように表記します。

  count=100;

この、変数に値を設定する作業を「代入」と呼んでいます。つまり上の例は「変数 count に 100 を代入する」と表現します。 [1]

変数というものはメモリに名前をつけただけ、という簡単なものです。そのメモリに値を書き込んでいるのです。

変数

変数への代入:メモリの一部に名前を付けて値を格納する

3.2 変数の名前

変数の名前はプログラマが自由に決めて構いませんが、以下の条件を守る必要があります。

  1. アルファベット(大文字小文字とも)と _ (アンダーライン)と数字しか使えない
  2. 少なくとも変数名の最初の一文字めはアルファベット(大文字小文字とも)か _ (アンダーライン)であること
  3. 予約語ではない(予約語の項を参照)
  4. 英大文字と英子文字は区別される( aaa と AAA、aAa はすべて別の変数となる)

また、abcdef という変数を使っているとき、abc def というように間に空白を入れてはいけません。この場合abcdefの別の変数を二つ並べて書いたと見なされ、恐らく文法的にエラーとされるでしょう。

よくある名前は x, y, i, j, number, count, angle_max などのようなものでしょうが、なるべくわかりやすい名前をつけるようにすると、あとでプログラムを見直すときに楽になるでしょう。


3.3 型、宣言

C 言語の変数は、その利用に先んじて宣言が必要です。つまり以下のような格好になります。

  int count;
  count=100;

(細かい話をすると、この宣言によって「count」という名前をつけてメモリをそのために確保する、という動きをします。)

変数には型があるため、その宣言も型を明記しておこないます。「この型の変数としてコレとコレとコレを使う」というかたちで宣言する必要があります。つまり上の例のint は宣言する変数の型(かた)を示しています。

代表的な型に以下のようなものがあります。

int整数型
float実数型
char文字型
double倍精度型

( C 言語では文字列は文字の配列として実現するので、文字列型というのはありません。文字列の扱いについては文字列関数の項を参照。型の違う演算処理での注意点として型変換の項も参照。)

4. 演算子

演算子には算術演算子、論理演算子、ビット演算子などがあるのですが、ここでは算術演算子だけをとりあげます。

4.1 算術演算子

算術演算(数値の計算)に使える演算子は以下の通りです。

+, - などの四則演算子

演算子意味利用例(bを20、cを6とする)
+加算(+)a=b+c; (a は 26 になる)
-減算(ー)a=b-c; (a は 14 になる)
*乗算(×)a=b*c; (a は 120 になる)
/除算(÷)a=b/c; (a は 3 になる)
%剰余(割った余り)a=b%c; (a は 2 になる)

計算順序は *, /, % が +, - より優先です。
つまり 100+20/6 は 120 を 6 で割るのではなく、まず 20 を 6 で割るのが先です。

また、算術演算には型があり、20/6 は整数と整数の演算であるため結果も整数型となり、つまり 3.33.. ではなく 3 が得られます。つまり 100+20/6 の結果は 103 となります。もし 100+20 を先に計算したい場合は、(100+20)/6 と書きます。もし 20/6 の結果を実数で得たい場合は 100.0+20.0/6.0 のように実数型を意識した記述に置き換える必要があるでしょう。
剰余演算(%)は整数専用となっています。

+=, -= など代入演算子

変数への代入処理を対象にして以下のように使える演算子もあります。(代入演算子と呼びます。)

演算子利用例(aを20、bを6とする)同じ意味の記述
+=a+=b; (aは26となる)a=a+b;
-=a-=b; (aは14となる)a=a-b;
*=a*=b; (aは120となる)a=a*b;
/=a/=b; (aは3となる)a=a/b;
%=a%=b; (aは2となる)a=a%b;

++, -- 演算子

変数への 1 ずつの加減算については以下のように使える演算子もあります。(インクリメント(加算)、デクリメント(減算)演算子と呼びます。)

演算子利用例(aを20とする)同じ意味の記述
++a++; (aは21となる)a=a+1; や a+=1;
--a--; (aは19となる)a=a-1; や a-=1;

それ以外にインクリメント、デクリメント演算子は ++a 、--a のように使うこともできます。違いは演算処理のなかで使われたときです。++a は演算に先立って加算され、a++ は演算の後で加算されます。慣れないうちは a++; のように、一行だけの記述で、つまり a=a+1; の代わりに使うのがよいでしょう。

「演算に先立って」「演算の後で」ということの違いがはっきりする例を以下に示しておきます。a++ と ++a で結果が 120 と 126 と違いがでていますね。興味のある人は何が起きているか考えてみると良いでしょう。

プログラム例

main(){
  int a,b;
  a = 20; b = 6 * a++;
  printf(" a++ 実行後 a=%d, b=%d\n", a,b);
  a = 20; b = 6 * ++a;
  printf(" ++a 実行後 a=%d, b=%d\n", a,b);
}

実行結果

 a++ 実行後 a=21, b=120
 ++a 実行後 a=21, b=126

6 * a++ は 6 と掛け合わされたあとで加算され、6 * ++a は 6 と掛け合わされる前に加算されています。

型変換

計算式は常に型を意識する必要があります。たとえば以下の例では、希望としては 0.165 ...が rate に代入されてほしいところですが、実際には 0.0 が代入されます。

  int hit;
  float rate;
  hit=20;
  rate=hit / 121;

理由は hit と 121 はともに整数型なので、hit / 121 の演算は整数演算として処理され、結果が 0 となり、それが実数型に変換されて rate に格納されたためです。

こうした場合は、演算が実数型で行われるように演算途中で以下のように明示的に型の指定をします。

  rate=(float)hit / 121.0;

例では変数の前に「( )」でくくって型指定を入れています。また、121 は整数型でしたが 121.0 と明示的に実数型であることを示しています。これで rate には 0.165... が代入されます。
型はある程度自動的に合わせてくれますから、もし演算の片側が実数型の場合は、その精度に合わせて自動的に実数型で演算をしてくれます。つまり rate=hit / 121.0; または rate=(float)hit / 120; とだけ書いても構いません。

逆に実数型の計算結果を整数型に型変換すると、その結果は少数以下を切り落としたものになります。つまり以下の計算結果は 2.857 ではなく2 になります。

  int a;
  a=20.0/7.0;

また、計算途中で明示的に整数化することもできます。若干トリッキーな記述になってしまいますが、以下の計算結果は 3.5 になります。どこからどこまでが整数型で演算され、どこから実数型となったのか追いかけてみると良いでしょう。

  float b;
  b=(int)(20.0/7.0)+1.5;

このようなトリッキーな記述はバグを発生させがちで、あまりするべきではありませんが、実数変数と整数変数を混在させて演算させていると、つい型が異なる演算で少数以下の扱いが違っていることに気づかず、計算が合わなくて困ることがあります。
トリッキーな記述は避けるとしても、知識として演算には型があり、こうしたことが発生する、と言うことを覚えておくとよいでしょう。

5. 条件分岐

5.1 if 文

たとえば変数 a の値がゼロだったときに一行メッセージを表示する場合は、以下のように if 文を使います。

  if( a == 0 ) {
     printf("ゼロでした\n");
  }

if に続いて ( ) で囲まれたなかの条件判定が真 (yes) だった場合(条件判定が成立した場合)、それに続く { } で囲まれたブロック内を実行します。判定が負 (no) だった場合はそのブロックを飛ばして、次の行の処理を行います。

if文

if 文による処理の分岐

else 節

条件判定が偽だったとき(上の例ではゼロではなかった場合)に、別の処理をさせたければ以下のようにします。

  if( a == 0 ) {
     printf("ゼロでした\n");
  } else {
    printf("ゼロ以外です\n");
  }

else 句

else 句による否定側への処理の設定

5.2 条件記述

条件判定部分には以下のような記述ができます。

==,!=,>,< などの関係演算子

数値などの大小比較について以下のような演算子が利用できます。

演算子意味利用例
==等しいif (a==b) { ... }
!=等しくないif (a!=b) { ... }
>左辺が大きいif (a>b) { ... }
>=左辺が等しいか大きいif (a>=b) { ... }
<左辺が小さいif (a<b) { ... }
<=左辺が等しいか小さいif (a<=b) { ... }

==, != を等値演算子、>, < などを関係演算子と呼んでいます。
これらより算術演算子のほうが優先度が高いため、
if(a < b-1) という記述は
if( a < (b-1) ) と同じとみなされます。

(左から順に処理されてまず a < b が先に処理されるようにはなりません。)

||, && などの論理演算子

複数の条件を並べて判定したい場合は以下のように書きます。

演算子意味利用例
&&AND
(両方の条件が成立したら)
if (a==b && a<100) { ... }
(aとb が等しく、かつ a が 100 未満なら真)
||OR
(どちらかの条件が成立したら)
if (a==b || a<100) { ... }
(aとb が等いか、または a が 100 未満なら真)
!NOT
(条件の反転)
if (! a==b) { ... }
(aとb が等しくなければ真)

&& や || を論理演算子と呼んでいます。否定の ! は否定演算子と呼ばれています。
これらは関係演算子よりさらに低い優先度が設定されているので、
if( a < b && c > d )
if( ( a < b ) && ( c > d ) ) として処理されます。
また、
if( a < b-1 && c + 2 > d -5 )
if( ( a < ( b-1 ) ) && ( ( c + 2 ) > ( d -5 ) ) として処理されます。

(なるべくバグを発生させない、プログラマの勘違いを誘発させないようにするために、暗黙の優先順位に依存した複雑な論理式を書くより、( ) を明示的に使ってわかりやすい記述を心がける方がよいでしょう。)

5.3 switch case 文

たとえば変数 a の値が 0, 1, 2, それ以外だった場合、それに応じて出力するメッセージを変えたい場合、switch 文を使ってこう書くことができます。

  switch( a ) {
  case 0:
    printf("グー\n");
    break;
  case 1:
    printf("チョキ\n");
    break;
  case 2:
    printf("パー\n");
    break;
  default:
    printf("反則\n");
  }

switch に続く ( ) に判定に用いる変数や演算式を書き、{ } に囲まれたなかに列挙する case の後に書いた値(変数や演算式は使えません)と等しいかどうかで条件判定が行われます。

等しかった場合はその case の後の行から処理をはじめ、break があると switch 文を抜けて終わりの } の次の行に処理を移します。
等しくなかった場合は次の case 句の判定を行います。
そうやって判定が最後まで成立しなかった場合は default 句の後の行から処理をはじめます。default 句は省略可能で、省略した場合にいずれの case にも該当しなかった場合は何もせず switch 文の { } ブロックの次の行に処理を移します。

switch 文

switch 文による処理の分岐

注意するべきは case 句の終わりを意味する break で、これがないと処理は次の case 句の中に食い込んでしまいます。例えば case 0: の break が無かった場合には、printf("グー\n"); を実行したあと、switch 文から抜けずに case 1: の printf("チョキ\n"); の処理をはじめてしまいます。case 句にはそれぞれ終わりはなく、単純に break に遭遇するまで次々と処理を下に向かって行うようにできています。
default 句の最後にも break を書いて構いませんが、あってもなくても switch から出ていくことには違いがないので、書く必要はありません。

(参考)if 文での記述例

同じような判定処理は if 文を使ってこう書くことができます。

  if( a == 0 ) {
    printf("グー\n");
  } else {
    if( a == 1 ) {
      printf("チョキ\n");
    } else {
      if( a == 2 ) { 
        printf("パー\n");
      } else {
        printf("反則です\n");
      }
    }
  }

if 文による入れ子

if 文を多重(入れ子構造)に使った記述例

else if 句を使ってこのようにも書けます。

  if( a == 0 ) {
    printf("グー\n");
  } else if( a == 1 ) {
    printf("チョキ\n");
  } else if( a == 2 ) { 
    printf("パー\n");
  } else {
    printf("反則です\n");
  }

else if 句による分岐

else if 句を使った記述例

わかりやすい、適した記述を使い分けるようにすると良いでしょう。

6. ループ

繰り返し(ループ)は基本動作の一つです。ループと条件分岐を組み合わせてコンピュータは自動処理をしていると言っても間違いではないほどです。

6.1 while によるループ

a が 100 以下の間、繰り返し続ける処理は while を用いて以下のように書けます。

  a=30;
  while( a <= 100 ) {
    ..... (目的の処理)
  }

while に続く ( ) の中の条件判定が真である限り、続く { } に囲まれたブロックを繰り返して処理します。最初に while() に入るときにすでに条件判定が偽になっていた場合は、一度もブロック内の処理をせずに、その次の行を実行します。

whileによるループ

while によるループ

上の「目的の処理」部分には、a を(いつか)100 以下に変化させるようなものが何か含まれているでしょう。そうでないと、このループは永遠に止まりませんから。
同様に、まず while を実行する前に a には何か値が設定されているはずです。そうでないと、ループに入る前の最初の判定ができませんから。
(ループ後に判定をしたい場合はdoとwhileによるループの項を参照。)

無限ループ

明示的に永遠に止まらないループを作りたくなる場合があります。そういうときは以下のように書くと良いでしょう。

  while( 1 ) {
    ..... (目的の処理)
  }

while(1)による無限ループ

while(1) による無限ループ

条件判定文における 1 は「真」を意味するため、このループは永遠に止まりません。これを無限ループといいます。
処理をただしく終了させるためには、break によるループからの脱出 などを用意する必要があります。

break によるループからの脱出

while 文の途中で、何らかの条件判定の結果によってループから脱出したくなった場合は、以下のように書きます。

  a=30;
  while( a <= 100 ) {
    ..... (目的の処理前半)
    if( b == 10 ) {
      break;
    }
    ..... (目的の処理後半)
  }
  ..... (ループ後の処理)

breakによるループからの脱出

breakによるループからの脱出

break は直前の while 文や do while文(後述)、for 文 (後述) からの脱出を意味します。つまり「目的の処理前半」を終えて、 b が 10 になっていたら、後半の処理をせず、while ループから抜けて「ループ後の処理」部分に処理が移ります。

continue による途中処理のスキップ

逆に、ループからは脱出せず、ループの末尾までスキップしてから繰り返し処理を再開する指示を continue 文で書けます。

  while( a <= 100 ) {
    ..... (目的の処理前半)
    if( b == 10 ) {
      continue;
    }
    ..... (目的の処理後半)
  }
  ..... (ループ後の処理)

continueによる途中処理のスキップ

continueによる途中処理のスキップ

continue は、while, do, for などから脱出するのではなく、それ以降の処理を次の繰り返し処理までスキップします。具体的には「目的の処理前半」を終えて、 b が 10 になっていたら、後半の処理をせず、while ループの繰り返し処理から再開します。つまり a <= 100 の判定を行い、真なら「目的の処理前半」へ、偽ならwhile ループから抜けて「ループ後の処理」部分に処理が移ります。

6.2 do と while によるループ

a が 100 以下の間、繰り返し続ける処理は do と while を用いて以下のようにも書けます。

  do {
    ..... (目的の処理)
  } while( a <= 100 );

最後に書かれた while に続く ( ) の中の条件判定が真である限り、do から直前までの { } に囲まれたブロックを繰り返して処理します。最初に while() に入るときにすでに条件判定が偽になっていた場合は、一度もブロック内の処理をせずに、その次の行を実行します。

do while によるループ

do while によるループ

while によるループとの違いは条件判定がループの最初にあるか、最後にあるかです。do while の場合は最低一回、最初の一度は必ずループ内の処理を通過します。
breakcontinue による制御もwhileによるループと同様に機能します。

6.3 for によるループ

ある変数が 1 から 100 までひとつずつ変化するあいだ繰り返すような処理を書くには for 文が便利です。

  for( a=1; a <= 100; a++ ) {
    .... (目的の処理)
  }

for に続く ( ) の中には文を三つ書きます。(二つの ; セミコロンで三つの文に区切られている点に注目)
一文めが初期処理、二文めが条件判定記述、三文めが毎回処理です。処理の流れは以下のようになります。

  1. a に 1 が代入され、
  2. a が 100 以下かどうかを判定し、条件が成立すれば(この例では最初 a は 1 なので必ず成立する)
  3. for( ) に続く { } のブロックを実行し、
  4. 最後に自動的に a++ によって a に 1 加算し、
  5. ループする(つまり 2. に処理が戻る)

for によるループ

for によるループ

for に続く ( ) の中で使う変数は必ずしも一つに揃える必要はなく、単に初期処理、条件判定、毎回処理の三つの処理だと考えて下さい。これは for() が while によるループの特殊な形であると考えることでわかりやすくなるでしょう。
つまり for() は以下のような while() による記述を一行で書けるよう変形したものなのです。

  a=1;
  while( a <= 100 ) {
    ... (目的の処理)
    a++;
  }

7. 配列変数

C 言語では a[10] というような表現で、配列変数が利用できます。以下に簡単な概要説明と扱い方、文法上の規則について説明します。

7.1 添え字、配列の考え方

以下に整数型の変数 a を、要素数 5 の配列として宣言し、要素 2 に 100 を代入する例を示します。

  int a[5];
  a[2]=100;

結果的に a[0], a[1], a[2], a[3], a[4] の 5 つの変数領域が確保されました。要素は 0 番から数えるため、a[2] は先頭から数えて 3 番目の要素となります。この要素を指定する [ ] に囲まれたところに指定する数値を「添え字(そえじ)」と呼びます。

配列変数

配列変数の要素への代入

変数と計算式の項で示した、単純な変数に値を代入する図と比較するとわかりやすいでしょう。

ところで一行目の int a[5] による配列変数の宣言と、二行目の a[2]=100 による要素への値の代入の両者における [ ] 内の数字の意味が微妙に異なることに注意して下さい。前者は「要素が 5 つある」ことを意味し、後者は添え字であり「要素 2 (先頭から数えると 3 番目の要素)」を意味しています。後者の意味での a[5] という要素はありません(添え字に 5 を指定してはいけません)。

配列を使う処理の例

以下に要素 100 の整数型配列 x[ ] をとり、そこに値をひとつずつ入れていく例を示します。

  main() {
    int i, x[100];
    for(i=0; i<100; i++) {
      x[i]=i;
    }
  }

7.2 文字列変数

C 言語では文字列を扱うための型は用意されておらず、代わりに文字型の配列を利用します。以下に文字列を扱うための文字型配列変数 s を確保し、文字列を格納する例を示します。

  char s[10];
  strcpy(s, "Sample");

まず char s[10] という記述によって、s という名前で 10 要素(10文字、10バイト)の配列を確保しています。要素は 0 番から 9 番までの 10 個です。(strcpy関数などの文字列処理関数についてはそれぞれ別項を参照)

s の実体

C 言語では "Sample" というような文字列そのものが文字型の配列です。'S','a','m','p','l','e','\0' という 7 つの文字の配列だと思って下さい。('\0' は文字列の終わりを示す特殊文字の表現です。具体的にいうと、一文字あたり一バイトなので、その一バイトの中身が 0 、ということです。)

Sample という文字列

続く strcpy(s, "Sample") で、そこに Sample という文字列を複写しています。

文字列の複写

配列変数を s[3] のような形ではなく、s だけの名前で利用している点に注意して下さい。その意味についてはポインタについての概念を理解する必要があるので今は説明しません。

作った文字列変数の中身を表示するには以下のようにします。そこでも s とだけ書かれています。

  printf("文字列は %s になりました\n", s );
  printf("長さは %d です\n", strlen(s) );
  printf("一番目の要素は %c :四番目は %c です\n", s[0], s[3] );

上の例なら、表示結果は以下のようになるでしょう。

文字列は Sample になりました
長さは 6 です
一番目の要素は S :四番目は p です

8. 関数

8.1 戻り値と引数

関数を呼び出す時は以下のように書きます。(下の例の abs() 関数は与えられた数値の絶対値を返す関数。絶対値とは符号を抜いた値で、例えば -3 の絶対値は 3 です。3 の絶対値はそのまま 3 。)

  a=abs(b);

関数には戻り値 [1] (もどりち)と引数(ひきすう)があります。上の例では b を引数として abs() 関数を呼び出したので、変数 a には戻り値として b の絶対値が代入されます。
戻り値と引数とはそれぞれ型がありますから、どの関数が、どの型で引数を渡し、どの型で結果を戻すのが良いのか、あらかじめ知っておく必要があります。上の例の abs() 関数は引数、戻り値ともに整数型です。 [2]

関数は計算式の中に書けるので、以下のような記述も可能です。

  if ( ( abs(b) + x ) > 100 ) {
    .....  (目的の処理)
  }

また、一般に戻り値を利用せず、関数の中で実行される処理だけに注目するタイプの関数もあります。いままでときどき出ていた printf() などです。戻り値が必要ない(戻り値を利用しない)場合は以下のようにただ関数名だけ書いて呼び出します。

  printf("試しに表示してみましょう。\n");

ライブラリ

関数を利用する際にはライブラリの include が必要な場合があります。それぞれの関数がどのライブラリに含まれているか、これから示す関数一覧にそれぞれつけておきますので、必要に応じて include すると良いでしょう。

一覧表の見方

以下に標準的に用意されている関数の代表的なものを示しておきます。

#include <stdlib.h>
int abs(int a)

このように書いてあった場合、以下のことを意味しています。

  1. 一行目はライブラリとして stdlib.h を include する必要がある
  2. int abs で関数名と、その戻り値が整数型であることを示し、
  3. ( ) の中の表記で引数が一つ必要で、それが整数型であることを示す


8.2 算術関数

算術演算に使える関数の代表的なものを示しておきます。

abs 絶対値を求める

#include <stdlib.h>
int abs(int x)

第一引数の絶対値を返します。

実数、それも double 型用の絶対値取得のための関数は fabs() です。実数、それも float 型の絶対値を求めるためには fabsf() が用意されています。

rand 乱数を求める

#include <stdlib.h>
int rand()

呼び出すたびにランダムに異なる数値(乱数といいます)を返します。引数はなく、返す値の幅は 0〜2の31乗(2147483648)未満です。
ただしこれは疑似乱数と呼ばれる種類の乱数で、最初に srand() 関数を利用して、乱数発生の種(seed)を初期値として設定する必要があります。

srand 乱数の種を与える

#include <stdlib.h>
srand(int seed)

rand() 関数に必要な疑似乱数を発生させるための種(seed)を整数型の引数ととして与えます。戻り値はありません。
具体的には以下のような組み合わせで使います。最初に一度 srand() をランダムな数値で呼び出し(例では利用者が手で思いついた数字を入力する)、それ以降繰り返して rand() 関数を呼べば、そのたびごとに違う結果が得られます。

#include <stdio.h>
#include <stdlib.h>
main() {
  int seed, i, r;
  printf("please set the seed :");
  scanf("%d",&seed);

  srand(seed);  /* seed の設定 */
  for(i=0;i<10;i++) {
    r=rand();
    printf("%ld\n", r);
  }
}

floor 実数部分を切りおとす

#include <math.h>
double floor(double x)

0.5 を渡すと 0.0 が返ります。引数より大きくない最大の整数と考えて下さい。

いわゆる(小数点第一位での)四捨五入は 0.5 を加えて切り落とすと考えればよいでしょう。

  y=floor(x + 0.5);

ceil 実数部分を切りあげる

#include <math.h>
double ceil(double x)

0.5 を渡すと 1.0 が返ります。引数より小さくない最小の整数と考えて下さい。

pow 累乗を求める

#include <math.h>
double pow(double x, double y)

x の y 乗を計算します。

sqrt 平方根(ルート)を求める

#include <math.h>
double sqrt(double x)

x の平方根(ルート、1/2 乗)を計算します。

sin 正弦を求める

#include <math.h>
double sin(double x)

x の正弦値を求めます。ただし x はラジアン単位です。0〜360 の度数で計算させたいときは以下のようにすると良いでしょう。

  double r, d;
  d=45.0; /* 角度は 45 度 */
  r= 3.14159265 / 180.0 * d; /* ラジアン単位に変換 */
  y=sin(r); /* 正弦を計算 */

cos 余弦を求める

#include <math.h>
double cos(double x)

x の余弦値を求めます。ただし x はラジアン単位です。

例えば sin, cos の値を 30 度ごとに求めるには以下のようにすると良いでしょう。

/* radian 単位 ( 30°は 1/6π ) に変換 */
#include <stdio.h>
#include <math.h>
main() 
{
  int i;
  double r, d;
  /* 0-360 まで 30 度ずつ変化 */
  for(d=0.0; d<=360.0; d+=30.0) {
    r= 3.14159265 / 180.0 * d ;  
    printf("d=%5.1f r=%4.2f sin=%6.3f cos=%6.3f\n",
           d,r,sin(r), cos(r));
  }
}

実行結果

d=  0.0 r=0.00 sin= 0.000 cos= 1.000
d= 30.0 r=0.52 sin= 0.500 cos= 0.866
d= 60.0 r=1.05 sin= 0.866 cos= 0.500
d= 90.0 r=1.57 sin= 1.000 cos= 0.000
d=120.0 r=2.09 sin= 0.866 cos=-0.500
d=150.0 r=2.62 sin= 0.500 cos=-0.866
d=180.0 r=3.14 sin= 0.000 cos=-1.000
d=210.0 r=3.67 sin=-0.500 cos=-0.866
d=240.0 r=4.19 sin=-0.866 cos=-0.500
d=270.0 r=4.71 sin=-1.000 cos=-0.000
d=300.0 r=5.24 sin=-0.866 cos= 0.500
d=330.0 r=5.76 sin=-0.500 cos= 0.866
d=360.0 r=6.28 sin=-0.000 cos= 1.000

tan 正接を求める

#include <math.h>
double tan(double x)

x の正接値を求めます。ただし x はラジアン単位です。

8.3 標準入出力関数

printf など、画面表示や入力に使える関数の代表的なものを示しておきます。

printf による文字表示

#include <stdio.h>
int printf(char *s, ...)

戻り値はありますが、あまり利用することはないでしょう。第一引数 s には文字列が入り、そこでの書式指定に従ってそれ以降の引数が処理され、画面に表示されます。

もっとも単純な例は以下のように第二引数以降がないものでしょう。

  printf("一行表示する\n");

ただ画面にそのとおり「一行表示する」とだけ表示されます。「\n」は改行指定(後述)です。
変数や計算式の内容を表示するためには以下のように使われます。

  printf("個数=%d, 平均=%f6.3\n",cnt, (float)cnt/(float)total );

実行結果例(cnt=3, total=20 のとき)

  個数=3, 平均= 0.150

書式指定文字列(第一引数)のなかにある % に続く d や f の指示に従って、第二引数以降の変数の値や計算式の結果が「そこに埋め込まれる」ような形で表示されます。

以下にこの書式指定に使える制御文字と変換文字列についてまとめておきます。

printf の制御文字

printf() 関数の書式指定文字列には改行などの制御文字が含められます。代表的なものを以下にあげておきます。(環境によっては制御文字の目印は「¥」に見えたり「\」に見えたりすると思いますが、どちらでも(恐らく)問題ありません。)

文字意味使用例その結果
\n改行printf("\n");(単に一行空ける)
  printf("一行で\n二行ぶん\n"); 一行で
二行ぶん
\\\ を表示printf("1$ = \\120 \n");1$ = \120
\"" を表示printf("嵐山\"スパタ\"甚五郎\n");嵐山"スパタ"甚五郎
\'' を表示printf("高尾\'サントス\'三十郎\n");高尾'サントス'三十郎

上にあげたものでは、改行以外は C の文法上そのままでは使えない記号「"」「'」「\」を表示するために使われています。なお「%」も、書式指定文字列のなかでの変換文字列を示すものとして使われていますので、そのままでは使えません。そのときは以下のようにします。

文字意味使用例その結果
%%%を表示printf("確率何%%だろう?\n");確率何%だろう?

printf の変換文字列

書式指定文字列のなかで %d のように書くことで、その部分を変数の内容や計算式の結果で置き換えて表示させることができます。

  printf("個数=%d, 平均=%f6.3\n",cnt, (float)cnt/(float)total  );

実行結果例(cnt=3, total=20 のとき)

  個数=3, 平均= 0.150

といった具合いです。この置き換えられる部分のことを変換文字列と呼んでいます。変換文字列は表示する型に応じて多くの種類があります。以下に代表的なものをしめします。

変換文字列意味使用例その結果
%d整数を表示printf("[%d]\n",10);[10]
  printf("[%5d]\n",10);[   10] (5桁で表示して不足分は空白で埋める)
  printf("[%05d]\n",10);[00010] (5桁で表示して不足分は0で埋める)
%f実数を表示printf("[%f]\n",12.345);[12.345000](小数部分の桁数は不定)
  printf("[%9.5f]\n",12.345);[ 12.34500](小数点含めて全体が9桁、小数以下が5桁、整数部分は空白を左詰め、小数部分は0を右詰め)
%c文字を表示printf("[%c]\n",'a');[a]
%s文字列を表示printf("[%s]\n","sample");[sample]
  printf("[%10s]\n","sample");[    sample](10桁で表示して不足は右寄せ)
  printf("[%-10s]\n","sample");[sample    ](10桁で表示して不足は左寄せ)
%x整数を16進で表示printf("[%x]\n",1234);[4d2]

scanf による文字入力

#include <stdio.h>
int scanf(char *s, ...)

キーボードから文字や数値を入力して変数に格納します。以下の例は整数型の変数 a に数値を入れる場合の記述です。戻り値はあまり使うことはないでしょう。第一引数が書式指定の文字列、それ以降に格納する変数が並びます。

  printf("数値を入れてEnterキーを押す:");
  scanf("%d", &a);
  printf("入力は %d でした\n",a);

実行すると画面に「数値を入れてEnterキーを押す:」と表示され(このprintf() 文には改行がないので)すぐ右の位置でカーソルが表示されるでしょう。そこで数値を入力して、Enter キーを押すと、「入力は ** でした」と、入力した数値が表示されるので、うまく変数に値が格納されたことが確認できるでしょう。

第一引数である書式指定文字列の書き方はprintfとほぼ同じです。printf の変換文字列などを参考にして下さい。それ以降に引数として与える変数には数値型もしくは文字型であれば & をつけるようにしてください。文字列を入力したい場合の扱いはちょっと難しくなりますのでこの場ではサンプルを以下に示すにとどめておきます。 [1]

main() {
  char s[64];
  printf("文字列を入力して Enter:");
  scanf("%s", s);
  printf("[%s]\n",s);
}

実行結果

文字列を入力して Enter:あらま (←「あらま」はユーザが入力した)
[あらま]


8.4 文字列処理関数

C 言語における文字列の扱い

C 言語では文字列は文字型変数の配列として処理されます。つまり文字列処理関数は文字型の配列変数の処理をやっているのです。配列変数のことを理解していないと難しいかもしれませんので、例を多く示しておきます。(文字列変数配列変数の項も参照)

strlen 文字列の長さを調べる

#include <string.h>
int strlen(char *s1)

文字列の長さを返します。戻り値は整数型です [1]

('\0' が何番目にあるかを数えているのですが、先頭を 0 番目と数えるので、一文字も含まれていない場合(最初の文字が '\0' だった場合)は長さを 0 と返し、直感と一致します。)

strcpy 文字列の複写

#include <string.h>
char *strcpy(char *s1, char *s2)

文字列 s1 に、文字列 s2 の内容をコピーします。s2 の末尾にある '\0' も含めてコピーされます。戻り値は s1 を指しています。

strncpy 上限付きの文字列の複写

#include <string.h>
char *strncpy(char *s1, char *s2, int n)

文字列 s1 に、最大 n 文字を上限として文字列 s2 の内容をコピーします。s2 の末尾を意味する '\0' が n 番目以降にあった場合、'\0' はコピーされないので末尾処理に注意が必要です。戻り値は s1 を指しています。

strcat 文字列の追加

#include <string.h>
char *strcat(char *s1, char *s2)

文字列 s1 に、文字列 s2 の内容を追加します。s1 の末尾にあった '\0' を取り除き、そこから s2 の内容を、s2 の末尾にある '\0' も含めてコピーします。戻り値は s1 、つまり追加された後の文字列を指しています。

strncat 上限付きの文字列の追加

#include <string.h>
char *strncat(char *s1, char *s2, int n)

文字列 s1 に、最大 n 文字を上限として文字列 s2 の内容を追加します。s1 の末尾にあった '\0' を取り除き、そこから s2 の内容をコピーしていきますが、s2 の末尾を意味する '\0' が n 番目以降にあった場合、'\0' はコピーされないので末尾処理に注意が必要です。戻り値は s1 、つまり追加された後の文字列を指しています。

strcmp 文字列の比較

#include <string.h>
int strcmp(char *s1, char *s2)

条件判定に用います。文字列 s1 と s2 が等しければ 0 を、そうでなければ 0 以外を返します。典型的には以下のようにして使います。

  if( strcmp( s1, s2 ) == 0 ) { 
    printf("一致しました\n");
  } else {
    printf("違います\n");
  }

strncmp 上限付き文字列の比較

#include <string.h>
int strncmp(char *s1, char *s2, int n)

条件判定に用います。文字列 s1 と s2 を、n 文字までの範囲で比較して等しければ 0 を、そうでなければ 0 以外を返します。


9. GrWin による描画

GrWin は溜渕継博(TMARIBUCHI Tsuguhiro)氏によって開発された Windows 用のグラフィクス・ライブラリです。FORTRAN および C 言語のためのインタフェイスが用意されています。C 言語処理系としては私がクラスで利用させてもらっている MinGW 以外にも多くのものが利用できます。
詳細は http://spdg1.sci.shizuoka.ac.jp/grwinlib/ を参照。

今回の実習では、担当講師がより簡単な描画による実習ができるように、幾らか機能追加しています。そうやって追加された関数を説明する際には「Y拡張機能」と書き足しておきます。標準的な GrWin にはない機能ですので、自分で GrWin をインストールして試すような場合は注意してください。

9.1 最も簡単なサンプル

以下に利用例を示します。(10,10) を左下に、(200,30) を右上にして、色番号 1 (濃い緑色)の長方形を描く、というものです。

#include <stdio.h>
#include "GrWin.h"

int main() {
  GWopen(0); /* 描画ウィンドウを開く */
  GWindow(0.0, 0.0, 200.0, 200.0); /* 座標系の設定 */

  GWsrect(10.0, 10.0, 200.0, 30.0, 1); /* 長方形 */

  GWquit(); /* 描画ウィンドウを閉じる */
  return 0;
}

コンパイルし、実行すると以下のような描画ウィンドウが開き、結果が表示されます。

描画結果

このプログラムはただ長方形を描いてすぐ終了するため、
GWquit による以下のようなダイアログが表示されているでしょう。

終了ダイアログ

「はい」をクリックすると描画ウィンドウを閉じます。
(サンプルプログラムは GWquit() 直後の return 文によってすぐ終了しますので、プログラムの実行はこのダイアログが出た時点で既に終了していることに注意して下さい。この状態はただ描画結果を保持しているだけです。)

プログラムを見ると、座標系が左下を原点にしていることがわかると思います。

座標系

9.2 基本的な関数

GWopen 描画ウィンドウを開く

GWopen(win)

描画ウィンドウを開く。
引数 win は整数。0 を指定すると新規に描画ウィンドウを作成して開く。0 以上の場合は既存の一度閉じられたウィンドウ番号と見なし、そのウィンドウを開く。
戻り値は作成されたウィンドウ番号(0以上)にあたる。作成に失敗すれば 0 が返る。

GWclose 描画ウィンドウを閉じる

GWclose(win)

引数 win は整数型。ウィンドウ番号 win の描画ウィンドウを閉じる。
win に 0 を与えた場合はカレントウィンドウ(最も上のウィンドウ)を閉じる。
win に負(-1など)を与えた場合は、開いている全ての描画ウィンドウを閉じる。
戻り値として指定されたウィンドウを閉じた後にカレントとなったウィンドウ番号(整数型)が返る。全てのウィンドウが閉じてしまった場合は 0 が返る。

GWclear 画面を消去する

GWclear(c)

引数 c で与えられた色で描画ウィンドウを塗りつぶす。
c に -1 を指定すると白色で塗りつぶすとともに過去の描画情報を全て消す。
通常は GWclear(-1) として使うと良いだろう。

GWquit 終了処理をする(簡易版)

GWquit()

終了ダイアログを表示し、その応答を待ってからすべての描画ウィンドウを閉じて終了処理をする。GWquitx(1) とした場合と等価。

GWquitx 終了処理をする

GWquitx(n)

引数 n は整数。n が 0 であれば即座にすべての描画ウィンドウを閉じて終了処理をする。n が 1 であれば終了ダイアログを表示し、その応答を待ってからすべての描画ウィンドウを閉じ、終了処理を行う。

GWleave 描画ウィンドウを閉じずに終了

GWleave()

描画ウィンドウを閉じないままで終了処理を行う。
なお、GWquit, GWquitx や GWleave を行わずにプログラムを終了した場合も同様に描画ウィンドウは閉じられないままプログラムが終了する。

9.3 図形の描画に関する関数

GWsetpxl 点を打つ

GWsetpxl(x, y, c)

座標位置 (x, y) に点を置く。
引数 x, y は float 型。引数 c は整数型で色を指定する。
色番号については色の設定処理を参照。

GWxline 線を引く

GWxline(x1, y1, x2, y2, c)

座標位置 (x1, y1) と (x2, y2) を直線で結ぶ。
引数 x1, y1, x2, y2 は float 型。引数 c は整数型で色を指定する。
色番号については色の設定処理を参照。
(Y拡張機能)

GWxrect 四角形を描く(中空)

GWxrect(x1, y1, x2, y2, c)

座標位置 (x1, y1) と (x2, y2) を対向する頂点とする四角(中は塗りつぶさず枠だけ
)を描く。
引数 x1, y1, x2, y2 は float 型。引数 c は整数型で色を指定する。
(Y拡張機能)

GWsrect 四角形を描く(塗る)

GWsrect(x1, y1, x2, y2, c)

座標位置 (x1, y1) と (x2, y2) を対向する頂点とする塗りつぶされた四角を描く。
引数 x1, y1, x2, y2 は float 型。引数 c は整数型で色を指定する。

GWcircle 円を描く(中空)

GWcircle(x1, y1, x2, y2, c)

座標位置 (x1, y1) と (x2, y2) を対向する頂点とする四角形に内接する円(中は塗りつぶさない)を描く。
引数 x1, y1, x2, y2 は float 型。引数 c は整数型で色を指定する。
(Y拡張機能)

円

GWscircle 円を描く(塗る)

GWscircle(x1, y1, x2, y2, c)

座標位置 (x1, y1) と (x2, y2) を対向する頂点とする四角形に内接する塗りつぶされた円を描く。
引数 x1, y1, x2, y2 は float 型。引数 c は整数型で色を指定する。
(Y拡張機能)

GWxarc 円弧を描く

GWxarc(x1, y1, x2, y2, r1, r2, c)

座標位置 (x1, y1) と (x2, y2) を対向する頂点とする四角形に内接する円弧を描く。
円弧の開始角と終了角は r1, r2 でそれぞれ行う。0〜360度を 0.0〜1.0 として、水平位置から反時計回りに r1 移動したところから、r2 ぶんだけ描く。
引数 x1, y1, x2, y2 は float 型。引数 c は整数型で色を指定する。
(Y拡張機能)

円弧

上のサンプルであれば左下角の座標が (x1, y1), 右上の角が (x2, y2) となり、r1, r2 がそれぞれ 30 度、90 度あたりを指しているとすれば、r1 = 30 / 360 でおよそ 0.083 , r2 = 90/360 = 0.25 あたりとなるだろう。

GWxpie 扇形を描く

GWxpie(x1, y1, x2, y2, r1, r2, c)

座標位置 (x1, y1) と (x2, y2) を対向する頂点とする四角形に内接する扇形(円弧の内部を扇形に塗りつぶしたもの)を描く。
円弧の開始角と終了角は GWxarc に同じ。
(Y拡張機能)

GWpolygon 多角形を描く

GWpolygon(points, n, m)

実数型の配列変数 points に描画したい多角形の頂点座標 (x, y) を複数格納していく。配列については配列の項を参照。詳しい使い方については多角形の描画にサンプルと解説があるのでそちらを参照すると良い。

引数 points は float 型の配列変数。引数 n は整数型で頂点の数。引数 m も整数型で、塗りつぶしモードを指定する。m=0 で通常、m=1 で Winding モードだが通常は 0 で良い。

9.4 色などを設定する関数

GWcolor 色を設定する

GWcolor(c, d) 色を設定する

色を設定する。引数 c, d はともに整数。色 c を対象 d に設定する。

色番号は以下の通り。

01栗色2暗い緑3オリーブ4濃紺
56緑青7灰色8明るい緑9薄い水色
10薄い灰色11青灰色12濃い灰色1314
151617赤紫18水色19
                                   

設定対象は以下の通り。

1前景色2背景色3ペンの色4ブラシの色
5マークの色6マークの背景色7テキストの色8テキストの背景色
9記号の色10記号の背景色11ピクセル色
                                    

GWcolor で効果がないとき

以下の手続きを試してみると良い。(第一引数 c には色番号を与える。)

  GWsetpen(c, -1, -1, 4);
  GWsetbrs(c, 1, -1);

上の例は中を塗りつぶす場合の設定。中を塗らない場合は GWsetbrs の指定を以下のようにすると良い。

  GWsetpen(c, -1, -1, 4);
  GWsetbrs(c, 0, -1);

GWkrgb RGB による色指定

GWkrgb(r, g, b)

引数 r, g, b はそれぞれ整数型。0 から 255 までの範囲で RGB (Red, Green, Blue) の三原色による色指定を行う。たとえば黒は全ての原色について発色がゼロの (0,0,0)、白は全ての発色が最大の (255,255,255) となる。赤は Red が最大でそれ以外が無い (255, 0, 0) であり、黄は Blue, Green が最大で赤が最低の (0, 255, 255) である。

結果は整数型の戻り値として与えられ、これを次の描画関数の色指定のために使う。具体的には例えば以下のようにして使う。

  int c;
  c=GWkrgb(100, 120, 20);
  GWsrect(10.0, 10.0, 20.0, 100.0, c);

細かく色を設定しながら絵を描くことで、例えば以下のようなグラデーションも表現することができる。

rgb

9.5 そのほかの関数

GWputtxt 文字を表示する

GWputtxt(x, y, &string)

引数 x, y は float 型。引数 s は文字列(文字配列変数へのポインタ)。
座標位置 (x, y) に string で指定された文字列を表示する。

字体やサイズの指定は GWsettxt を利用するが、ここでは指定が複雑なので説明しない。以下に色を設定する例示だけしておく。c1 が文字色、c2 が背景色である。

  GWsettxt(0, 1.0, 1, c1, c2, "");
  GWputtxt( 10.0, 20.0, "Hello World");

GWsleep 処理を一定時間停止する

指定された時間だけ処理を停止する(次の処理を待つ)。

GWsleep(s)

引数 s は整数型であり、これにミリ秒(1/1000)単位で待ち時間を与える。たとえば GWsleep(10) とすると 10 ミリ秒待つ。GWsleep(1000) で一秒待つ。

GWidle キー、マウスの入力を待つ

(描画ウィンドウに対して)キーを押すかマウスをクリックするまで処理を停止し、キーの場合はキー番号、マウスの場合はボタンの種類(左右)や座標位置を返す。

GWidle( &k, &x, &y, s )

整数型の第四引数 s によって、ミリ秒単位の待ち時間上限を設定できる。s によって指定された時間以内にキー、マウスの操作が無かった場合は第一引数 k (整数型)に 0 を設定して戻ってくる。
もし時間内にキーが押されたときには k に 1 以上のキーコードが設定される。もしマウスが押された場合、左ボタンなら -1 が、右ボタンなら -2 が設定される。
引数 x, y は float 型であり、マウスが押された場合はここに座標位置が設定される。

kbgetch キー入力を待つ

kbgetch(n)

(描画ウィンドウではなく)コンソールウィンドウを対称にキーが押されるまで待ち、押されたキーの番号が戻り値に設定される。型は整数。

引数 n は整数。n が 1 であれば入力をエコー( X とタイプすれば画面に X と表示する)する。0 であればエコーしない。

kbgetch によるキー入力は描画ウィンドウに対してではなく、プログラムを実行したウィンドウに対して行われるものを対象にしている点に注意。つまり test.c をコンパイルして test.exe ができていたとして、test.exe を DOS プロンプトから実行した場合に、この DOS プロンプトのウィンドウに対するキー入力が kbgetch 関数によって処理される。(つまり描画ウィンドウではなく DOS プロンプトのウィンドウを一度クリックして前面にもってくる必要があるだろう、ということ。)

9.6 サンプルプログラム

細かな機能などについて実例があった方が分かりやすいものについて、幾つかサンプルを示しておきます。

RGB 三原色による色の設定

GWkrgb() 関数を利用して細かな色指定をすることで、グラデーションなどが表現できる。
グラデーション例:
中間色、棒 rgb1.c 結果
パレット 1. rgb2.c 結果
パレット 2. rgb3.c 結果
色見本帳 rgb4.c 結果

多角形の描画

GWpolygon() 関数を利用して多角形を描くことが出来る。
例えば以下のような三角形を描く場合は、その頂点(例では三つ)の座標位置をそれぞれ配列変数 [1] に格納し、GWpolygon() に対して配列そのものと、要素の数を引数に指定する。

まず以下のサンプルプログラムを実行して、どのような記述でどのような図形が描かれるのか試すと良い。

サンプルプログラム poly.c 結果

プログラム解説

polygon

試みに上のような三角形を描く。この三角形の各頂点の座標位置は、それぞれ以下のような値になるだろう。

 ( 100.0, 100.0 )
 ( 200.0, 100.0 )
 ( 150.0, 250.0 )

プログラムの中では、それを points という名前の実数型の配列変数に格納し、その後の GWpolygon という関数で多角形として描かせている。配列についての詳細は配列の項を参照。具体的には以下のように宣言することで、3 つの座標を x, y の 2 つについて格納できる状態になる。

   float points[3][2];

8角形を描く場合はこれが points[8][2] となる。この points という名前の変数に、以下の三行(一行に 2 文あるので合計 6 文)の代入文によって、それぞれの x, y 座標位置を格納する。[ ] の中の数字の変化に注目。

   points[0][0]=100.0;  points[0][1]=100.0;
   points[1][0]=200.0;  points[1][1]=100.0;
   points[2][0]=150.0;  points[2][1]=250.0;

8角形を描く場合はこれが points[7][0] と points[7][1] まで繰り返されることになる。( 0-7 までを使って 8 つ )

こうした後で、

   GWpolygon(*points,3,0);

と指定することで、3つの頂点をもつ多角形として描かれる。8角形なら二番目の引数は 3 ではなく 8 になるだろう。

なおサンプルプログラムでは GWpolygon() 関数を呼び出す直前に、以下のように色指定をすることで、中を塗りつぶした赤色の三角形を描いた。

  GWsetpen(13, -1, -1, 4);
  GWsetbrs(13, 1, -1);    

色番号についてはGWcolor() 関数を参照。GWsetpen(), GWsetbrs() についてはそのすぐ次の「GWcolor で効果がないとき」を参照。

GWsetpen() の第一引数である 13 は赤色を意味する。GWsetbrs(13, 1, -1) は塗りつぶしの指定。塗りつぶさない場合は (13, 0, -1) と指定する。


回転した楕円を描く

GWcircle()GWscircle() 関数はどちらも縦または横につぶれた楕円を描くもので、これが斜めにつぶれた(つまりある程度回転させた)楕円を描くことはできない。
そこで以下に回転させた楕円の軌道上の座標点を計算し、GWpolygon() 関数を利用して(非常に頂点数の多い滑らかな)多角形として回転した楕円を描く方法を示す。

以下のサンプルプログラムはそのために作成した GWsellipse() 関数を用いた例である。まずサンプルプログラムを実行して、どのような記述でどのような図形が描かれるのか試すと良い。

サンプルプログラム ellipse.c 結果

プログラム解説

サンプルではそのために回転した楕円(塗る)を描く関数 GWsellipse() を用意し、これを呼び出している。

GWsellipse(x1, y1, x2, y2, r, c)

座標位置 (x1, y1) と (x2, y2) を対向する頂点とする四角形に内接する楕円を、角度 r だけ左回りに回転させた図形を描く。
この角度 r は 0〜360 度までで指定する。
引数 x1, y1, x2, y2, r は float 型。引数 c は整数型で色を指定する。

傾いた楕円

サンプルプログラムでは背景に枠線などを引いているが、必要なのは GWsellipse() の呼び出しただ一行である。利用者は単にサンプルプログラムにある関数 GWsellipse の定義をそのまま利用すれば良いだろう。

10. 謝辞

このドキュメントは基礎実習上級クラスのために作成されました。私に作成の動機を与えてくれた受講生たちに感謝します。
2004.9

京都産業大学 理学部
安田豊