関数の作り方について
この項目で学ぶこと
関数とは
いつだか触れたとおり、関数とは複数の文、一連の処理の集まりで、数学で扱う関数とはまったくの別物です。C言語には今まで使ってきたようなprintf関数やscanf関数などのあらかじめ用意している標準ライブラリ関数のほかに、プログラマが独自の関数を定義することが出来ます。多くの場合、関数は引数を変えることで動作を制御します。引数とは処理の材料となる値のことで、今まで( )の中に記述してきた値のことです。関数呼び出し時に渡す引数を特に実引数といいます。これまで使ってきたのが実引数です。
関数名(引数);
引数がない関数も存在します。今回はそのような関数も定義してみましょう。
今まで使用することがなかった関数の機能として、戻り値があります。戻り値とは関数を実行した結果の値のことをいいます。イメージするためにサンプルソースを見てみましょう。
今まで使用することがなかった関数の機能として、戻り値があります。戻り値とは関数を実行した結果の値のことをいいます。イメージするためにサンプルソースを見てみましょう。
数学関数fabs,powを用いたサンプルソース
#include<stdio.h> #include<math.h> // 数学関数を使用するときに必要 int main(){ double zettaiti,ruijou; zettaiti = fabs(-123.4); printf("-123.4の絶対値は「%f」です。\n",zettaiti); ruijou = pow(2.,3.); printf("2の3乗は「%f」です。\n",ruijou); return 0; }
fabs関数は引数の絶対値を返します。pow関数は第1引数の第2引数乗を返します。pow関数の引数で使用している「2.」の.は、この値がdouble型であることを示しています。pow関数は引数に小数しか受け付けません。ちなみに「2.f」とした場合、float型であるとみなされます。
関数を作る・使用するメリットはどれも相互的なので、同じことを言い換えてるだけじゃねーかと思われるかもしれませんが一応書きます。
- 関数を呼び出すとき、関数の中でどのような処理が行われているのかを知らなくても目的の結果を得ることが出来る
printf関数の内部でどのような処理が行われているのか知らなくても文字列を出力することが出来ますね。つまりはそういうことです。
- 同じ処理を何度も書く必要がなくなる
上のfabs関数はif文を使えば使わなくても同じことが出来ますね。もし目的の値が正ならそのまま代入、負なら-1倍して代入すればいいのです。しかし、必要な場面で何度もそのように書くことは面倒です。そのような事態を回避することができます。
- 今まで記述していた動作をまとめて変更することが出来る
直前のメリットを無視して関数を用いずに同じ処理をあちこちに書たとします。もしそれらの動作を変えたいと思ったときに全ての処理を書き換える必要があります。関数を用いているとその関数を書き換えるだけで全ての動作を変えることができます。説明しにくいので詳しくはサンプルソースを見てください。
- 1つあたりの関数が見やすくなる
1つの処理を多くの関数に分割することで1関数あたりの記述が短くなり見通しが良くなります。
関数の定義の仕方
関数を定義するには以下のように記述します。
戻り値の型 関数名 (データ型1 第1引数, データ型2 第2引数...){ 処理内容 return文 }
関数定義の際に使う引数を仮引数といいます。仮引数は変数です。関数呼び出し時に渡される実引数は、仮引数という変数に代入されます。そして、以後の処理は、これまで main 関数でやってきたのと同じような流れになります。
実際のサンプルソースや使い方については後半を見てください。
実際のサンプルソースや使い方については後半を見てください。
プロトタイプ宣言について
普通、関数を用いる場合はこのプロトタイプ宣言というものを使用します。プロトタイプ宣言を用いない方法は紹介しないので、気になるようであれば自分で調べてください。
プロトタイプ宣言とは、これからこのような関数を使用するということを知らせるものです。宣言された関数を使用する前に記述する必要があり、一般的にはインクルード文の直後に記述します。
プロトタイプ宣言とは、これからこのような関数を使用するということを知らせるものです。宣言された関数を使用する前に記述する必要があり、一般的にはインクルード文の直後に記述します。
呼び出されると「MyPrint関数が呼び出されました」と表示する関数
#include<stdio.h> int MyPrint(); // プロトタイプ宣言 int main(){ MyPrint(); // 呼び出す return 0; } // 戻り値の型 関数名 引数(今回は指定しない) int MyPrint(){ printf("MyPrint関数が呼び出されました\n"); return 0; }
今回の例のように関数は必ずしも引数を必要としません。
戻り値を利用する
戻り値の型は好きなものを使用することが出来ます。関数の戻り値の型にはvoid型があります。これは関数が値を返さないときに使用する型です。したがって先ほどのMyPrint関数は次のように書き換えることが出来ます。
#include<stdio.h> void MyPrint(); int main(){ MyPrint(); return 0; } // voidは空虚を意味します。 void MyPrint(){ printf("MyPrint関数が呼び出されました\n"); }
では戻り値をサンプルソースを紹介します。
引数の値を足した結果、引いた結果を返す関数のサンプルソース
#include<stdio.h> int AddNumber(int Number1,int Number2); int SubtractionNumber(int Number1,int Number2); int main(){ int a = AddNumber(5,6); int b = SubtractionNumber(20,24); printf("a = %d\n",a); printf("b = %d\n",b); return 0; } int AddNumber(int Number1,int Number2){ int Answer = Number1 + Number2; return Answer; // 戻り値には変数を指定することが出来る } int SubtractionNumber(int Number1,int Number2){ return (Number1 - Number2); // 計算結果を返すことも出来る }
グローバル変数について
関数の中で宣言された変数はその関数内でしか使用することが出来ません。グローバル変数を用いることで関数の枠を超えて共通の変数にアクセスすることが出来ます。グローバル変数は関数の外に宣言することで使用することが出来るようになります。
main関数で値を代入し、PrintGlobal関数を用いて値を表示する
#include<stdio.h> int Global; void PrintGlobal(); int main(){ Global = 3; PrintGlobal(); return 0; } void PrintGlobal(){ printf("グローバル変数の値は %d\n",Global); }
グローバル変数はさまざまな関数からアクセスできるようになる反面、さまざまな場所で誤って値を変更してしまう可能性があります。これはバグの増加につながるので、便利だといって何でもグローバル変数にすることはやめましょう。
参照渡しについて
説明を試みて挫折した。大量のサンプルソースを載せてごまかそうかと思う。
とりあえず適当に説明して後で直してもらおう。呼び出し元の変数や配列に値を代入する関数を作りたいとします。そういうときに参照渡しをします。C++には参照という概念があるのですが、C言語にはありません。そこで、第8章でやったポインタを使います。書き換えたい変数や配列の先頭アドレスを実引数として受け取り、ポインタ型の仮引数に代入することで、元の変数を操作することができます。つまり、実質的に参照渡しと同様のことができるということです。
ポインタを使った例
#include <stdio.h> void swapInt(int *a, int *b); // int 型の変数の中身を交換する int main(void){ int val1 = 30, val2 = 50; printf("val1 = %d\tval2 = %d\n", val1, val2); swapInt(&val1, &val2); // val1 と val2 の中身を入れ替える printf("val1 = %d\tval2 = %d\n", val1, val2); return 0; } void swapInt(int *a, int *b){ int temp = *a; // temp という変数に一時的に *a の値をメモ *a = *b; // *a に *b の値を書き込む *b = temp; // *b に temp の値 (つまり元の *a の値) を書き込む return; }
再帰呼び出しについて
ある関数の中で、その関数自体を呼び出すことを再帰呼び出し(recursive call)といいます。また、再帰呼び出しできることを再入可能(reentrant)といいます。C言語は再入可能なプログラミング言語です。
再帰呼び出しをする場合、if文などで終了条件を与える必要があります。そうしないと無限ループになり、スタックオーバフローという状態に陥ります。
再帰呼び出しをする場合、if文などで終了条件を与える必要があります。そうしないと無限ループになり、スタックオーバフローという状態に陥ります。
n!(nの階乗)を計算する例 (再帰を使用)
#include <stdio.h> int fact(int n); // n!(nの階乗)を返す int main(void){ int num; printf("自然数を入力:"); scanf("%d", &num); printf("%d! = %d\n", num, fact(num)); return 0; } int fact(int n){ if(n <= 1){ // n ≦ 1 のとき 1 を返す (終了条件) return 1; // return 文を使うとその関数から抜ける } return n * fact(n - 1); // 再帰 (n > 1 のとき n! = n * (n - 1)! であることを利用) }
main関数について
これまで使ってきたmain関数は、実はコマンドライン引数というものを受け取ることができます。これを見てください。
コマンドライン引数を使った例
#include <stdio.h> int main(int argc, char *argv[]){ int i; for(i = 0; i < argc; i++){ printf("argv[%d] = %s\n", i, argv[i]); } return 0; }
main関数が2つの引数を受け取っています。
argc コマンドライン引数の個数 argv コマンドライン引数の配列
Windowsユーザはコマンドライン? 何それ? 食えるの? って人が多いかもしれませんが、一応Windowsでも使います。Windows使いなら、コマンドプロンプトを使えば実感できると思います。コマンドプロンプトの操作方法は、解説するだけでページ1つ分になりそうなのでやめときます。自分で調べてください。
で、プログラムのファイル名が test.exe だとすると、
で、プログラムのファイル名が test.exe だとすると、
test.exe abc pqrstu
としてプログラムを実行すると、結果は
argv[0] = test.exe argv[1] = abc argv[2] = pqrstu
となります。UNIXコマンドもコマンドライン引数というのを使って作られているものがほとんどです。また、Windowsでは実行ファイルに別のファイルをドラッグアンドドロップすると、
実行ファイル名 別のファイル名
という感じで実行されます。拡張子の関連付けとか、ショートカットとかでもコマンドライン引数を指定しています。普段は見せていないだけです。