基礎プログラミング 第13回(2007年5月28日) 「関数」の補足説明 Report-Fの解説
ループを使わない場合,似た処理を繰り返し書く必要があった static void Main(string[] args){ int a,b,c; do { Console.WriteLine(“正の数を入力して”); a = int.Parse(Console.ReadLine()); } while (a <= 0); b = int.Parse(Console.ReadLine()); } while (b <= 0); c = int.Parse(Console.ReadLine()); } while (c <= 0); }
ループを使うと,似た処理をまとめることができた static void Main(string[] args){ int[] a = new int[3]; for(int i=0; i<3; i++){ do { Console.WriteLine(“正の数を入力して”); a[i] = int.Parse(Console.ReadLine()); } while (a[i] <= 0); }
関数を使っても,似た処理をまとめることができる static int InputPositiveInt(){ int x = 0; do { Console.WriteLine(“正の数を入力して”); x = int.Parse(Console.ReadLine()); } while (x<=0); return x; } static void Main(string[] args){ int[] a = new int[3]; for(int i=0;i<3;i++) a[i] = InputPositiveInt();
関数の「引数」と「返値」 Mainの中の「似た処理」を,まとめて関数に追い出したい 関数にデータを渡すときには「引数」で渡す 関数内で処理したデータをMain(呼び出し元)で 受け取るときには「返値」を受け取る 関数は,返値を返すときにreturn [変数]; を用いる
数値(値型)の場合,【値渡し】 1: static void Main(string[] args){ 2: int a = Double(2); 3: int b = Double(a); 4: Console.WriteLine(“a={0}, b={1}”, a, b); 5: } 6: static int Double(int x){ 7: x = x * 2; 8: return x; 9: }
3: int b = Double(a); 2: int a = Double(2); 8 4 4 4 8 2 Double(2); 4; 8; b = Double(a); 変数の世界(Main) static int Double(int x = 2); static int Double(int x = a); int x 8 4 4 4 8 2 x = x * 2; x = x * 2; 変数の世界(Double) return x; return x;
配列(参照型)の場合, 【参照渡し】 1: static void Main(string[] args){ 2: int[] a = new int[]{2,3,5}; 3: Double(a); 4: for(int i=0;i<3;i++) Console.WriteLine(“a[{0}]= {1}”, i, a[i]); // 確認用の出力 5: } 6: static void Double(int[] x){ 7: for(int i=0;i<x.Length; i++) x[i] = x[i] * 2; 8:}
3: Double(a); Double(a); static void Double(int[] x = a); 1 2 int[] a 4 2 3 6 10 5 参照型の=は リンクを張る という意味 変数の世界(Main) static void Double(int[] x = a); int[] x x[1] = x[1] * 2; x[2] = x[2] * 2; x[0] = x[0] * 2; 変数の世界(Double) return;
引数の「値渡し/参照渡し」について 結局,=のときと同じ考え方でよいことになる 引数の型が値型(int, double, float, bool, etc...) なら,引数の変数(x)は関数呼出しの値(a)を 「コピーする」 引数の型が参照型(配列,String, etc...) なら,引数の変数(x)は関数呼出しの値(a) (データ)に「リンクを張る」 値型のデータを無理矢理「参照渡し」する方法も用意されてはいる (ref, outキーワード) 基礎プロの範囲外なので説明省略
11回講義スライド17枚目の意図:Main関数が 値型のデータを関数に渡し,さらに関数内で処理してもらった結果を受取るときはreturnが必要 static int Double(int x){ x = x * 2; return x; } static void Main(...){ int a = 5; a = Double(a); //返値をaで受取った Console.WriteLine(a); // 画面には[10]と表示される static void Double(int x){ x = x * 2; } static void Main(...){ int a = 5; Double(a); //渡しっぱなし Console.WriteLine(a); // 画面には[5]と表示される
例1: 自作の Array.Copy() 関数 (ただしint[] のみを受け付ける簡易版) class Array { static void Copy(int[] orig, int[] copy, int length) { for(int i=0; i< length; i++) copy[i] = orig[i]; } } // 使い方は,第9回の6枚目のスライドを参照すること
例2:引数に渡した配列のコピーを作成して返す関数 (ただし,配列要素の並び順を逆にして返す) static int[] CopyAndReverseAry(int[] x) { int[] revcopy = new int[x.Length]; for(int i=0;i<x.Length;i++) revcopy[i] = x[x.Length - i - 1]; return revcopy; } //この関数は,Report-Fのヒントになっています static void Main(...){ int[] a = new int[3]{2,3,5}; int[] reva = CopyAndReverseAry(a); }
もう一度,“=”の意味を再確認 3 =の左右は同じ型, (ただし,左は変数,右はデータとみなす) =の左右が「値型」(int, float, etc...)なら =の右側(データ)を,=の左側の変数(箱)に 「代入」する =の左右が「参照型」(String, 配列, String Builder, その他のオブジェクト)なら =の左側にある「変数」のリンク先を,=の右側(データ)に結びつける(またはリンクを張りなおす) 3
Report-F の復習[17CopyAry] intの配列を引数とし,その配列のコピーを 返す関数CopyArrayをつくってください static int[] CopyArray(int[] ary) ★動作確認プログラムもつくること!! 動作確認プログラムは, 9回目(5月14日)のプリントを参考に,本当に「コピー」できているかどうか,までを 確認可能なものにすること (注)Array.Copy()関数の使用は禁止 (ヒント)関数内部で新しい配列の『容器』を作成し,ループで1つ1つintの数値をコピーしたあと,return 文で「作成した配列」を返す
Report-F 課題の意図 intの配列をコピーするCopyAry()関数をつくる Main()関数からCopyAry()関数を呼ぶ ここでの「コピー」とは,同じ値が入ったデータの実体を,オリジナルとは別に作ってください,という意味 「同じ数値が入っていることを確認してくれ」という意味ではない 「コピー」よりも,「バックアップ」と表現したほうがわかりやすかったかもしれない 言い換えると,「きちんと配列がバックアップされているかどうか」を調べればよい. そのためには,仮にバックアップデータを変更してみて,オリジナルが変更されないことを確かめればよい
Report-F 残念なプログラム (CopyAry()関数) static int[] CopyAry(int[] ary){ int[] copy = new int[ary.Length]; for(int i=0 ; i < ary.Length; i++) copy[i] = ary[i] * 2; //2倍を代入 return copy; } //CopyAry()関数の内部では,純粋に 配列要素のコピーだけを実行してほしかった
Report-F あとひといきのプログラム (CopyAry()関数) static int[] CopyAry(int[] ary){ //配列サイズ制限有 int[] copy = new int[3]; for(int i=0 ; i < 3 ; i++) copy[i] = ary[i]; return copy; } static int[] CopyAry(int[] ary){ //配列サイズ制限無し int[] copy = new int[ary.Length]; for(int i=0 ; i < ary.Length; i++) copy[i] = ary[i];
Report-F 多かった間違い(1) (Main()での確認プログラム) int[] orig = new int[3]{2,3,5}; int[] backup; backup = CopyAry(orig); // バックアップ実行 for(int i=0;i<3;i++) Console.WriteLine( “orig[{0}] = {1}”, i , orig[i]); for(int i=0;i<3;i++) Console.WriteLine( “backup [{0}] = {1}”, i , backup[i]*2);
Report-F 多かった間違い(2) (Main()での確認プログラム) int[] orig = new int[3]{2,3,5}; int[] backup; backup = CopyAry(orig); // バックアップ実行 for(int i=0;i<3;i++) Console.WriteLine( “orig[{0}] = {1}”, i , orig[i]); for(int i=0;i<3;i++) backup[i]*=2; for(int i=0;i<3;i++) Console.WriteLine( “backup [{0}] = {1}”, i , backup[i]);
Report-F 正解例 ( Main()での確認プログラム部分) int[] orig = new int[3]{2,3,5}; int[] backup; backup = CopyAry(orig); // バックアップ実行 for(int i=0;i<3;i++) backup[i]*=2; for(int i=0;i<3;i++) Console.WriteLine( “orig[{0}] = {1}”, i , orig[i]); for(int i=0;i<3;i++) Console.WriteLine( “backup [{0}] = {1}”, i , backup[i]);
練習問題13-1 (各自プロジェクトを作成すること) StringBuilderの配列を1つ受け取り,その 完全なコピー(フルバックアップ)を作成して返す 関数CopyStrBuildAry()を作成しなさい. static StringBuilder[] CopyStrBuildAry(StringBuilder[] sbary){ ... } 完全なコピー(フルバックアップ)とは,オリジナル,コピーそれぞれが完全に独立したデータ実体を持っている状態を指すものとする. Main()関数では,関数CopyStrBuildAry()の動作を確認する処理を記述しなさい. ヒント:intの場合はcopy[i]=ary[i]でコピーできたが,StringBuilderの場合は=で実体コピーできないので new StringBuilder(sbary[i].ToString()) (新規作成)
13-1 完全なコピーになっていない駄目な例 (配列の要素が共有されている) StringBuilder[] 型の変数 orig StringBuilder[] 型の変数 backup Main() StringBuilder[] 型の変数 copy (返値用) StrBuildAry()
13-1 期待する状態(完全なコピー) (配列の要素を共有していない.独立したデータ実体) 13-1 期待する状態(完全なコピー) (配列の要素を共有していない.独立したデータ実体) StringBuilder[] 型の変数 orig StringBuilder[] 型の変数 backup Main() StringBuilder[] 型の変数 copy (返値用) StrBuildAry()