プログラミング言語 2 第 07 回 (2007 年 06 月 18 日 ) 1 今日の配布物 片面の用紙 1 枚 今日の課題が書かれています 本日の出欠を兼ねています 2/32 1
今日やること http://www.tnlab.ice.uec.ac.jp/~s-okubo/class/language/ にアクセスすると 教材があります 2007 年 06 月 18 日分と書いてある部分が 本日の教材です 本日の内容 関数の自作の基礎の復習 先週の課題の解答例 関数の再帰呼び出し 3/32 関数の自作の基礎 4/32 2
復習 : 関数の作成 #include<stdio.h> int sample01(int max,int st){ max=max+st+2; return max; main(){ 文 value=sample(10,12); 関数には 引数 戻り値 文 ( 本体 ) が必要になる 5/32 関数の作成 戻り値の型を指定関数名引数と引数の型を指定 int sample01(int max,int st){ max=max+st+2; return max; 文 戻り値を指定 構造 戻り値の型を指定関数名 ( 引数と引数の型を指定 ){ ){ 文 return 戻り値 ; 6/32 3
引数 引数と引数の型を指定 int sample01(int max,int st){ max=max+st+2; return max; 関数の引数の型を宣言するときは 変数毎に型を宣言 double x,y,z のように まとめて宣言することはできない 典型的なリファレンス の回の その 2 の書き方と一緒 7/32 戻り値 戻り値の型を指定 int sample01(int max,int st){ max=max+st+2; return max; 戻り値を指定 戻り値の型を指定する必要がある 戻り値がないなら void と書く 実際に戻す値は return の次に書く 8/32 4
引数について 関数の引数には 2 パターンあります 値渡し関数に 値を渡します アドレス渡し関数に 変数のアドレスを渡します それぞれの特徴を捕らえて 適宜使い分ける必要があります 9/32 値渡し 関数の引数として 値を渡す方法 関数に渡されるのは あくまでも 値のみ です 変数が渡されるわけではありません 関数内部では 渡された値を 自前の変数に代入し 処理を行います 自前の変数 tmp に 10 を代入するよ main(){ int scan_value=10; value=sample01(scan_value); 値は 10 だよ int sample01(int tmp){ tmp=tmp+2; return tmp; 10/32 5
アドレス渡しその 1 関数の引数として ( 変数の ) アドレスを渡す方法 関数に渡されるのは 変数のアドレス です 関数内部では 自分で使用する変数のアドレスを 引数として渡されたアドレスに設定します 関数内部と外部で名前は異なる変数かもしれませんが 保存する場所は一緒になります そのアドレス使うよ main(){ int scan_value=10; value=sample01(&scan_value); アドレスを渡すよ int sample01(int *tmp){ *tmp=*tmp+2; return *tmp; 11/32 アドレス渡しその 2 main(){ int scan_value=10; value=sample01(&scan_value); アドレスを渡すので & がついている アドレスを貰うので ポインタを宣言 int sample01(int *tmp){ *tmp=*tmp+2; return *tmp; 関数 main 内の scan_value のアドレス = 関数 sample01 内の *tmp のアドレス 関数 sample01 内で *tmpの値を変えると 関数 main 内のscan_valueの値も変わる 12/32 6
アドレス渡しその 3 どういうときに使うのか? 関数に配列を渡したいとき 配列の内容を まとめて値渡しする事はできない 関数から 2 つ以上の結果を手に入れたいとき 戻り値は 1 つしか指定できない! 変数のアドレスを渡して そのアドレスに保存されている値を関数内で弄ってしまう 13/32 簡単な例 : main(){ int int v,w,value; 省略省略 value=sample02(v,&w); 省略省略 double sample02(int x, x, int int *y){ *y){ x++; x++; *y=x; return x; x; main 中の vの値 wのアドレス sample02 中の xの値 *yのアドレス sample02 中で *y の値を変更 = main 中の w の値を変更 sample02 中で x の値を変更 main 中の v の値を変更 14/32 7
復習 : スコープ #include<stdio.h> int scan_value; int sample01(int max){ int i; i; int value=0; 略 main(){ int st,value; st=sample01(value) 関数の中で宣言された変数は その関数中でしか使用できない 関数の外で宣言された変数は どの関数からでもみることができる 全体で参照できる ( 大域変数 ) sample01 の中でのみ参照できる ( ローカル変数 ) main の中でのみ参照できる ( ローカル変数 ) 大域変数を使用しないなら 関数間の値の受け渡しは 引数や戻り値を通して行う 15/32 第 06 回の課題の簡単な解説 16/32 8
第 06 回の課題 課題 1: 次の使用を満たす関数 sample02 を作成しなさい 引数として 3つのdouble 型の値 x, y, z と double 型の変数 w のアドレスを貰う x ( ) ( ) 戻り値として double 型の値 y ln z cos 1.5 ( ) を返す cos y 関数呼び出し終了時の変数 w の値は 2 x とする 課題のポイント : 引数を きちんと設定できているか 3 つの変数と 1 つのアドレス 戻り値を きちんと返しているか アドレスとして貰った w の値を 適切に設定できているか 17/32 その 1/1: 回答例 double sample02(double x,double y,double z,double *w){ *w= 2*sqrt(x); return (sqrt(pow(x,y))*log(z)*cos(1.5))/fabs(cos(y)); 1 行目 : 3つの変数と1つのアドレスを貰う 関数の引数の型を宣言するときは 変数毎に型を宣言 double x,y,z のように まとめて宣言することはできない 典型的なリファレンス の回の その2 の書き方と一緒 4つ目の変数は アドレスを貰うために ポインタに 戻り値のかたは double 型である 18/32 9
その 1/1: 回答例 double sample02(double x,double y,double z,double *w){ *w= 2*sqrt(x); return (sqrt(pow(x,y))*log(z)*cos(1.5))/fabs(cos(y)); 2 行目 : *w の値を 2*sqrt(x) に アドレス渡しによってアドレスを貰っている *w の値を弄ると そのアドレスに保存されるため 外部に値を返すことができる 19/32 その 1/1: 回答例 double sample02(double x,double y,double z,double *w){ *w= 2*sqrt(x); return (sqrt(pow(x,y))*log(z)*cos(1.5))/fabs(cos(y)); 3 行目 : 戻り値を返す return の次にある値が 戻り値として返される 20/32 10
第 06 回の課題 課題 2: 次の要求を満たすプログラムを書け 要求 1: input.txt を開いて 入力データを読み込む input.txt は 10 行からなっている input.txt の 各行には スペースで区切られて3つの double 型の値が並んでいる 順に x,y,z とする 要求 2: output.txt に出力を書き込む output.txt は 10 行からなる input.txt の 各行には スペースで区切られて 2つの double 型の値が並ぶ 1つめの値は2 x とする 2つめの値は x y ln ( z) cos( 1.5) とする cos( y) 21/32 その 1/2: 回答例 01: main(){ 02: int i; i; 03: double x,y,z,w; 04: double value; 変数の宣言をします x,y,z : 読み込み用 w : sample02 にアドレスを渡して 値を貰う用 value : sample02 の戻り値受け取り用 22/32 11
回答例 その2/2: 05: for(i=1;i<=10;i++){ 06: scanf("%lf%lf%lf",&x,&y,&z); 07: value=sample02(x,y,z,&w); 08: printf("%f %f n",w,value); 09: 10: 10 行有るので 10 回繰り返す 23/32 その 2/2: 回答例 05: for(i=1;i<=10;i++){ 06: scanf("%lf%lf%lf",&x,&y,&z); 07: value=sample02(x,y,z,&w); 08: printf("%f %f n",w,value); 09: 10: 標準入力から3つのデータを読み込む./a.out < input.txt として 標準入力をファイルからにする事してみたため 24/32 12
その 2/2: 回答例 05: for(i=1;i<=10;i++){ 06: scanf("%lf%lf%lf",&x,&y,&z); 07: value=sample02(x,y,z,&w); 08: printf("%f %f n",w,value); 09: 10: sample02を実行して 戻り値をvalueに代入 引数は 3つの値と 1つのアドレス アドレスなので & がついている 25/32 その 2/2: 回答例 05: for(i=1;i<=10;i++){ 06: scanf("%lf%lf%lf",&x,&y,&z); 07: value=sample02(x,y,z,&w); 08: printf("%f %f n",w,value); 09: 10: 標準出力に 2つの値を出力 printfは 標準出力に出力を行う命令 26/32 13
main と sample02 の変数の関係 main(){ double sample02(double x,double y, y, double int int i; z,double *w){ *w){ i; double x,y,z,w; *w= *w= 2*sqrt(x); double value; 略 for(i=1;i<=10;i++){ scanf("%lf%lf%lf",&x,&y,&z); main 中の sample02 中の value=sample02(x,y,z,&w); xの値 xの値 printf("%f %f n",w,value); yの値 yの値 zの値 zの値 wのアドレス *wのアドレス sample02 中で *w の値を変更 = main 中の w の値を変更 27/32 関数の再帰呼び出し 28/32 14
関数の再帰呼び出しとは ある関数内で 自分自身を関数として使用することができる 関数の再帰呼び出し たとえば... int sample03(int i){ if(i=1){ return 1; 1; else{ return i*sample03(i-1); 関数 sample03 の中で 関数 sample03 を使用している! 29/32 停止条件 再帰する関数を書く場合 停止条件が重要 停止条件が無い関数を書いてしまうと... int sample03(int i){ return i*sample03(i-1); sample03の中でsample03を呼び 呼び出された sample03 の中で sample03 を呼び 更に呼び出された sample03 の中で sample03 を呼び... 止まらないプログラムの完成 30/32 15
停止条件の例 停止条件を加えると... int sample03(int i){ if(i=1){ i=1なら return 1; 1; 再帰しない else{ i=1でないなら return i*sample03(i-1); 再帰呼び出し 引数として 最初に正の整数が与えられれば 1つずつ小さくなっていくので いずれ 再帰が止まる 止まるプログラムの完成 31/32 再帰の様子 sample03(3) の実行の様子 sample03(3) の実行 01: 01: int int sample03(int i){ i){ 02: 02: if(i=1){ 03: 03: return return 1; 1; 04: 04: else{ else{ 05: 05: return return i*sample03(i-1); 06: 06: 07: 07: 02 02 行目行目 :i :i > 1 なので else else 文以下を実行 05 05 行目行目 :i :i * sample03(2) の計算のため sample03(2) を実行 sample03(2) の実行 02 02 行目行目 :i :i > 1 なので else else 文以下を実行 05 05 行目行目 :i :i * sample03(1) の計算のため sample03(1) を実行 sample03(1) の実行 02 02 行目行目 :i :i = 1 なので if if 文以下を実行 04 04 行目行目 : 戻り値として 1 を返す sample03(1)=1なので i*sample03(1) = 2 を戻り値として返す sample03(2)=2 なので i*sample03(2) = 6 を戻り値として返す 32/32 16
誤植のおもひで 14 ページ : 誤 :sample02 中で *w の値を変更 正 :sample02 中で *y の値を変更 32 ページ : 最後から 2 行目 誤 : i*sample03(2) = 2 を戻り値として返す 正 : i*sample03(1) = 2 を戻り値として返す 32 ページ : 最後の行 誤 : i*sample03(3) = 2 を戻り値として返す 誤 : i*sample03(2) = 2 を戻り値として返す 33/32 17