もう一度関数についておさらいをすると、C言語はループや判別などの制御構造と、 様々な機能を一つにまとめた関数というものから成り立っている。 関数には入力と出力の機能があり、値の受け渡しができるようになっている。 ここで簡単な関数を書いてみる。 受け渡された値を二倍して返すというものである。
kannsuu.c------------------------------------------------------------- #include <stdio.h> int nibai(int c){ //与えられた数を二倍する。戻り値は二倍した値 c=c*2; return(c); } int main(void){ int a,b; a=7; b=nibai(a); printf("a=%d, b=%d \n", a, b); } 実行結果 ------------------------------------------------------------- ~/> ./a a=7, b=14 ----------------------------------------------------------------------ここで気をつけたいのは、関数に渡した値は関数の中でどんなに値を変更しても 戻ってきたときにはその変更は反映されていないということである。 先の例でいうと、関数nibai()の中で、 main()関数から渡された値7(これを引数という)が二倍にされているが、 main()関数のb=nibai(a);の次の行でもaの値は7のままである。 ではどうやって受け取るか?二倍した結果はreturn(c);で戻され、 bに代入される。(これを戻り値という) ただし、戻り値は一つしかあり得ないので複数の結果を得たいときには 次のポインターという概念を用いる。
pointer.c------------------------------------------------------------- #include <stdio.h> int irekae(int *a,int *b){ //値を入れ替える関数。 int c; c = *a; *a = *b; *b = c; return 0; //戻り値には特に意味はない } int main(void){ int *p, *m; int i=5, j=8; p=&i; m=&j; printf("始め %d, %d \n", i, j); irekae(p, m); printf("終わり %d, %d \n", i, j); } 実行結果 ------------------------------------------------------------- ~/> ./a 始め 5, 8 終わり 8, 5 ----------------------------------------------------------------------ポインターとはアドレス(メモリ上の番地)を扱うための変数で、 ポインターの型宣言は"int *p;"のようにする。 普通の変数は別なところにあり(上の例ではi=5)、 そのアドレス(&iで取得できる,例えば1000)をポインターに代入(p=&i)する (これでpの値は1000になる,更に*pとすればアドレス1000の所にある値5を得る)。 そして徐にポインターを関数に渡せば、 関数での中でいじった結果を受け取ることができるようになる。 つまり、変数の値を渡すのではなく、変数がどこにあるのかを渡すのである。
配列の考え方は先に述べたとおりである。 ただし関数に配列を渡すには、配列を全部渡すのではなく、 配列の名前と配列の大きさを渡す。
hairetu.c------------------------------------------------------------- #include <stdio.h> #include <stdlib.h> int saidai(int b[], int n){ int i, j=0; for(i=0; i<n; ++i){ if(j<b[i]){ j=b[i]; } } return j; } int main(void){ int i=5, j; int a[i]; a[0]=3; a[1]=5; a[2]=1; a[3]=7; a[4]=2; j = saidai(a,i); printf("最大値 %d \n", j); } 実行結果 ------------------------------------------------------------- ~/> ./a 最大値 7 ----------------------------------------------------------------------
じつは配列の名前というのは配列の先頭アドレスを示すので、 配列の中身をポインターを使って扱うことができる。 どのようにするのかというと、 ポインターというのは単なるアドレスの入れ物ではなく アドレスの演算(足したり引いたり)を行うことができるので、 メモリの中を自由に行き来きして値を読み書きするのである。 先ほどの関数"saidai()"をポインターを用いて書き換えてみよう。
hai_poin.c------------------------------------------------------------ #include <stdio.h> #include <stdlib.h> int saidai(int *b, int n){ int i, j=0; for(i=0; i<n; ++i){ if(j<*(b+i)){ //*(b+i)とは、 j=*(b+i); //配列の先頭アドレスbからiだけ進んだところの値 } } return j; } int main(void){ int i=5, j; int a[i]; a[0]=3; a[1]=5; a[2]=1; a[3]=7; a[4]=2; j = saidai(a,i); printf("最大値 %d \n", j); } 実行結果 ------------------------------------------------------------- ~/> ./a 最大値 7 ----------------------------------------------------------------------
座標や複素数など複数の変数が組になったものを一貫して扱うには 構造体を用いるとよい。 配列を使ってある点のx座標,y座標を(double型の)a[0],a[1]に入れて、 別な点のx座標,y座標をb[0],b[1]に入れるとしてもいいが、 その地点の名前(char)や他の情報も一纏めにしておきたい場合もある。 ここでは、 ある点の名前とそのx座標,y座標を一纏めにしたような新たな型を まず定義して、 そのような型を持つ変数(構造体変数)をつくる。 大まかにはこの構造体変数を用いて行うが、 実際に値が入っているのは構造体の中の変数(メンバ)である。 構造体のメンバへのアクセスは (構造体変数).(メンバの名前) によって行う。
kouzou.c-------------------------------------------------------------- #include <stdio.h> #include <math.h> typedef struct{ //新たに構造体の型を定義する //この構造体には以下のような変数(メンバ)が入ってます double x_axis; //x座標の値を入れる変数 double y_axis; //y座標の値を入れる変数 char name; //地点の名前を入れる変数 }ZAHYOU; //新たに作った構造体の型の名前 ZAHYOU kaiten(ZAHYOU a, int psi){ //座標回転の関数 ZAHYOU b; double theta; theta = psi*(2*M_PI/360); //M_PI=3.14159265358979323846 b.x_axis = a.x_axis*cos(theta)-a.y_axis*sin(theta); b.y_axis = a.x_axis*sin(theta)+a.y_axis*cos(theta); b.name = a.name +1; //アルファベットを一つずつ進む(A,B,C,…) return b; } int main(){ ZAHYOU sitten1, sitten2, sitten3; //三つのZAHYOU型構造体変数を作る int phi=45; //回転させる角度は45° sitten1.x_axis=1; //構造体のメンバに値を代入 sitten1.y_axis=0; sitten1.name ='A'; //はじめの地点は"A" sitten2 = kaiten(sitten1, phi); sitten3 = kaiten(sitten2, phi); printf("%c地点 x=%g y=%g \n", sitten1.name, sitten1.x_axis, sitten1.y_axis); printf("%c地点 x=%g y=%g \n", sitten2.name, sitten2.x_axis, sitten2.y_axis); printf("%c地点 x=%g y=%g \n", sitten3.name, sitten3.x_axis, sitten3.y_axis); } 実行結果 ------------------------------------------------------------- ~/> ./a A地点 x=1 y=0 B地点 x=0.707107 y=0.707107 C地点 x=5.77609e-17 y=1 ----------------------------------------------------------------------
ファイルを読み込むためにはまずファイルをオープンする必要がある。 そのための関数がfopenで、ファイル構造体へのポインタを返してくるので、 以降ファイルをいじる場合にはこのポインタを使う。 たとえば一行単位の読み込みの場合には、 fgets関数の引数としてデータを格納する領域とその大きさ、 そしてファイルポインタを渡す。 ファイルを閉じる場合にはfclose関数を用いる。
file.c---------------------------------------------------------------- #include <stdio.h> int main(void){ FILE *fp; int nbyte=128; char buff[128]; char fname[15]="sample.txt"; //オープンするファイルの名前 if((fp=fopen(fname,"r"))==NULL){//ファイルのオープン "r"は読み込み用 //"w"なら書き込み用 printf("Cannot open \"%s\"\n",fname); return; } while(1){ //ここで無限ループに入る if(fgets(buff, nbyte, fp) == NULL) break; //fgetsはファイルポインタfpのある位置から //改行文字を読み込むかnbyteだけ読み込み、 //buffに格納する //fpがファイルの終端を読み込むとfgetsはNULLを返すので //breakでループから抜け出す printf("%s", buff); } fclose(fp); } 実行結果 ------------------------------------------------------------- ~/> cat sample.txt hogehoge aiueo ~/> ./a hogehoge aiueo ----------------------------------------------------------------------