同じプログラムコードを複数の場所で何度も利用したい場合があります。 例えば、今まで説明してきた中で、たびたび「入力を促すメッセージを出力して、整数を入力してもらう」という場面が出てきました。 そのために何度も同じようなソースコードを書いてきました。
「反復処理」 のところでも説明しましたが、 同じコードを複数の箇所に書くのはプログラムを管理していく上で好ましくありません。 そこで、こういう頻繁に使われる機能をまとめて、何度も呼び出せるようにしたのが関数(function)です。
関数という名前は、数学用語の関数からきています。 数学の関数は、ある値を入力すると、一定のルールに従った出力が得られます。 また、入力することの出来る値の範囲や、出力として得られる値の範囲はしっかりと決められています。
C#の関数も同じように、入力出来る値と、出力される値の型をあらかじめ決めておかなければ行けません。 例えば、実数(浮動小数点数)を入力して、その値のsinを求めるような関数を作りたい場合、 以下のようにして関数を作ることが出来ます。
// sin x を求める関数。 // テイラー展開を利用。 // かなり適当に作ってるので、この方法ではそんなに精度はよくない。 double Sin(double x) { double xx = -x * x; double fact = 1; double sin = x; for(int i=1; i<100;) { fact *= i; ++i; fact *= i; ++i; x *= xx; sin += x / fact; } return sin; }
まず、コメントの部分を除いて1番最初の、「
double Sin(double x)
」の部分を細かく見ていくと、
先頭の「
double
」が関数の出力(これを戻り値という)の型、
次の「Sin」が関数の名前、
()の中にある「
double x
」が入力の型と入力された値を保持するための変数(これを引数という)です。
その後に続く{}の中身が関数の内部で行う処理です。
そして、出力にしたい値はreturnというキーワードの後ろに書きます。
作成した関数を呼び出すには、
変数 = 関数名(入力)
というように書きます。 以下に関数を呼び出す例を挙げます。
using System; class SinSample { static void Main() { for(int i=0; i<10; ++i) { double x = 0.01 * i; double y = Sin(x); // 関数呼び出し Console.Write("sin({0:f2}) = {1:f6}\n", x, y); } } /// <summary> /// sin(x) の値を求める。 /// 実装は割りと適当。 /// </summary> static double Sin(double x) // 関数定義 { double xx = -x * x; double fact = 1; double sin = x; for(int i=1; i<100;) { fact *= i; ++i; fact *= i; ++i; x *= xx; sin += x / fact; } return sin; } }
sin(0.00) = 0.000000 sin(0.01) = 0.010000 sin(0.02) = 0.019999 sin(0.03) = 0.029996 sin(0.04) = 0.039989 sin(0.05) = 0.049979 sin(0.06) = 0.059964 sin(0.07) = 0.069943 sin(0.08) = 0.079915 sin(0.09) = 0.089879
Sin 関数の定義の部分の前についている
static
というキーワードについては、
「静的変数・静的メソッド」
で説明します。
もう一つ違う例を挙げて見ましょう。 今まで、実数の入力は以下のようにして行っていました。
Console.Write("ユーザーに入力を促すメッセージ"); x = double.Parse(Console.ReadLine());
実数を入力する必要のある場面ごとにこのようなコードを書くのは面倒ですし、 これを関数化して見ましょう。 まず、単純に関数化した結果を以下に示します。
double GetDouble(string message) { Console.Write(message); double x = double.Parse(Console.ReadLine()); return x; }
今までずっと無視してきていたのですが、実はこのままでは、実数に出来ない文字列を入力してしまうとエラーが発生して、以下のようなエラーメッセージを表示してプログラムが途中で止まってしまいます。
未処理の例外 : System.FormatException: 入力文字列の形式が正しくありません。 at System.Number.ParseDouble(String s, NumberStyles style, NumberFormatInfo info) at System.Double.Parse(String s, NumberStyles style, IFormatProvider provider) at StatementSample2.Main()
本当は例外処理(後述)というものを行ってこのようなエラーが出たときの対処を行わないといけません。
そこで、この例外処理を行うように変更を加えてみましょう。
(例外処理については
「例外処理」
参照。
tryとcatchは例外処理を行うための構文です。)
double GetDouble(string message) { double x; while(true) { try { // 入力を促すメッセージを表示して、値を入力してもらう Console.Write(message); x = double.Parse(Console.ReadLine()); } catch(Exception) { // 不正な入力が行われた場合の処理 Console.Write( "error : 正しい値が入力されませんでした\n入力しなおしてください\n"); continue; } break; } return x; }
この修正した関数を用いて 「変数と式」 の最後で示したサンプルを書き換えてみましょう。
using System; class StatementSample2 { static void Main() { double x, y, z; // 変数を宣言。 // x, y にユーザーの入力した値を代入。 x = GetDouble("input x : "); y = GetDouble("input y : "); // 入力された値を元に計算 z = x * x + y * y; // z に x と y の二乗和を代入 x /= z; // x = x / z; と同じ。 y /= -z; // y = -y / z; と同じ。 // 計算結果を出力 Console.Write("({0}, {1})", x, y); } /// <summary> /// 入力を促すメッセージを表示して、実数を入力してもらう。 /// 正しく実数として解釈できる文字が入力されるまで繰り返す。 /// <param name="message"> 入力を促すメッセージ </param> /// <return> 入力された値 </return> /// </summary> static double GetDouble(string message) { double x; while(true) { try { // 入力を促すメッセージを表示して、値を入力してもらう Console.Write(message); x = double.Parse(Console.ReadLine()); } catch(Exception) { // 不正な入力が行われた場合の処理 Console.Write( "error : 正しい値が入力されませんでした\n入力しなおしてください\n"); continue; } break; } return x; } }
数学の関数では、例えば「f(x, y、z) = x2+y2+z2」といったように、入力が複数ある場合があります。
C#の関数でもこのように引数が複数ある関数を作れます。
引数を複数使いたい場合、数学の関数と同じように、関数を定義する際に、以下のように複数の引数を , で区切って並べます。
このように、引数を , で区切って並べたものを引数リストといいます。
double Norm(double x, double y, double z) { // ノルムの計算 return x*x + y*y + z*z; }
数学ではあまり考えられませんが、C#では引数のない関数も定義できます。 引数のない関数は、以下のように、引数リストを空にして定義します。
ulong seed = 4275646295673547UL; ulong Random() { // 線形合同法による乱数の生成 unchecked{seed = seed * 1566083941UL + 1;} return seed; }
同様に戻り値のない関数も定義できます。
戻り値のない関数は、以下のように、戻り値の型を
void
(「空の、何もない」という意味)というものにしておきます。
void WriteArray(int[] array) { Console.Write("{"); for(int i=0; i<array.Length-1; ++i) { Console.Write("{0}, ", array[i]); } Console.Write(array[array.Length-1] + "}\n"); }
戻り値のない物でも「関数」と呼ぶのはC言語やC++言語から受け継いだ習慣です。 その他の言語では、 戻り値のないものは「サブルーチン」とか「プロシージャ」といって関数と区別する場合もあります。
using System; class FunctionSample { static void Main() { int[] array = new int[3]; // 乱数を使って値を生成 for(int i=0; i<array.Length; ++i) { array[i] = (int)(Random() >> 58); // [0,63] の整数乱数生成 } // ノルムを計算 double norm = Norm(array[0], array[1], array[2]); // 値の出力 WriteArray(array); Console.Write("norm = {0}\n", norm); } static ulong seed = 4275646293455673547UL; /// <summary> /// 線形合同法による乱数の生成 /// </summary> static ulong Random() { unchecked{seed = seed * 1566083941UL + 1;} return seed; } /// <summary> /// 入力した3つの値のノルムを計算 /// <summary> static double Norm(double x, double y, double z) { return x*x + y*y + z*z; } /// <summary> /// 配列を , で各要素を区切って、{}で括った形式で出力 /// <summary> static void WriteArray(int[] array) { Console.Write("{"); for(int i=0; i<array.Length-1; ++i) { Console.Write("{0}, ", array[i]); } Console.Write(array[array.Length-1] + "}\n"); } }
{40, 31, 39}
norm = 4082
関数を作る際、関数の名前が同じで引数リストだけが異なる関数を複数作ることが出来ます。 例えば、以下のように同じ名前の関数を作成することが出来ます。
void WriteTypeAndValue(string s) { Console.Write("文字列 : {0}\n", s); } void WriteTypeAndValue(int n) { Console.Write("整数 : {0}\n", n); } void WriteTypeAndValue(double x) { Console.Write("実数 : {0}\n", x); }
このように、引数リストだけが異なる関数を作ることを関数のオーバーロード(overload : 過負荷、上積み)といいます。
using System; class OverloadSample { static void Main() { WriteTypeAndValue("サンプル"); // WriteTypeAndValue(string) が呼ばれる WriteTypeAndValue(13); // WriteTypeAndValue(int) が呼ばれる WriteTypeAndValue(3.14159265); // WriteTypeAndValue(double) が呼ばれる } /// <summary> /// 型名と値を出力する(string 版)。 /// </summary> static void WriteTypeAndValue(string s) { Console.Write("文字列 : {0}\n", s); } /// <summary> /// 型名と値を出力する(int 版)。 /// </summary> static void WriteTypeAndValue(int n) { Console.Write("整数 : {0}\n", n); } /// <summary> /// 型名と値を出力する(double 版)。 /// </summary> static void WriteTypeAndValue(double x) { Console.Write("実数 : {0}\n", x); } }
文字列 : サンプル 整数 : 13 実数 : 3.14159265
int 型の配列に格納されている値の最大値、最小値および平均値を求める関数をそれぞれ作成せよ。
/// <summary> /// 配列中の値の最大値を求める。 /// </summary> /// <param name="a">対象の配列</param> /// <returns>最大値</returns> static int Max(int[] a) /// <summary> /// 配列中の値の最小値を求める。 /// </summary> /// <param name="a">対象の配列</param> /// <returns>最小値</returns> static int Min(int[] a) /// <summary> /// 配列中の値の平均値を求める。 /// </summary> /// <param name="a">対象の配列</param> /// <returns>平均値</returns> static double Average(int[] a)
double 型の値 x の整数冪を求める関数 Power を作成せよ。
/// <summary> /// x の整数冪を求める。 /// </summary> /// <param name="x">仮数 x</param> /// <param name="n">指数 n</param> /// <returns>x の n 乗</returns> static double Power( double x, int n)
配列に格納されている値の最大値を求める関数を、
int[] に対するものと
double[] に対するものの2種類作成せよ。