p.181「メソッドの再帰呼び出し」から
p.181 メソッドの再帰呼び出し
・C#などではメソッドの内部で自分自身を呼び出せる。これを再帰(リカージョン)といい、用いることでアルゴリズムをシンプルに表現できる 場合がある ・なお、再帰のみを記述すると無限ループまたはメモリの使いつくしによる異常終了を起こすので、再帰を終わらせる条件が必要 ・応用例:階乗(後述)、フィボナッチ数列(後述)、最大公約数、シェルソートなど
p.181 階乗を計算する
・ある整数nから1までの全整数の積をnの階乗といいn!と表す。 例: 4! = 4×3×2×1 = 24、5! = 5×4×3×2×1 = 120 ・よって、n! = n × (n - 1)! と、再帰で表すとシンプルで繰り返し構造が不要になる ・階乗のルールとして、0! = 1! = 1 があるので、これを再帰の終了条件に用いると良い
p.182 fact01.cs
//p.182 fact01.cs
using System;
class Fact { //インスタンスメソッドッドのみを含むクラス
public long CalcFact(int n) { //再帰を含むインスタンスメソッドで階乗を返す
long fact; //内部用のローカル変数
if (n == 0) { //0!は固定なので再帰の終了条件に用いる
fact = 1; //0! = 1
} else {
fact = n * CalcFact(n - 1); //n! = n×(n-1)!
}
return fact; //n!を返す
}
}
class fact01 {
public static void Main() {
Fact f = new Fact();
for (int i = 0; i <= 20; i++) {
Console.WriteLine("{0}! = {1}", i, f.CalcFact(i));
}
}
}
アレンジ演習:p.182 fact01.cs
・0! = 1のみならず、1! = 1であることを加えて実行効率を上げよう ・また、条件演算子を用いて記述をよりシンプルにしよう
作成例
//アレンジ演習:p.182 fact01.cs
using System;
class Fact { //インスタンスメソッドッドのみを含むクラス
public long CalcFact(int n) { //再帰を含むインスタンスメソッドで階乗を返す
return (n <= 1) ? 1 : n * CalcFact(n - 1); //【変更】
}
}
class fact01 {
public static void Main() {
Fact f = new Fact();
for (int i = 0; i <= 20; i++) {
Console.WriteLine("{0,2}! = {1}", i, f.CalcFact(i));
}
}
}
p.185 fibonacci.cs
//p.185 fibonacci.cs
using System;
class fibo {
public long CalcFibo(int n) {
long fb;
if (n == 1 || n == 2) { //再帰の終了条件
fb = 1;
} else {
fb = CalcFibo(n - 1) + CalcFibo(n - 2); //再帰する
}
return fb;
}
}
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.185 fibonacci.cs
・条件演算子を用いて記述をよりシンプルにしよう
作成例
//アレンジ演習:p.185 fibonacci.cs
using System;
using System.Runtime.Remoting.Messaging;
class fibo {
public long CalcFibo(int n) {
return (n <= 2) ? 1L : (long)CalcFibo(n - 1) + CalcFibo(n - 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(値渡しと参照渡し)
・C#のメソッドの基本は値渡しであり、実引数の値が仮引数にコピーされるので、関数内部で仮引数の値を変更しても、呼び出し側の実引数には 反映しない。 ⇒ p.189 swap01.cs ・C#の参照型データにおいても値渡しになるので、参照型であるstring型の変数であっても参照渡しにはならない ⇒ p.190 swap02.cs ・なお、配列などのデータ構造を実引数にした場合、構造名が参照を示すので、参照がコピーされ、関数内部で仮引数のデータ構造をの中の値を 変更すると、呼び出し側の実引数のデータ構造に反映する ※ returnする必要はない
p.191 charngearray01.cs
//p.191 charngearray01.cs
using System;
class change {
public void modify(int[] array) { //引数が配列なの参照を受け取るメソッド
int n = array.Length; //参照経由で実引数のプロパティ(後述)を実行して要素数が得られる
for (int i = 0; i < n; i++) { //要素数を用いて全要素について繰り返す
array[i] *= 2; //要素の値を書き換えるので実引数になっている配列の要素値も書き変わる
}
}
}
class changearray01 {
public static void Main() {
change c = new change(); //
int[] myarray = new int[3]{1, 2, 3};
Console.WriteLine("----modifyメソッド実行前----");
int i = 0;
foreach (int x in myarray) { //配列の全要素について繰り返す
Console.WriteLine("myarray[{0}] = {1}", i, x);
i++; //添字を扱えないのでカウンタの代わりに
}
c.modify(myarray);
Console.WriteLine("----modifyメソッド実行後----");
i = 0;
foreach (int x in myarray) { //配列の全要素について繰り返す
Console.WriteLine("myarray[{0}] = {1}", i, x);
i++; //添字を扱えないのでカウンタの代わりに
}
}
}
アレンジ演習:p.191 charngearray01.cs
・配列の初期化をシンプルにし、foreachで添え字を扱うのは非効率なのでforにしよう
作成例
//アレンジ演習:p.191 charngearray01.cs
using System;
class change {
public void modify(int[] array) { //引数が配列なの参照を受け取るメソッド
int n = array.Length; //参照経由で実引数のプロパティ(後述)を実行して要素数が得られる
for (int i = 0; i < n; i++) { //要素数を用いて全要素について繰り返す
array[i] *= 2; //要素の値を書き換えるので実引数になっている配列の要素値も書き変わる
}
}
}
class changearray01 {
public static void Main() {
change c = new change();
int[] myarray = {1, 2, 3}; //【変更】
Console.WriteLine("----modifyメソッド実行前----");
for (int i = 0; i < myarray.Length; i++) { //【変更】配列の全要素について繰り返す
Console.WriteLine("myarray[{0}] = {1}", i, myarray[i]); //【変更】
}
c.modify(myarray);
Console.WriteLine("----modifyメソッド実行後----");
for (int i = 0; i < myarray.Length; i++) { //【変更】配列の全要素について繰り返す
Console.WriteLine("myarray[{0}] = {1}", i, myarray[i]); //【変更】
}
}
}
p.18(ref)
・実引数を参照渡しにして、メソッド内部で仮引数の値を変更すると、呼び出し側の実引数に反映するのがref指定。
・実引数が単独の変数であり、実引数と仮引数の両方にref指定がされていることが必要だが、変数名は異なってOK
・関数側の書式例: アクセス修飾子 戻り値型 メソッド名(ref 型 仮引数名, …){…}
※ 戻り値型はvoidでも良い(するとreturn不要)。仮引数が複数ある場合、ref指定の有無が混在できる
・呼出側の書式例: メソッド名(ref 実引数である変数, …);
・ただし、実引数は初期化あるいは値の代入がされていること
p.193 swap03.cs
//p.193 swap03.cs
using System;
class myclass {
private int temp; //内部の作業用の変数
public void swap(ref int x, ref int y) { //ref指定の仮引数なので参照渡しになる
temp = x;
x = y; //書き換えたxの値は呼び出し元の変数に反映する
y = temp; //書き換えたyの値は呼び出し元の変数に反映する
}
}
class swap01 {
public static void Main() {
myclass s = new myclass();
int x = 10, y = 20;
s.swap(ref x, ref y); //ref指定の実引数なので参照渡しになる
Console.WriteLine("x = {0}, y = {1}", x, y); //呼び出しによりxとyの値が交換されている
}
}
アレンジ演習:p.193 swap03.cs
・実引数名と仮引数名を異なる名前にして、参照渡しの効果を見やすくしよう ・myclassクラスのインスタンス変数tempは(インスタンス変数である必要がないので)ローカルのvar型にしよう
作成例
//アレンジ演習:p.193 swap03.cs
using System;
class myclass {
public void swap(ref int x, ref int y) { //ref指定の仮引数なので参照渡しになる
var temp = x; //【変更】一時的変数にxの値を退避する
x = y; //書き換えたxの値は呼び出し元の変数に反映する
y = temp; //書き換えたyの値は呼び出し元の変数に反映する
}
}
class swap01 {
public static void Main() {
myclass s = new myclass();
int a = 10, b = 20;
s.swap(ref a, ref b); //ref指定の実引数なので参照渡し(a⇔x、b⇔y)になる
Console.WriteLine("a = {0}, b = {1}", a, b); //呼び出しによりxとyの値が交換されている
}
}
p.194(outキーワードによる参照渡し)」
・refキーワードの強化版で「実引数は初期化あるいは値の代入がされていること」が不要になる ・outキーワードを指定したメソッドの呼び出し以降であれば、実引数の値を利用できる ※ ただし、利用可否と動作はC#のバージョンに依存するので、バージョン7より前では使えないので注意 ※ また、out指定にした仮引数は初期化あるいは値の代入がされていないという前提で扱われるので、代入の右辺などに用いるとエラーになる。 この場合はrefにすること
アレンジ演習 p.193 swap03.cs
・refをoutに変えるとエラーになることを確認しよう ・「temp = x;」「x = y;」は、xおよびyが初期化あるいは値の代入がされていない可能性があるのでエラーになる
作成例(エラーになる)
//アレンジ演習:p.193 swap03.cs
using System;
class myclass {
public void swap(out int x, out int y) { //【変更】out指定の仮引数なので参照渡しになる
var temp = x; //【エラー】xが初期化されていない可能性があるので代入不可
x = y; //【エラー】yが初期化されていない可能性があるので代入不可
y = temp; //書き換えたyの値は呼び出し元の変数に反映する
}
}
class swap01 {
public static void Main() {
myclass s = new myclass();
int a = 10, b = 20;
s.swap(out a, out b); //out指定の実引数なので参照渡し(a⇔x、b⇔y)になる
Console.WriteLine("a = {0}, b = {1}", a, b); //呼び出しによりxとyの値が交換されている
}
}
p.194 outkeyword01.cs
//p.194 outkeyword01.cs
using System;
class MyClass {
public void Square(double x, double y, out double s) { //仮引数sは参照渡し
s = x * y; //代入の左辺なので初期化されていなくても良い
}
}
class outkeyword01 {
public static void Main() {
double a = 125.3, b = 16.25, c;
MyClass mc = new MyClass();
//cには値を代入していません
mc.Square(a, b, out c); //実引数cは結果の受け取り用なので初期化不要
Console.WriteLine("縦{0}m, 横{1}mの長方形の面積は{2}平方メートル", a, b, c);
}
}
アレンジ演習:p.194 outkeyword01.cs
・この場合、outキーワードも、それをつけた引数も不要で、returnでできることを確認しよう
作成例
//アレンジ演習:p.194 outkeyword01.cs
using System;
class MyClass {
public double Square(double x, double y) { //【変更】
return x * y; //【変更】
}
class outkeyword01 {
public static void Main() {
double a = 125.3, b = 16.25; //【変更】
MyClass mc = new MyClass();
Console.WriteLine("縦{0}m, 横{1}mの長方形の面積は{2}平方メートル", a, b, mc.Square(a, b)); //【変更】
}
}
アレンジ演習:p.194 outkeyword01.cs
・outキーワードが便利な例として、2実数の和と積を返すメソッド public void AddMul(double x, double y , out double sum, out double mul) にしてみよう
作成例
//アレンジ演習:p.194 outkeyword01.cs
using System;
class MyClass {
public void AddMul(double x, double y , out double sum, out double mul) { //【変更】
sum = x + y; mul = x * y; //【変更】
}
}
class outkeyword01 {
public static void Main() {
double a = 125.3, b = 16.25, c, d; //【変更】
MyClass mc = new MyClass();
mc.AddMul(a, b, out c, out d); //【変更】
Console.WriteLine("和は{0}, 積は{1}", c, d); //【変更】
}
}