C言語のプログラムは、プリプロセッサー(15章)と宣言文と、実行文からなる。これらの うちプログラム実行中にCPUが動作するのは実行文の処理だけである。後に学習するが、 プリプロセッサーはコンパイル時(gccコマンド使用時)の動作に使われる。宣言文で ある変数宣言などは、プログラムがメモリーにロードされる際にそれを予約するのに使わ れるだけである。メモリーにロードされたプログラムが実際に動作するのであるが、その 動作内容は実行文に従う。コンピューターは、プログラム中の実行文の順序に従い、自動 的にメモリーの内容を処理しているだけである。
この実行文は、必ず関数のブロック中に書かれなくてはならない。今まで、学習してきた プログラムでは、main関数のみがあり、その中に実行文が書かれていたはずである。 main関数は特別で、C言語のプログラムには、必ず1個必要で、そこから実行される ことになっている。
実際のC言語のプログラムは、main関数のみからできていることは希で、リスト 1のように複数の関数からできている。
01: #include <stdio.h> 02: 03: double max(double a, double b); 04: 05: /*=====================================================================*/ 06: /* メイン関数 */ 07: /*=====================================================================*/ 08: int main(){ 09: double x, y, z; 10: 11: x=2.5; 12: y=3.1415; 13: 14: z=max(x, y); 15: 16: printf("大きい方は、%eです。\n", z); 17: 18: return 0; 19: } 20: 21: /*=====================================================================*/ 22: /* 大きい方を探す関数 */ 23: /*=====================================================================*/ 24: double max(double a, double b){ 25: double big; 26: 27: if(a<b){ 28: big = b; 29: }else{ 30: big = a; 31: } 32: 33: return big; 34: 35: }関数は、長いプログラムを効率よく記述するために必要である。そのために、この関数には2つの役 割がある。一つは、同じような処理を一つにまとめることである。実際のプログラムの動 作は、同じ処理、あるいは似たような処理が非常に多い。いちいちそれを書くとプログラ ムが長くなり、プログラマーは大変である。そこで、一つにまとめ、必要なときに呼び出 すのである。
もう一つ、長いプログラムの問題は、処理が分かりにくい点である。例えば、 windows2000だとソースプログラムは大体4000万行だと言われている。この場合、それぞ れの実行文の役割など分からない。コンピューターは大量のトランジスターからできてい るが、それぞれの役割が分からないのと同じである。このように大量の部品(実行文)から 構成されるコンピューター(プログラム)の動作を考える際に重要なことは、モジュール 2に 分解することである。そうすると、動作の内容が分かるようになる。長いプログラムを作 る場合も同じで、機能単位(モジュール)に分け、分かりやすくすることが重要である。C 言語では関数を使い、機能単位にプログラムを分割する。
まとめると、関数の役割は
01: #include <stdio.h> 02: #include <stdlib.h> 03: #include <time.h> 04: 05: int main(void){ 06: int a[10], b[100], max_a, max_b, i; 07: 08: srand((unsigned) time(NULL)); /* 起動毎に異なる乱数発生のため */ 09: 10: for(i=0; i<10; i++) a[i]=rand(); /* 配列 a[] の値設定 */ 11: for(i=0; i<100; i++) b[i]=rand(); /* 配列 b[] の値設定 */ 12: 13: /* ---- 配列 a[] の最大値検索 ----- */ 14: max_a=a[0]; 15: for(i=1; i<10; i++){ 16: if(max_a<a[i]) max_a=a[i]; 17: } 18: 19: /* ---- 配列 b[] の最大値検索 ----- */ 20: max_b=b[0]; 21: for(i=1; i<100; i++){ 22: if(max_b<b[i]) max_b=b[i]; 23: } 24: 25: printf("max a=%d\n",max_a); /* 最大値印刷 */ 26: printf("max b=%d\n",max_b); 27: 28: return 0; 29: }
01: #include <stdio.h> 02: #include <stdlib.h> 03: #include <time.h> 04: 05: int find_max(int n, int ix[]); /* プロトタイプ宣言 */ 06: 07: /*=========================================================================*/ 08: /* main 関数 */ 09: /*=========================================================================*/ 10: int main(void){ 11: int a[10], b[100], max_a, max_b, i; 12: 13: srand((unsigned) time(NULL)); /* 起動毎に異なる乱数発生のため */ 14: 15: for(i=0; i<10; i++) a[i]=rand(); /* 配列 a[] の値設定 */ 16: for(i=0; i<100; i++) b[i]=rand(); /* 配列 b[] の値設定 */ 17: 18: max_a=find_max(10, a); 19: max_b=find_max(100,b); 20: 21: printf("max a=%d\n",max_a); /* 最大値印刷 */ 22: printf("max b=%d\n",max_b); 23: 24: return 0; 25: } 26: 27: /*=========================================================================*/ 28: /* 最大値探索の関数 */ 29: /*=========================================================================*/ 30: int find_max(int n, int ix[]){ 31: int i, max; 32: 33: /* ---- 配列 a[] の最大値検索 ----- */ 34: max=ix[0]; 35: for(i=1; i<n; i++){ 36: if(max<ix[i]) max=ix[i]; 37: } 38: 39: return max; 40: }
01: #include <stdio.h> 02: #include <stdlib.h> 03: #include <time.h> 04: 05: /* ---- プロトタイプ宣言 ----- */ 06: void make_data(int nx, int ix[], int ny, int iy[]); 07: int find_max(int n, int ix[]); 08: void print_results(int a, int b); 09: 10: /*=========================================================================*/ 11: /* main 関数 */ 12: /*=========================================================================*/ 13: int main(void){ 14: int a[10], b[100], max_a, max_b; 15: 16: 17: make_data(10, a, 100, b); /* 乱数データ作成 */ 18: 19: max_a=find_max(10, a); /* 最大値検索 */ 20: max_b=find_max(100,b); 21: 22: print_results(max_a, max_b); /* 最大値印刷 */ 23: 24: return 0; 25: } 26: 27: 28: /*=========================================================================*/ 29: /* データ作成 */ 30: /*=========================================================================*/ 31: void make_data(int nx, int ix[], int ny, int iy[]){ 32: int i; 33: 34: srand((unsigned) time(NULL)); /* 起動毎に異なる乱数発生のため */ 35: 36: for(i=0; i<nx; i++) ix[i]=rand(); 37: for(i=0; i<ny; i++) iy[i]=rand(); 38: 39: } 40: 41: 42: /*=========================================================================*/ 43: /* 最大値探索の関数 */ 44: /*=========================================================================*/ 45: int find_max(int n, int ix[]){ 46: int i, max; 47: 48: /* ---- 配列 a[] の最大値検索 ----- */ 49: max=ix[0]; 50: for(i=1; i<n; i++){ 51: if(max<ix[i]) max=ix[i]; 52: } 53: 54: return max; 55: } 56: 57: 58: /*=========================================================================*/ 59: /* 結果の印刷 */ 60: /*=========================================================================*/ 61: void print_results(int a, int b){ 62: 63: printf("max a=%d\n",a); 64: printf("max b=%d\n",b); 65: 66: }
関数へのデータの渡し方に、2つの方法がある(通常は値渡し)。
それでは、データの受け渡しについて、教科書を見ながら、練習せよ。
01: #include <stdio.h> 02: 03: void swap(int i, int j); 04: 05: int main(){ 06: int a=2, b=3; 07: 08: swap(a, b); 09: 10: printf("a=%d b=%d\n", a, b); 11: 12: return 0; 13: } 14: 15: 16: void swap(int i, int j){ 17: int temp; 18: 19: temp = i; 20: i=j; 21: j=temp; 22: }
01: #include <stdio.h> 02: 03: void swap(int *i, int *j); 04: 05: int main(){ 06: int a=2, b=3; 07: 08: swap(&a, &b); 09: 10: printf("a=%d b=%d\n", a, b); 11: 12: return 0; 13: } 14: 15: 16: void swap(int *i, int *j){ 17: int temp; 18: 19: temp = *i; 20: *i=*j; 21: *j=temp; 22: }
void keisan(double theta, double *s1, *s2);
この辺のことをリスト7でみる。関数transposeで行列を転置 3している。main関数のtranspose(a)で、 関数transposeに配列a[5][5]の先頭アドレスを渡している。配列のところで学 習したように、配列名は、その配列の先頭アドレスを表す。
先頭アドレスは、transpose関数では、
transpose(int x[][5])の形で受け取る。アドレスが渡されたので、ポインターで受け取ることも出来るが、そう すると、後々の操作が大変になるので通常は避ける。配列を受け取る場合は、配列として 受け取る方が断然良い。
ここで、受け取る配列の大きさが、x[][5]とすべて記述されていない。配列のところで、 学習したように、サイズは、指定の配列をアクセスするためのアドレスのオフセット計算 に用いる。したがって、オフセット計算に不用な一番左のサイズは書かなくても良い。も ちろん書いても良く、twx[5][5]でも問題はないが、間違える確率が増えるので通常は書か ない。 。
一方、配列定義のときは、コンパイラーはメモリーを確保するために、配列全体サイズが 必要である。したがって、x[5][5]のようにすべて記述しなくてはならない。このような 理由から、もっと多次元の配列、例えば、x[100][100][5][5]のような配列をhogehoge関 数側で受け取る場合、hogehoge(int x[][100][5][5])と書く。左端のみ省略可である。
01: #include <stdio.h> 02: 03: void swap(int i, int j); 04: 05: int main(){ 06: int a=2, b=3; 07: 08: swap(a, b); 09: 10: printf("a=%d b=%d\n", a, b); 11: 12: return 0; 13: } 14: 15: 16: void swap(int i, int j){ 17: int temp; 18: 19: temp = i; 20: i=j; 21: j=temp; 22: }
11 12 13 14 15 21 22 23 24 25 31 32 33 34 35 41 42 43 44 45 51 52 53 54 55 11 21 31 41 51 12 22 32 42 52 13 23 33 43 53 14 24 34 44 54 15 25 35 45 55
この講義で諸君が作る程度の短いプログラムならば、グローバル変数を使っても良いだろ う。むしろポインターが分からなくて悩むよりは、グローバル変数を使った方がプログラ ムを楽しめて良いだろう。