関数と返値

printf関数やscanf関数は,画面メッセージ出力やキーボードからのデータ読み取りを実行するものとして説明されてきた.しかし,厳密に言えば,C言語における「関数」には2つの使い方がある.

後者の使い方の典型例として,例えば,平方根や三角関数などの数学関数の計算などがある.例えば,三角関数であれば,

/* math.c */
/* Eitetsu Oomoto, 07/04/2006 */

#include  <stdio.h>
#include  <math.h>

main()
{
    double result;
    double x;

    x = 3.14159265/2; /* 90度 */
    result = sin(x); 
    printf("sin(PI/2) = %lf", result); 

    exit(0);
}

といったようにsin関数の計算値を求めた結果を利用できる.この場合,変数に代入している.このように関数を実行した結果として得られる値を,関数の「返値(かえりち)」或いは「戻り値」と呼ぶ.

printf関数やscanf関数も「関数」であるので,実際には返値がある.しかし,その値は,printfやscanfが正常に実行できた場合には整数0(ゼロ), 何らかの原因により正常に実行できなかった場合には0以外の整数となっており,通常の場合には使い道が無い.

printf関数のように,関数の返値には意味が無く,関数によって何かの「機能」を実行させることに意味がある場合には,その関数をそのままプログラム中に書くだけで良く,関数の返値は自動的に破棄される.

つまり,「画面にメッセージを表示」という「機能」が使いたいだけであれば,

    y = printf("Please input a number: "); 
と書くのでなく,
    printf("Please input a number: "); 

というように関数のみを書けば良いということである.尚,printf関数は,このような使い方が100%であると考えて良い.

以下の説明では,簡単なグラフィックス機能を実現するEGGXライブラリについて記載されており,各図形を描くなどの機能が関数として実現されている.一部の関数は,その返値をプログラム中で利用する


EGGXの利用方法

EGGXは,愛知教育大学の山内千里氏によって作成されたX-Window System上で動作する簡易グラフィックライブラリである.EGGXの詳細は,http://phe.phyas.aichi-edu.ac.jp/‾cyamauch/eggx_procall/ を参照のこと.また,PDF形式のマニュアルが用意されているので,こちらを参照のこと.

座標系

EGGXライブラリを用いて画面上にグラフィックスを描く場合,まず描画領域(油絵のカンバスや画用紙を思い浮かべれば良い)となる描画用ウィンドウを予め開き,そのウィンドウ上で任意のグラフィックスを描画することになる.

コンピュ−タの画面全体は小さな点(画素,ピクセル)の集りとして考えることができ,画面左下を原点として右側へ水平に伸びるX軸と上へ伸びるY軸からなる2次元座標平面と考えることが出来る.この画面全体を表す座標軸を「スクリ−ン座標」と呼ぶ.これに対し,グラフィックスを描画する作図用ウィンドウ内部にも,ウィンドウ左下を原点とし,水平方向をx軸,垂直方向をy軸とする座標系があり,これを「ウィンドウ座標」と呼ぶ.各座標の値は実数値(float型)で表されるが,実際に画面描画される際には整数値に調整される.

グラフィックスを描く際は,描画用ウィンドウを指定した上で,そのウィンドウにおける「ウィンドウ座標」をつかって,その「描画用ウィンドウの内部のどこに?」を指定することになる.EGGXに用意されているグラフィック図形には,「点」「直線」「円」「円弧と扇形」「長方形」「多角形」「矢印」「文字列」などがあり,それぞれ対応する関数を実行することで画面描画される.以下の図は,高さ400、幅500の作図用ウィンドウを作成し、その中にウィンドウ座標で(x,y) = (300.0, 200.0)を中心として半径120.0の円を描く場合の座標の関係を表わす.

このグラフィックスを描画するプログラム eggx.c は以下のようになる.

/* eggx.c */
/* Eitetsu Oomoto, 07/09/2005 */

#include  <stdio.h>
#include  <eggx.h>

main()
{
    int w;

    w = gopen(500, 400);
    circle(w, 300.0, 200.0, 120.0, 120.0); 
    ggetch(w); 

    exit(0);
}

尚,描画ウィンドウはプログラムが終了すると画面から消えてしまうので,上記の例ではggetch()関数を使うことでキ−ボ−ドから何かキ−がタイプされるまで実行を一時停止させている.

カラ−設定

EGGXではグラフィックを描く際に使う色を指定することができる.その指定方法には,大きく二つある.

色名指定:描画したい色の名前をnewcolor()関数に指定する.例えば,

newcolor(win, "red");

というように指示する.使用できる色名は,/usr/X11R6/lib/X11/rgb.txt に一覧されているので,使いたい色の名前はこのファイルから探せば良い.

RGBカラ−モ−ド:光の三原色の赤(Red), 緑(Green), 青(Blue)の明るさを0から255の整数値で表現し,その割合で色彩を表す.それをnewrbgcolor()関数に指示する.0が最低輝度,255が最高輝度である.例えば,青色の場合は,

newrgbcolor(win, 0, 0, 255);

というように指示する.

グラフィックス描画用各種関数の説明

以下の関数説明において,関数名の前についた int は,その関数がint型の返値となることを示す.また, void は,「返値を返さない関数」或いは「返値を使うことが禁止されている関数」,すなわち,「機能を実行するためだけの関数」であることを意味している.

描画用ウィンドウの作成と表示:
gopen()は新しく描画用ウィンドウを作成し,画面に表示を行う.返値はint型の値であり,この値で,複数のウィンドウそれぞれを区別する.

 int gopen ( int x , int y )
    int x: ウィンドウの幅
    int y: ウィンドウの高さ

作成されるウィンドウの背景色のデフォルト(既定値)は黒である.

プログラムが終了したとき(あるいは強制終了したとき),描画ウィンドウは自動的にクローズされる.また,gclose()関数を利用すれば,プログラム実行中に閉じることができる.

関数の戻り値は作成されたウィンドウの識別番号(id)を示す整数であり,この数値を用いて開いている複数のウィンドウを識別する.これ以降の描画用関数の操作は,全てこのウィンドウidを必ず指定して(どの描画ウィンドウなのかを指定することを意味する)行うことになるので,int型変数を予め用意しておき,gopen()関数の戻り値を格納しておく必要がある.また,描画用ウィンドウはいくつも同時に開くことができる.

描画ウィンドウのクローズ:
gclose()は指定したidを持つ描画ウィンドウを閉じる(クローズ).

void gclose ( int wid )
		wid: ウィンドウのid

引数として、クローズさせたいウィンドウのidを指定する.尚,関数名の前にvoidと付いていることから分かるように,関数の戻り値はない.

例えば幅300,高さ300の描画ウィンドウAと,幅200,高さ200の描画ウィンドウBの両方を表示して一旦停止し,キ−ボ−ドから何かのキ−がタイプされるとAウィンドウを閉じて,再度停止し,再びキ−ボ−ドがタイプされると終了するプログラム dual.c は以下のようになる.

/* dual.c */
/* Eitetsu Oomoto, 07/09/2005 */

#include  <stdio.h>
#include  <eggx.h>

main()
{
    int w_a;
    int w_b;

    w_a = gopen(300, 300); 
    w_b = gopen(200, 200); 
    ggetch(w_b); 
    
    gclose(w_a); 
    ggetch(w_b); 

    exit(0);
}

描画色の指定:
newcolor()関数は図形の描画色を指定された色にセットする.

void newcolor ( int wid, char *argsformat)
    wid: ウィンドウのid
    argsformat: 色名(利用可能な色名は/usr/X11R6/lib/X11/rgb.txtに書かれている)

また,newrgbcolor()関数はRGBの輝度組み合わせにより描画色を表現する.

void newrgbcolor ( int wid, int r, int b, int g)
		wid: ウィンドウのid
		r: 赤の輝度(0‾255)
		g: 緑の輝度(0‾255) 
		b: 青の輝度(0‾255)

さらに,gsetbgcolor()関数は,ウィンドウの背景色を指定された色にセットする.

void gsetbgcolor ( int wid, char *argsformat)
		wid: ウィンドウのid
		argsformat: 色名

尚,gsetbgcolorで背景色をセットした後,後述するgclr()関数で描画ウィンドウのクリアを行えば,背景色による全面塗りつぶしが行われる.

点の描画:
pset()は点を描画する関数である.

void pset ( int wid, float x, float y)
    wid: ウィンドウのid
    x,y: 点のx,y座標

直線の描画:
line()は直線を描画する関数である.

void line ( int wid, float x, float y, int mode ) 
    wid: ウィンドウのid
    x,y: ペン移動位置のx,y座標
    mode: 描画モード(PENUP, PENDOWN, PSET)

始点・終点の座標はウィンドウ座標系で与えるが,ウィンドウの外側にはみ出るような座標を指定しても構わない.

line()関数の使い方は毛筆を使っているようなイメージで考える.すなわち,「直線を描く」とは,「ペンを上げた状態で直線の始点に移動した後,ペンを下ろした状態で直線の終点に移動する」ことで出来る.ペンを上げた状態はmodeにPENUPを指定し,ペンを下ろした状態はmodeにPENDOWNを指示してline()関数を呼ぶ(実行する).また,modeにPSETを指示すると,ペン位置をその座標位置に移動した上で,そこに点を描画する.

例えば,int型変数widで示されるウィンドウ内に(10.0, 10.0)から(100.0, 200.0)までの直線を引く場合を考える.この場合,次のように line()関数を二度実行することで実現できる.

line(wid, 10.0, 10.0, PENUP);
line(wid, 100.0, 200.0, PENDOWN); 

円の描画:
circle()は円(正確には楕円)を描画する.

void circle ( int wid, float x, float y, float xrad, float yrad)
    wid: ウィンドウのid
    x,y: 中心のx,y座標
    xrad: 横方向の半径
    yrad: 縦方向の半径

例えば,中心の座標が(100.0, 100.0)で,半径が50.0の真円を描くには,

circle (wid, 100.0, 100.0, 50.0, 50.0);

とし,中心の座標が(100.0, 100.0)で,横方向の半径が80.0,縦方向の半径が40.0の横長の楕円を描くには,

circle (wid, 100.0, 100.0, 80.0, 40.0);

とする.

円弧と扇形:
drawarc()は円弧を描画する関数である.fillarc()は内部を塗りつぶした扇形を描画する.

void drawarc( int wid, float x, float y, float xrad, float yrad, float sang, float eang, int idir)
void fillarc( int wid, float x, float y, float xrad, float yrad, float sang, float eang, int idir)
    wid: ウィンドウのid
    x,y: 中心のx,y座標
    xrad: 横方向の半径
    yrad: 縦方向の半径
    sang: 開始角(度単位, 0‾360)
    eang: 終了角(度単位, 0‾360)
    idir: 描画方向(1で左周り,-1で右回り)

長方形:
drawrect()は長方形を描画し,fillrect()は内部を塗りつぶした長方形を描画する.

void drawrect ( int wid, float x, float y, float w, float h )
void fillrect ( int wid, float x, float y, float w, float h ) 
     wid: ウィンドウのid
     x,y: 左下隅のx,y座標
     w: 幅 (width≧0.0)
     h: 高さ (height≧0.0) 

左下隅の座標はウィンドウ座標系で与えるが,ウィンドウの外側にはみ出る座標を指定しても構わない.

多角形:
fillpoly()は内部を塗り潰した多角形を描画する.始点と終点は自動的に結ばれる.

void fillpoly ( int wid, float x[], float y[], int n, int i )
    wid: ウィンドウのid
    x[]: 頂点のx座標を格納した配列
    y[]: 頂点のy座標を格納した配列
    n: 頂点の個数
    i: 塗りつぶし形状(通常は0, 凸多角形の場合は1を指定)

文字列:
drawstr()は文字列を描画する関数である.

void drawstr ( int wid, float x, float y, int size, float theta, char* txt )
    wid: ウィンドウのid
    x,y: 文字列左下隅のx,y座標
    int size: 文字の大きさ(一文字の大きさをドット単位で指定,尚,日本語文字表示の場合にはマクロFONTSETを指定する)
    float theta: 文字列の回転角(現バージョンでは機能しない)
    char *txt: 描画文字列を格納した配列のポインタ

左下隅の座標はウィンドウ座標系で与えるが,ウィンドウの外側にはみ出る座標を指定しても構わない.

例えば,幅500、高さ400の作図用ウィンドウを作成し、その中に、ウィンドウ座標で(x,y) = (300.0, 200.0)を文字列の左端として"Hello World 初めてのグラフィック"という日本語文字で紫色表示する場合,以下のようなプログラムとなる.

#include <stdio.h>
#include <eggx.h>

main()
{
    int w;

    w = gopen(500, 400);
    newrgbcolor(w, 255, 0, 255);
    drawstr(w, 100.0, 200.0, FONTSET, 0, "Hello World 初めてのグラフィック"); 
    ggetch(w); 

    exit(0); 
}

ウィンドウ内部の図形消去:
gclr()はウィンドウ内に描画された図形を消去する関数である.

void gclr ( int wid ) 
    wid: ウィンドウのid

この関数により,ウィンドウ内部の図形を全て消去して,背景色でウィンドウ全部を塗りつぶす.

キーボードからの文字入力:

C言語には,キーボードから入力された文字を読み取る関数として,scanf()やgetchar()といった関数があるが,通常,これらの関数はリターンキー(エンターキー)がタイプされるまで一時停止するという特性がある.これは,例えばゲームなどの対話型プログラムを実現するには困ったことになる.EGGXには,キーボードから1文字入力されると,直ちにその文字を読み取って返す関数ggetch()が用意されている.

int ggetch ( int wid ) 
    wid: ウィンドウのid

この関数は,widで指定されたウィンドウに対してキーボードからキー入力があるまで待ち,何か1つのキーが押されると,その文を表す整数値を返す.例えば,リターンキーであれば13(0x0d), Spaceキーであれば32(0x20)が返される.詳細はEGGXのマニュアルを参照のこと.

また,読み取ったキーは整数値で返ってくるので,一般には整数型変数を用意しておき,それに関数の返り値を代入して利用する.

int w;
int key;
.....
.....
key = ggetch(w); 
.....
.....

しかし,キーは何でも良く,単に「とにかく何かキーがタイプされるまで待つ」だけの場合には,ggetch()関数を単独で書く場合がある.この場合,読み取ったキーの情報(返値)は,コンピュータによって自動的に捨てられる.

int w;
.....
.....
ggetch(w); 
.....
.....

その他の機能:

EGGXライブラリに用意されている関数には,その他の機能を実現するものとして,ウィンドウに表示したグラフィックスを画像ファイルとして保存する関数saveimg()やマウス操作入力の情報を返すggetxpress()といった関数がある.これらやggetch()関数などを上手に組み合わせれば,簡単なゲームプログラムも作成できるだろう.詳細はEGGXのマニュアルを参照して欲しい.

プログラムのコンパイルと実行:
EGGXライブラリをプログラムで使用する場合,eggx.hというヘッダファイルをインクル−ド宣言しておかなければならない.

プログラムの先頭部分に,
#include <eggx.h>
という行を付け加えておくこと.

EGGXライブラリの関数を呼び出しているプログラムをコンパイルするには,eggというコマンドをgccコマンドの代りに使えばよい.尚,このコマンドは,実際には内部的にgccを呼び出すシェルスクリプトに過ぎない.

また,eggコマンドでコンパイルする時には,ソースプログラムが完璧にC言語の最新規格の文法に従っているかどうか,極めて厳密に調べた上で,曖昧な部分は警告を発するようになっているが,現段階では「警告」については,あまり気にする必要は無い.しかし,警告に紛れて「文法error」を見落とさないようにすること.

練習課題:適当なディレクトリで以下のプログラムを作成してコンパイル・実行してみよ.

練習:プログラムeggx.cが以下のディレクトリ位置に置いてあるので,これを各自の作業ディレクトリにコピーした上で,コンパイル・実行してみよ.

/NF/class0/oomoto/program/eggx/eggx.c

練習:プログラムwindow.cが同様に以下の位置に配置してあるので,同様に実行してみよ.

/NF/class0/oomoto/program/eggx/window.c

練習:window.cを元に次のようなプログラムwindow2.cを作成してみよ.
まず,幅200.0,高さ200.0の描画ウィンドウAを表示し,引き続いて,幅300.0,高さ300.0の描画ウィンドウBを表示した後,一旦停止する.キ−ボ−ドから何かタイプされると後から表示したウィンドウBを閉じて再度停止し,キ−ボ−ドから何かタイプされると終了する.

練習:以下のように動くプログラムdual_draw.cを作成せよ.
window2.cを改良して,Aウィンドウ内にウィンドウ座標で(10.0, 20.0)から(150.0, 100.0)まで,黄色の直線を描き,Bウィンドウにウィンドウ座標で(150.0, 150.0)を中心とし,半径100.0で赤色の塗りつぶし円を描いた後,一旦停止し,キ−ボ−ドから何かタイプされると終了する.

練習:
以下の練習問題では,描画ウィンドウとして,背景が白で幅300,高さ300の描画ウィンドウを仮定し,各図形の座標値はウィンドウ座標で表されているとする.ウィンドウ座標で(150,150), (250,150), (250,250), (150, 250)を頂点とする黒線で構成された正方形を描くプログラムsimple_square.cを作成してみよ.描画ウィンドウの背景を白とするには,ウィンドウを開いた後,白色で背景を塗りつぶせば良い.背景の塗りつぶしには,gclr()関数を用いる.

練習:
(150, 200)を中心とする赤い円, (200, 200)を中心とする緑の円, (250, 200)を中心とする青い円をそれぞれ接するよう描くプログラムtriple_circle.cを作成せよ.但し,円の半径は全て同じとする.

提出課題その1:
上記で行った練習で作成した全てのプログラム (eggx.c, window.c, window2.c, dual_draw.c, simple_square.c, triple_circle.c)をレポート作成用コマンド /NF/class0/oomoto/applied/bin/report_gen.sh を 用いて一つにまとめたテキストファイルを作成せよ.但し,どこからどこまでがどのプログラムなのか分かるようにしておくこと. また,そのファイルの先頭部分に自分の学生証番号と氏名を書き込んでおくこと.
出来上がったテキストファイルをlprコマンドで印刷して,本日の講義時間(7月4日 3時限)中に提出せよ.

提出課題その2:
(150,150), (250,150), (250,250), (150, 250)を頂点とする青く塗りつぶした正方形と,それに内接する黄色く塗りつぶした円を重ねて, 下図のように描くプログラムsquare.cを作成せよ.ウィンドウの背景は白色とする.

尚,塗りつぶした円は,「中心角が360度の塗りつぶし円弧」として描画する.例えば,中心が(100, 200),半径50の塗りつぶした円を描きたければ,

int wid;
.....
.....
fillarc(wid, 100, 200, 50, 50, 0, 360, 1); 
.....
.....

といった具合に書く.

課題提出はレポートシステムを利用して提出すること.
レポート名 oomoto/program/square
締切り期限:7月10日(火曜日)

提出課題その3:
scanf関数を用いて,中心のX座標,Y座標,半径の3つを入力し,それらの数値に応じて下図のような円とそれに内接する菱形,外接する正方形を描画するプログラムscanf.cを作成せよ.但し,全ての図形は塗りつぶし無し,線の太さは1.0とし,色指定は,円は黒,ひし形はオレンジ色,正方形は紫色とせよ.また,ウィンドウの背景は白色とする(ヒント: オレンジ色を光の三原色で表現するには,緑の光を80〜90%程度,赤の光を80〜90%程度を混合すれば作れる.また,ひし形は直線を4本引くことを考えよ.)

提出はレポートシステムを利用して提出すること.
レポート名 oomoto/program/scanf
締切り期限:7月10日(火曜日)


次回の授業に備えて,教科書「新・C言語のススメ」第5章を予習してくること.


基礎プログラミング演習Iの表紙ページへ戻る