p.181 8.1 メソッドの再帰呼び出し
・C#などでは、メソッドの内部で自分自身を呼び出すことができる。これを再帰(リカージョン)という
・再帰を用いることで、アルゴリズムをシンプルに表現できる場合があり、重宝されている。
・ただし、再帰のみを記述する無限ループになり、戻り情報を残しながら繰返すので、これに用いられるスタックメモリの使いつくしによる
異常終了となる
・そこで、終了条件を記述する必要がある
・基本的構文: メソッド名(引数①) { if(終了条件?) { return 値; } else { return メソッド名(引数②); }
・見かけ上は繰返しではないが、自分を呼び出すことでループ構造になることに注意
p.181 階乗を計算する(メソッドの再帰呼び出しの一例)
・ある整数nから1までの全整数の積を、nの階乗といい、整数!で示す ・例: 2! = 2、 3! = 6、4! = 24、5! = 120 ・よって、階乗の計算式は n! = n * (n-1) * (n-2) * (n-3) * … * 2 であり、 (n - 1)! = (n-2) * (n-3) * … * 2 だから、 n! = n * (n-1)! と表すことができるので再帰呼び出しになる ・そして、階乗のルールとして、0!は1、1!は1 なので、これらを再帰の終了条件にすれば良い
アレンジ演習:p.183
・表示の桁数を設定し、再帰の回数も表示するようにしよう ・publicインスタンス変数cntを0で初期化し、再帰呼び出し時にインクリメントし、結果表示時に併記すれば良い 0! = 1, 0 1! = 1, 1 2! = 2, 3 3! = 6, 6 4! = 24, 10 5! = 120, 15 6! = 720, 21 7! = 5040, 28 8! = 40320, 36 9! = 362880, 45 10! = 3628800, 55 11! = 39916800, 66 12! = 479001600, 78 13! = 6227020800, 91 14! = 87178291200, 105 15! = 1307674368000, 120 16! = 20922789888000, 136 17! = 355687428096000, 153 18! = 6402373705728000, 171 19! = 121645100408832000, 190 20! = 2432902008176640000, 210 ・それから、nが1の場合も1を返すようにして、再帰の回数が減ることを確認しよう 0! = 1, 0 1! = 1, 0 2! = 2, 1 3! = 6, 3 4! = 24, 6 5! = 120, 10 6! = 720, 15 7! = 5040, 21 8! = 40320, 28 9! = 362880, 36 10! = 3628800, 45 11! = 39916800, 55 12! = 479001600, 66 13! = 6227020800, 78 14! = 87178291200, 91 15! = 1307674368000, 105 16! = 20922789888000, 120 17! = 355687428096000, 136 18! = 6402373705728000, 153 19! = 121645100408832000, 171 20! = 2432902008176640000, 190
作成例
//アレンジ演習:p.182 fact01.cs
using System;
class Fact {
public int cnt = 0; //【追加】再帰の回数
public long CalcFact(int n) { //再帰呼び出しされるメソッド
long fact; //階乗用
if (n == 0 || n == 1) { //【変更】0!または1!であれば(終了条件)
fact = 1; //階乗は1
} else { //でなければ
cnt++; //【追加】再帰の回数カウントアップ
fact = n * CalcFact(n - 1); //階乗は義に従い n × (n-1)!
}
return fact; //階乗を返す
}
}
class fact01 {
public static void Main() {
Fact f = new Fact();
for (int i = 0; i <= 20; i++) { //0!から20!まで
Console.WriteLine("{0,2}! = {1,20}, {2,3}", i, f.CalcFact(i), f.cnt); //【変更】
}
}
}
アレンジ演習:p.182 fact01.cs
・変数factを用いずに、結果を直接returnしても同じ動作になることを確認しよう
作成例
//アレンジ演習:p.182 fact01.cs
using System;
class Fact {
public int cnt = 0; //再帰の回数
public long CalcFact(int n) { //再帰呼び出しされるメソッド
if (n == 0 || n == 1) { //0!または1!であれば(終了条件)
return 1; //【変更】階乗は1
} else { //でなければ
cnt++; //再帰の回数カウントアップ
return n * CalcFact(n - 1); //【変更】階乗は定義に従い n × (n-1)!
}
}
}
class fact01 {
public static void Main() {
Fact f = new Fact();
for (int i = 0; i <= 20; i++) { //0!から20!まで
Console.WriteLine("{0,2}! = {1,20}, {2,3}", i, f.CalcFact(i), f.cnt);
}
}
}
アレンジ演習:p.182 fact01.cs
・条件演算子を用いて、さらにシンプルにしよう(回数の表示は割愛してOK)
作成例
//アレンジ演習:p.182 fact01.cs
using System;
class Fact {
public long CalcFact(int n) { //再帰呼び出しされるメソッド
return (n <= 1) ? 1 : n * CalcFact(n - 1); //【変更】1以下の階乗は1、でなければ n × (n-1)!
}
}
class fact01 {
public static void Main() {
Fact f = new Fact();
for (int i = 0; i <= 20; i++) { //0!から20!まで
&nbs候補はありWriteLine("{0,2}! = {1,20}", i, f.CalcFact(i)); //【変更】
}
}
}
p.185 フィボナッチ数列を求める
・フィボナッチ数列とは、1番目と2番目は1で、3番目以降は前2つの和になっている数列
・よって、n番目の値は(n-1)番目の値と(n-2)番目の値の和なので、再帰で表すことができる
・つまり、n番目の値を得るメソッドは:
メソッド(n) { return (n <= 2) ? 1 : メソッド(n-1) + メソッド(n-2); }
アレンジ演習:p.185 fibonacci.cs
・上記の例の通り、条件演算子を用いてシンプルにしよう
作成例
//アレンジ演習:p.167 fibonacci.cs
using System;
class fibo {
public long CalcFibo(int n) {
return (n <= 2) ? 1 : CalcFibo(n - 1) + CalcFibo(n - 2); //1,2番目は1で、3番目以降は前2つの和
}
}
class fibonacci {
public static void Main() {
fibo f = new fibo();
for (int i = 1; i <= 30; i++) {
Console.WriteLine("f({0}) = {1}", i, f.CalcFibo(i));
}
}
}
p.188 refとout(値渡しと参照渡し)
・p.189のswap01.csの説明にある通り、C#のメソッドにおける引数は「値のコピーが渡される」 ・よって、メソッド中で引数の値を書き換えても、元の値は変わらない ・このことを「値渡し」といい、安全性を高めている ・言語によっては、参照型データにおいて「値渡し」にはならない場合があるがC#では参照型データも「値渡し」 ⇒ p.190 swap02.cs ・対して、配列などのデータ構造を引数にすると「値のコピーが渡す」のではなく、効率性のために、参照(構造の位置情報)が渡される ・このことを「参照渡し」といい、大きな構造を渡す場合の効率を高めている ⇒ p.191 changearray01.cs ・「参照渡し」にすると、引数を値の受け渡しに利用できるので、returnを用いずに結果を返すことができ、しかも2個以上の情報を返す ことが可能 ・例:配列[0]と[1]の和を[2]に、差を[3]に、積を[4]に格納するメソッド
アレンジ演習:p.191 changearray01.cs
・引数の配列[0]と[1]の和を[2]に、差を[3]に、積を[4]に格納するメソッドにしてみよう
作成例
//アレンジ演習: p.191 charngearray01.cs
using System;
class change {
public void modify(int[] array) { //配列を参照渡しで受け取って書き換えるメソッド
array[2] = array[0] + array[1]; //配列[0]と[1]の和を[2]に格納する
array[3] = (array[0] > array[1]) ? array[0] - array[1] : array[1] - array[0]; //差を[3]に格納する
array[4] = array[0] * array[1]; //積を[4]に格納する
}
}
class changearray01 {
public static void Main(){
change c = new change();
int[] a = {10, 20, 0, 0, 0};
c.modify(a); //配列を参照渡しで渡して書き換えてもらう
Console.WriteLine("{0}と{1}の和は{2},差は{3},積は{4}", a[0], a[1], a[2], a[3], a[4]);
}
}
p.192 ref
・値において、呼び出し側の引数(実引数)とメソッド側の引数(仮引数)の両方に「ref」キーワードをつけると、
値渡しではなく参照渡しになる
・呼び出し側の書式: メソッド名(ref 実引数, …)
・メソッド側の書式: 戻り値型 メソッド名(ref 仮引数, …) {…}
・呼び出し側の実引数は変更可能な変数であること(式や定数やリテラルやデータ構造を渡すことはできない)
・実引数と仮引数の名前は異なって良い
・複数の引数がある場合、ref付きとref無しが混在しても良い
・なお、実引数は初期化または値の代入がされていることが条件
アレンジ演習:p.193 swap03.cs
・2実数を受け取るオーバロードメソッド void Swap(ref double x, ref double y) を追記しよう ・そして、double d1 = 3.14, d2 = 2.2 をref付きで渡して、実引数と仮引数の名前が異なっても動作することを確認しよう
作成例
//アレンジ演習:p.193 swap03.cs
using System;
class myclass {
public void swap(ref int x, ref int y) { //2引数を参照渡しで扱うメソッド
int temp = x; //【変更】メソッド内での初期化に変更
x = y; //2引数の値を交換
y = temp;
}
public void swap(ref double x, ref double y) { //【以下追加】2引数を参照渡しで扱うメソッドのオーバロード
double temp = x;
x = y; //2引数の値を交換
y = temp;
}
}
class swap01 {
public static void Main() {
myclass s = new myclass();
int x = 10, y = 20;
s.swap(ref x, ref y); //2引数を参照渡しでメソッドに渡す
Console.WriteLine("x = {0}, y = {1}", x, y); //2引数の値が交換されている
double d1 = 3.14, d2 = 2.2; //【以下追加】
s.swap(ref d1, ref d2); //2引数を参照渡しでオーバロードメソッドに渡す
Console.WriteLine("d1 = {0}, d2 = {1}", d1, d2); //2引数の値が交換されている
}
}
ミニ演習 mini193.cs
・アレンジ演習:p.191 changearray01.csを元に、引数のaとbの和をref waで、差をref saで、積をref seiで返すメソッドにしてみよう
void modify(int a, int b, ref int wa, ref int sa, ref int seki){…}
作成例
//ミニ演習 mini193.cs
using System;
class change {
public void modify(int a, int b, ref int wa, ref int sa, ref int seki) { //aとbの和差積を返すメソッド
wa = a + b; //和を格納する
sa = (a > b) ? a - b : b - a; //差を格納する
seki = a * b; //積を格納する
}
}
class changearray01 {
public static void Main(){
change c = new change();
int x = 0, y = 0, z = 0;
c.modify(10, 20, ref x, ref y, ref z); //参照渡しで和差積を書きこんでもらう
Console.WriteLine("{0}と{1}の和は{2},差は{3},積は{4}", 10, 20, x, y, z);
}
}
p.194 out
・実引数の初期化または値の代入がされていなくても参照渡しが可能なのがoutキーワード ・C#のバージョン7以降で利用可能 ・refの代わりに利用できるが「初期化または値の代入がされていない変数」とみなされるので、 outキーワード付きの仮引数は代入の右辺や表示には使えない ⇒ よって、swap03.csのrefをoutに書き換えるとエラーになる ・つまり、refを置き換えるものではなく、処理内容によって使い分けること
アレンジ演習:p.194 outkeyword01.cs
・外周(縦の倍+横の倍)もoutで返すようにしよう
作成例
//アレンジ演習:p.194 outkeyword01.cs
using System;
class MyClass {
public void Square(double x, double y, out double s, out double o) { //outでの参照渡しがあるメソッド
s = x * y; //仮引数sは左辺ならOK
o = x * 2 + y * 2; //仮引数oは左辺ならOK
}
}
class outkeyword01 {
public static void Main() {
double a = 125.3, b = 16.25, c, d; //c,dには値を代入していません
MyClass mc = new MyClass();
mc.Square(a, b, out c, out d); //縦横を渡して面積と外周を得るメソッド
Console.WriteLine("縦{0}m, 横{1}mの長方形の面積は{2}平方cm,外周は{3}cm", a, b, c, d);
}
}
提出:ミニ演習 mini193.cs・改
・アレンジ演習:p.191 changearray01.csを元に、引数のaとbの和をout waで、差をout saで、積をout seiで返すメソッドにしてみよう
void modify(int a, int b, out int wa, out int sa, out int seki){…}