プログラミングおよび演習 NO.9 |
関数 |
<-- 戻り値を返す
関数の呼び出し -->(2) 関数処理の開始
受け取った引数を用いて、関数本体の処理を開始します。関数の始りの{の直後の実行文から、順番に処理を進めていきます。(3) 関数処理の終了
関数の終わりの}に到達するか、または、return文やexit文が実行されると、関数処理が終了します。(4) 呼出し側の処理の再開
関数が終了すると、呼出した元の場所にジャンプして戻ってきます。その際、呼び出し側は、関数から戻り値を受け取ります。 その後、中断していた呼び出し側の処理が再開され、呼び出し地点の次の行の処理を進めていきます。関数を使うメリット: 関数を使うことで、以下のようなメリットが生まれます。
|
の2種類に分けることができます。
(1)はこれまで習ってきたように、例えば
などがそうです。それぞれの関数の種類に応じて、ヘッダーファイルをインクルードして、関数の型宣言をします。そうしておいて、必要なところで、
(1) 変数名a=関数名(引数1,引数2‥‥);のように、式の中の一部に関数を記述することもできます。その場合、該当箇所で関数呼び出しが行われ、戻り値が代入される形で式の計算が行われます。
以上は、戻り値を持つ関数の場合ですが、戻り値を持たない関数呼び出しは、
のようになります。例えば、
printf("Hello");とすると、文字列定数"Hello"を引数として、printf()関数が呼び出されます。
呼び出されたprintf()関数は、引数として受け取った文字列"Hello"を画面に表示します。
このように、関数呼び出しが行われると、
のようにプログラム処理が進みます。
(2)のユーザが定義した関数についても、考え方は同じです。違う所は、関数本体の定義(関数の本体の作成)と、関数の型宣言(ヘッダファイルの部分)を自分で用意する必要があることです。具体的には、以下のようになります。
関数の呼び出し側(関数の使用)
・関数の型宣言
関数を呼び出す前に、以下のように関数の型宣言が必要です。これは変数を使う前に型宣言が必要であったのと同じです。変数の場合と違うのは、引数の型(引数は複数ある)と、戻り値の型の、各々について型宣言する必要があることです。
戻り値の型 関数名(引数1の型,引数2の型,‥‥);
例:float add(float,float);
型宣言をした後、プログラム本体で、上記(1)で説明した関数呼び出しのルールに従って次のような形で関数を呼び出します。
・関数の呼び出し
宣言した関数は、次のように呼び出すことができます。
①変数=関数(引数1,引数2,‥‥);(戻り値がある場合)
②関数(引数1,引数2,‥‥); (戻り値がない場合)
例えば、この後の練習問題に従って、戻り値がある関数addは、
………
a=200.0;
b=300.0;
c=add(a,b);
のように使うことができます。この例では 引数をa=200,b=300に設定して 関数add(a,b)をよびだす、戻り値(関数の計算結果)をcに代入する、という処理を行います。この例のように、引数に変数(或いは式)を用いてもよいし、
c=add(200.0,300.0);
のように定数を用いることもできます。あるいは、
c=50*add(200.0,b)-4.0;
のように、式の一部に関数を使うこともできます。
関数の定義(関数の本体、呼び出され側)
次に、呼び出される関数の本体を次のように書きます。
■void型
関数の引数が無かったり、もしくは戻り値が無い場合があります。
まず、引数がない場合は、引数が無いことがわかるように
のように、引数にvoid(空という意味)を指定します。または
戻り値の型 関数名()のように()の中に何も書かないで使います。
同様に戻り値がない(return文の()の中が空、あるいはreturn文がない)場合は、
のように、戻り値の型をvoidに指定します。
関数を使うときの注意 ●呼び出し側の引数と、呼び出された関数本体の型を一致させる。 (もちろん、引数の数と順番も一致させる) ●関数の戻り値の型とreturn文の変数の型を一致させる。 関数のプログラムがうまく動かない場合の殆どは、上記の間違いが原因です。 |
例9-1#include<stdio.h> #include<math.h> /* 関数の型宣言 */ float add(float,float),sub(float,float),mul(float,float); float div(float,float),mag(float,float); int main(void) /* メイン関数 */ { /* 変数の宣言 */ float a,b,ans1,ans2,ans3,ans4,ans5; /* ここからが実行文(プログラムの本体) */ printf("Input a=");fflush(0);scanf("%e",&a); printf("Input b=");fflush(0);scanf("%e",&b); ans1=add(a,b); /* 関数add()の呼び出し */ ans2=sub(a,b); /* 関数sub()の呼び出し */ ans3=mul(a,b); /* 関数mul()の呼び出し */ ans4=div(a,b); /* 関数div()の呼び出し */ ans5=mag(a,b); /* 関数mag()の呼び出し */ printf("a+b=%f\n a-b=%f\n a*b=%f\n a/b=%f\n sqrt(a**2+b**2)=%f\n", ans1,ans2,ans3,ans4,ans5); return(0); } /* 和を求める関数 */ float add(float x, float y) { float z; z=x+y; return(z); } /* 差を求める関数 */ float sub(float x, float y) { float z; z=x-y; return(z); } /* 積を求める関数 */ float mul(float x, float y) { float z; z=x*y; return(z); } /* 商を求める関数 */ float div(float x, float y) { float z; z=x/y; return(z); } /* 2つの数の2乗の和の平方根 */ float mag(float x, float y) { float z; z=sqrt(x*x+y*y); return(z); } |
演習問題9-1 (Revised : 2014/10/20) 例9-1において、 ①七つの引数a,b,c,d,e,f,gの相加平均 (a+b+c+d+e+f+g)/7 を求める関数 amean(a,b,c,d,e,f,g) ② 〃 の中央値を求める関数 median(a,b,c,d,e,f,g) を追加してください。 なお、レポートには、必ず、手計算の結果との比較、プログラムの動作説明、関数を用いることのメリットなどに関する考察を含めてください。 [中央値の補足説明] 中央値は、データを大きさの順に並べたとき、データの数を2等分する位置の変数の値をいいます。 例えば、データの個数が7個の場合、大きい方から数えて(小さい方から数えても同じ)4番目のデータになります。データの並べ替え(ソーティング)は第8回例8-4のプログラムを利用してください。 |
演習問題9-2 (Revised : 2012/10/23) ウエスト周囲長と性別の2つの情報をキーボードからデータを入力すると、メタボリック症候群の可能性の有無を表示するプログラムを作成してください。メタボリック症候群の可能性を判定する関数を呼び出し、その戻り値をもとに判定結果を画面表示するプログラムを作ってください。詳細は以下の説明に従ってください。 実行結果は、手作業の結果と比較して正しく動作している事を確認して下さい。考察には手計算との照合結果とプログラムの動作説明を示してください。 [判定結果の表示] メタボリック症候群の判定基準には、世界糖尿病連盟(IDF)と日本肥満学会(JASSO)の各々の機関が推奨している2種類の基準があります。 以下の判定条件のもとに、IDFかJASSOの基準のうちのいずれかでメタボリック症候群の可能性有と判断された場合、その基準名と共に「メタボリック症候群の可能性有、血圧や中性脂肪などの値を確認して下さい」と表示する関数を作ってください。なお、両方の基準で可能性有と判断された場合には、基準名を両方表示した上で「メタボリック症候群の可能性有、血圧や中性脂肪などの値を確認して下さい」と表示し、両方の基準が共に可能性を否定した場合には、「メタボリック症候群の可能性無」と表示すること。 [判定条件] ウエスト周囲長(cm) と性別(Male:0,Female:1)を入力し、以下の条件を満たした時にメタボリック症候群の可能性有と判断する。 世界糖尿病連盟(IDF)の基準 ウエスト周囲長が 男性≧90cm、女性≧80cm のとき 日本肥満学会(JASSO)の基準 ウエスト周囲長が 男性≧85cm、女性≧90cm のとき [関数の引数] ウエスト周囲長(cm) AC 性別(Male:0,Female:1) Sex [関数の呼び出し] Metabol(AC,Sex) [関数の戻り値] メタボリック症候群の可能性無し 0 IDFの基準でメタボリック症候群の可能性有り 1 JASSOの基準でメタボリック症候群の可能性有り 2 IDFおよびJASSOの両方の基準でメタボリック症候群の可能性有り 3 |
変数の有効範囲と記憶クラス |
のように宣言します。ただし、autoを省略してもよいことになっていますので、普段は
int a;と書きます。今まで例題で使ってきた変数は全て自動変数です。
静的変数(static)
静的変数は、関数の実行が終了しても変数の中身は記録されて残っています。また、初期値を指定しない場合、最初の値を0に初期化します。整数型の静的変数aは
と宣言することにより使用できます。
以上の他に、レジスタ変数や外部変数がありますが、使用頻度は低いので、ここでは説明を省略します。
■有効範囲
C言語では、変数が何処で宣言されているかによって、その変数の有効範囲が決まります。 大きく分けて、プログラム全体にわたって共通して使える①グローバル変数と、 各々の関数の中だけで使える②ローカル変数の2つがあります。
①グローバル変数(プログラム全体で有効)
以下の例のように、main()を含めた関数の外側(ファイルの先頭部分)に、宣言して使います。
#include<stdio.h> int a; /*整数型変数a( グロ-バル変数)の宣言*/ /*以下のプログラム全体に渡って有効*/
|
#include<stdio.h> int main(void) {
int func(int x) /* 引数xはローカル変数*/ {
|
の違いがあることに注意してください。
異なる関数の中で, 同じ名前のローカル変数が使われている場合、各々は独立したまったく別の変数であることに注意してください (上図の例において、関数mainの中のaと関数funcの中のaは、名前が同じだけで全く別の変数)。
利点/欠点: グローバル変数の逆の利点/欠点がある。グローバル変数は、 プログラム全体で共通的に使用するデータや、サイズの大きい配列データなど、に用います。その他の場合は、
プログラムのモジュール化やカプセル化の考えにたって、なるべくローカル変数を使うようにします。
■関数の型宣言の場所と有効範囲
以下は、例9-1の関数magの宣言と呼び出し部分を抜粋したものです。そこでは、
#include<stdio.h>
int main(void)
{
float mag(float,float);
‥‥
}
のように、関数magの型宣言を呼び出す関数(上記の場合はmain関数)の中に書いています。 この場合、関数magの型宣言はmain関数の中だけで有効です。
言い換えると、main関数以外の関数からmag関数を呼び出す場合、呼び出す関数のそれぞれに、関数の型宣言が必要です。これに対して、例9-1のように、
#include<stdio.h>
float mag(float,float);
int main(void)
{
‥‥
}
として、main関数の外に書くこともできます。この場合、関数magの型宣言はプログラム全体にわたって有効です。 すなわち、上記のように宣言すると、main以外のどの関数からも関数magを呼び出すことができます。
以下では、品物の税抜き価格と消費税率を与えた時に、税込み価格を計算する関数(TotalPrice)を使うプログラムについて説明します。最初の例9-2では、関数データの受け渡し方法として、通常の引数(税抜き価格と消費税率の2つの引数)と戻り値(税込み価格)を用いて実現しています。
例9-2(ローカル変数を用いた関数データの受け渡し)(Revised : 2013/10/21)#include<stdio.h> #include<math.h> int main(void) { int TotalPrice(int, int); int price,taxrate,total; printf("Before-tax price="); fflush(0); scanf("%d",&price); printf("Rate of tax(%%)="); fflush(0); scanf("%d",&taxrate); total=TotalPrice(price,taxrate); printf("Tax-included price=%d\n",total); return(0); } int TotalPrice(int x, int y) { int z; z=x+x*y*0.01; return(z); } |
例9-3(グローバル領域に宣言した変数や関数を用いる形に書き変えた場合)(Revised : 2013/10/21)#include<stdio.h> #include<math.h> void TotalPrice(void); int price,taxrate,total; int main(void) { printf("Before-tax price="); fflush(0); scanf("%d",&price); printf("Rate of tax(%)="); fflush(0); scanf("%d",&taxrate); TotalPrice(); printf("Tax-included price=%d\n",total); return(0); } void TotalPrice(void) { total=price+price*taxrate*0.01; return; } |
演習問題 9-3 例9-2、9-3の各々のプログラムの動作を確認して説明してください。特に、関数への変数の受け渡し方や計算結果の受け取り方に注目してそれぞれのプログラムの動作を説明し、その上で両者の出力が同じになる理由を述べてください。 |
演習問題 9-4 (Revised : 2013/10/21)#include <stdio.h> int gi,si; int main(void) { int i,ai=0; void count(); printf("\t gi\t si\t ai\n"); for(i=0; i<3; i++){ count(); gi=gi+1; si=si+1; ai=ai+1; printf("main()\t gi= %d\t si=%d\t ai=%d\n",gi,si,ai); } return(0); } void count(void) { int i, ai=0; static int si; for(i=0; i<3; i++){ gi=gi+5; si=si+5; ai=ai+5; printf("count()\t gi= %d\t si=%d\t ai=%d\n",gi,si,ai); } return; } (1) 上記のプログラムを実行し、表示結果を説明しなさい。特に、gi,si,aiの三つの変数は、同じ計算を行っていることに注意し、宣言文の位置が違うだけで、三者三様の結果が表示される理由について、プログラムの動作説明を含めながら詳細に説明をしなさい。 (2) main関数内およびcount関数内の各々の自動変数aiの初期化について、以下のように変更するとどうなるかを答えなさい。具体的には、以下の3通りについて試し、各々の結果について、その理由をつけてレポートにまとめなさい。 ①count関数内のaiだけ0に初期化し、main関数のaiは宣言のみする場合 ②main関数内のaiだけ0に初期化し、count関数のaiは宣言のみする場合 ③main関数内とcount関数内のaiは両方とも初期化せずに宣言のみする場合 (3) プログラムを最初の状態に戻した後で、count関数内の静的変数siの 宣言文を取り除くとどうなるか試し、 レポートには結果とともにその理由について述べなさい。 |
#include<stdio.h>
/*このinclude文の意味は、stdio.hというファイルをここに挿入するという意味です。*/
また数学関数を使う時は、
#include<math.h>のように、必要なヘッダファイル(stdio.hやmath.h)をインクルード(挿入)してから、使うように説明してきました。 何故、インクルードしないと使えないのでしょうか?その答えは、次のようになります。
実は、stdio.hやmath.hの中身は、各々が用意しているいろいろな関数の型宣言が書いてあります。
例えば、math.hの中身は以下のような宣言文が並んでいます。