※ 筑波大学渡辺紀生作成の演習ページから一部修正・転載。
それでは今日の本題の「関数」に」入りましょう。関数とは、C言語のコマンドをまとめて一つの名前をつけ、簡単に呼び出せるようにしたものです。
これまで、 printf( ) , scanf( ) といった命令を使ってきましたが、これらは実はC言語が提供する標準的な関数(ライブラリ関数)です。
自分たちで関数を書くことで、これらの関数同様に複雑な処理を簡潔に行うことができます。複雑なC言語プログラムを作成する場合には、プログラム全体をそれぞれが単純な機能を持つ部分に分解して関数に割り当てることによって、見通しよくプログラムを作成することが出来ます。
関数を自分で作ることを「関数を定義する」といいます。一度定義した関数はプログラム内で何度でも使うことができます。
関数の定義は次のように行います。
返却値型 関数名(仮引数リスト){ 関数の本体 }
関数は値を受け取り、何らかの処理をして値を返却します。
引数
受け取る値を「引数」といいます。受け取る値は一個でもいいですし、複数個にすることもできます。受け取った引数の値は「仮引数リスト」で指定された変数に代入され、関数の本体の中で使うことができます。
返却値(戻り値)
関数は何らかの処理を行った後に、その関数を呼び出したコードに対して値を返します。これを戻り値といいます。関数の本体の中に return (...); を記述すると、そこで関数の処理が終わり、return の中の値が戻り値となります。戻り値の受け取り方は「1.2 関数の使い方」で例をみて理解しましょう。
副作用
前述のように、関数は引数として値を受け取り、なんらかの処理をして値を戻す処理の単位です。(数学の関数と同じかんじですね。)ただ、値を返す以外にも関数の外に影響をあたえることが有ります。
例えば、関数の中で printf("abcd"); を入れると、画面に abcd と表示されます。このように、戻り値以外の方法で関数の外に影響を与える処理を「副作用」といいます。副作用は関数の重要な機能の一つです。
関数の例
下のコードは関数 maxof() を定義する例です。
この関数では、戻り値の型が int , 関数名が maxof, 仮引数は int x と int y の2つです。2つの整数値を引数として受け取って、大きいほうの値を返す関数です。
int maxof( int x, int y) {
if ( x > y)
return (x);
else
return (y);
}
引数として受け取った値は、仮引数リスト (maxof の後の int x, int y)で指定した変数の名前(x と y)を使って、関数本体の中で参照することができます。
1.2 関数の使い方(関数呼び出し)
自分で定義した関数やC言語のライブラリ関数ををプログラム中で使用するためには、次のように関数を呼び出す必要があります。
関数名(実引数リスト);
例えば、上記の maxof( ) 関数を用いて整数3, 5のうち大きいほうの値を求めて x という変数に代入するには、プログラム中で以下のように書きます。
x = maxof(3, 5);
次のプログラム(List6-1)は、上の関数を用いて入力した2つの整数のうち大きいほうを出力するプログラムです。
/* 二つの整数の大きい方の値を返す関数 */
#include <stdio.h>
/*--- 大きい方の値を返す ---*/
int maxof(int x, int y) {
if (x > y)
return (x);
else
return (y);
}
int main(void) {
int na, nb;
int x = 10;
puts("二つの整数を入力してください。");
printf("整数1:");
scanf("%d", &na);
printf("整数2:");
scanf("%d", &nb);
printf("大きい方の値は%dです。\n", maxof(na, nb));
printf(" x = %d\n", x);
return (0); }
関数の定義部分は、その関数を使用しているmain( )関数より前に描く必要がある点に注意してください。上のコードでもそうなっていますね。(実際にみて確かめて!)
これは、関数呼び出しでコンパイラが実引数の型や数をチェックするために、その情報をコンパイラが前もって知っている必要があるからです。 (コンパイラfはコードを上から順番にコンパイルしていくのです。)
次のプログラムは、2つの引数をとって前者の値を後者の値の回数だけ掛け合わせた値を返す関数を使っています。
/* べき乗を求める */
#include <stdio.h>
/*--- dxのno乗を返す ---*/
double power(double dx, int no) {
int i;
double tmp = 1.0;
for (i = 1; i <= no; i++)
tmp *= dx;
return (tmp);
}
int main(void) {
int n;
double x;
printf("実数を入力してください:");
scanf("%lf", &x);
printf("整数を入力してください:");
scanf("%d", &n);
printf("%.2fの%d乗は%.2fです。\n", x, n, power(x, n));
return (0);
}
この関数呼び出し power(x, n) で与えた実引数 x, n は関数実行に際して値が関数側の変数にコピーされて実行されるため、関数内部で仮引数 dx, no の値を書き換えても呼び出し元の変数 x, n の値は全く変化しません。このように値がコピーされる変数の受け渡し方法を、値渡し(pass by value) と呼びます。そのため、以下のように関数内部で仮引数の値を関数の中で変更するような処理を使って power( ) 関数を書くことも出来ます(List 6-5)。(while の条件式のなかで no-- を使っているため、引数 no の値が変更されています。)
/*--- dxのno乗を返す(第2版) ---*/
double power(double dx, int no) {
double tmp = 1.0;
while (no-- > 0)
tmp *= dx;
return (tmp);
}
特に値を返さない(戻り値のない)関数を定義する場合、戻り値の型を void として関数を定義します。以下の例を見てみましょう(p. 122, List 6-7)。
/* 直角三角形(左下が直角)を表示(関数版) */
#include <stdio.h>
/*--- *をno個連続表示 ---*/
void put_stars(int no) {
while (no-- > 0)
putchar('*');
}
int main(void) {
int i, ln;
printf("何段ですか:");
scanf("%d", &ln);
for (i = 1; i <= ln; i++) {
put_stars(i);
putchar('\n');
}
return (0);
}
関数定義が void put_starts(int no) で始まっています。void というのは「空っぽ」という意味。つまり、「この関数は空っぽの戻り値を返します」 = 「戻り値を返しません」という関数宣言です。
関数が引数を受け取らない場合、関数宣言の中野引数リストを "void" にします。
以下の例(p. 124, List 6-9)を見てみましょう。関数宣言が int scan_unit(void) で始まっており、この関数 scan_unit() は引数を持たないことがわかります。
/* 読み込んだ非負の整数値を逆順に表示 */
#include <stdio.h>
/*--- 非負の整数を読み込んで返す ---*/
int scan_uint(void) {
int tmp;
do {
printf("非負の整数を入力してください:");
scanf("%d", &tmp);
if (tmp < 0)
puts("\a負の数を入力しないでください。");
} while (tmp < 0);
return (tmp);
}
/*--- 非負の整数を逆転した値を返す ---*/
int rev_int(int num) {
int tmp = 0;
if (num > 0) {
do {
tmp = tmp * 10 + num % 10;
num /= 10;
} while (num > 0);
}
return (tmp);
}
int main(void) {
int nx = scan_uint(); //関数の返却値で変数を初期化することも可能
printf("反転した値は%dです。\n", rev_int(nx));
return (0);
}
関数が理解できると、これまでの講義で書いてきたプログラムに必ずあった以下のコードの意味がわかります。
int main(void)
{
....
return(0);
}
このコードは「main という名前の関数」の定義です。C言語のプログラムでは、かならず一つの(そして一つだけの)main 関数を定義する必要があります。C言語のプログラムが実行されるときには、自動的にこの main 関数が実行されます。そのため、いままでのプログラムでは、みなさんが int main(void){....} の{} の中に書いてきた命令が実行されてきたわけです。
今回勉強したような main 以外の関数は、この 「main 関数の定義」の外の部分で定義され、main 関数の中、もしくは、他の関数の中で呼び出されることで実行されます。(main 関数だけは、他の関数から呼び出すことはできず、C言語のプログラムが実行されるときに自動的に呼び出されます。)
また、「int main (void)」 という書き方の「int」は、この main 関数が整数型の値を返すことを意味しています。通常、main 関数は、処理の最後に
return(0);
として 0 を返すようにするのが普通です。main が 0を返すのは、main が無事に実行されたということをシステムに知らせるための習慣的なルールになっています。「習慣的な」というのは「必ずしも守らなくてもエラーにはならないが、多くのプログラムがこのルールに従って書かれており、このルールに従うことで他のプログラムとの連携がしやすくなる」という意味です。
「int main(void)」の void は、この main 関数が引数を受け取らないことを意味しています。ただし、main 関数は実行時に引数をうけとり、それを使った処理を行うこともできます。これを「コマンドライン引数」といいます。今回の講義ではコマンドライン引数は扱いませんが、興味のある人は「コマンドライン引数」で検索してみてください。(たとえば、以下のページなんかも参考になると思います。)
http://www.ritsumei.ac.jp/~mmr14135/johoWeb/cmnds.html
※ 課題 1-4 は教科書にある「演習」問題です。教科書の関連する解説をみながらといてみてください。
(以下の教科書のページ番号、演習番号は「新・明解C言語(第1版)」のものです。第2版や「新」のつかない「明解C言語」(古い版)を使っている人は適宜ページなどを探してください。
二つの int 型整数の小さい方の値を返す関数を作成せよ。
int min2(int a, int b)
{
.....
}
※ 動作確認するための適切な main 関数なども作成し、完成したプログラムを作ること。以下の課題も同じ。
課題2: 三つの値をうけとり、一つの値を返す関数。
( 教科書 p. 137 演習 6-2)
三つの int 型整数の最小値を返す関数を作成せよ。
int min3(int a, int b, int c)
{
....
}
課題3: 一つの値を受け取り、一つの値を返す関数。
(教科書 p. 141 演習 6-5)
1 から n までの全整数の和を求めて返却する関数を作成せよ。
int sumup(int n)
{
....
}
課題4: 値をうけとらず、結果を「表示」する関数。
(p. 145 演習 6-7)
画面に「こんにちわ。」と表示する関数を作成せよ。
int hello(void)
{
....
}
※ 戻り値( return の値)は0とすること。
課題5: 初速度と仰角を受け取り、水平到達距離を返す関数。
物体を初速度 velocity(m/s)、仰角 angle 度で投げ出した場合の水平到達距離 (m) を求めるプログラムを作成する。velocity と angle を引数として受け取って水平到達距離を返す関数
double hdist( double velocity, double angle);
を作成し、その関数を用いてキーボードから初速度 velocity(m/s) 、仰角 angle 度を入力して水平到達距離 (m) を求めるプログラムを作成せよ。
※かならず hdist という関数を定義し、それを使って水平到達距離を求めるようにしてください。
ただし、重力加速度は9.8m/s2 とし、また空気抵抗は無視するものとする。また、標準ライブラリのサイン関数 (double sin (double)) を用いるには、プログラムの冒頭に #include <math.h> を記述し、でヘッダーファイル math.h を読み込んでおくこと。(#include <...> は... で示されたファイルに書かれたプロラムコードを #include の位置に挿入する命令です。math.h というファイルの中には sin() の定義(のプロトタイプ)が記入されており、#include<math.h>をあなたのプログラムに記入することで sin() が定義され、使えるようになります。
(プロトタイプに関しては後日「マクロ」の回で説明します。)
※ このページは渡辺紀生先生の筑波大学応用理工学情報処理(1クラス)用のページを転載したものです。
オリジナルページは以下のURLから。 http://www.u.tsukuba.ac.jp/~watanabe.norio.fw/C2-8.htm