++C++; // 未確認飛行 C 避けて通れない「非同期処理」を克服しよう

Top総合 目次C# によるプログラミング入門

関数

このエントリーをはてなブックマークに追加

目次

キーワード

概要

同じプログラムコードを複数の場所で何度も利用したい場合があります。 例えば、今まで説明してきた中で、たびたび「入力を促すメッセージを出力して、整数を入力してもらう」という場面が出てきました。 そのために何度も同じようなソースコードを書いてきました。

反復処理」 のところでも説明しましたが、 同じコードを複数の箇所に書くのはプログラムを管理していく上で好ましくありません。 そこで、こういう頻繁に使われる機能をまとめて、何度も呼び出せるようにしたのが関数(function)です。

(追記したい) 何度も出てくる処理でなくても、 処理に名前が付く単位で関数化すべき。 明確な名前を付ける(名前が付く単位で区切る)のがよいコードを書くコツ。

ポイント
  • 何度も出てくる処理は関数化する。
  • 数学の「関数」から取った名前。プログラミング用語的には、他に、サブルーチン、プロシージャ(手続き)、メソッド等といった呼び方がある。
  • C# の機能の呼び名としては、このページで説明しているものは正確には「メソッド」という。
    • とりあえず現時点では、メソッド = 関数 と思っておいて OK。
    • C# の場合、関数的な挙動をするものがいくつかあって、そのうち、一番「関数らしい関数」がメソッド。
サンプル

https://github.com/ufcpp/UfcppSample/tree/master/StructuredProgramming/Function

補足: 関数メンバー

C# の場合、このページで説明するような「関数」的な動作、 すなわち、何らかの値を受け取って、処理して、結果の値を返すような挙動をするものがいくつかあります。 具体的には、以下のようなものがあります。

これらを合わせて、関数メンバー(function member)と呼びます。 このページで説明しているものは、C# の機能名としては、正確にはメソッドといいます。

また、数学の関数を引き合いに出して説明していますが、 数学の関数のイメージと一番合うのは、「静的メソッド」というものです (メソッドにもインスタンス メソッドと静的メソッドの2種類があります)。 これについては、 「静的メンバー」 で説明します。

このページでの説明(つまり、メソッドに対する説明)の多くは、メソッド以外の関数メンバーにも当てはまります。 引数の書き方、関数本体の書き方、戻り値の返し方などは、すべての関数メンバーで共通です。 (引数や戻り値が指定できない関数メンバーもありますが、指定できる場合には、書き方が同じです。)

関数定義

C# では、以下のようにして関数(C# 用語としては、正確にはメソッド)を定義します。

戻り値の型 関数名(引数一覧)
{
    関数本体(具体的な処理)
}

Ver. 6.0 C# 6.0 では、関数本体の部分が1つの式だけからなる場合、以下のような書き方をすることができるようになりました。 これを、expression-bodied (本体が式の)関数と呼びます。

戻り値の型 関数名(引数一覧) => 関数本体の式

関数という名前は、数学用語の関数からきています。 数学の関数は、ある値を入力すると、一定のルールに従った出力が得られます。 また、入力することの出来る値の範囲や、出力として得られる値の範囲はしっかりと決められています。

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 」が入力の型と入力された値を保持するための変数(これを引数(paramter)といいます)です。

その後に続く{}の中身が関数の内部で行う処理です。 そして、出力にしたい値(これを戻り値(return value)といいます)は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()

本当は例外処理(後述)というものを行ってこのようなエラーが出たときの対処を行わないといけません。 そこで、この例外処理を行うように変更を加えてみましょう。 (例外処理については 「例外処理」 参照。 trycatchは例外処理を行うための構文です。)

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

補足: 状態

この例の Random は、数学の関数と違って、「状態」(state)を持っています。 一般に、数学の関数は、引数と戻り値の関係だけを説明していて、同じ引数を与えた場合、常に同じ戻り値が返ります。 一方、この例は、関数の外にある seed という変数に値を記録・書き換え(これを「状態を持つ」という)していて、呼ぶたびに状態が変わり、違う戻り値を返します。

ちなみに、この関数の外にある変数のことは、C# 的にはフィールド(field)と呼びます。詳しくは 「データの構造化」 で説明します。

補足: 引数

引数についてはいくつか補足があります。

オプション引数と名前付き引数

Ver. 4.0

C# 4 から、引数に規定値(default value)を与えて、呼び出し時に省略できたり(optional)、名前付き(named)で引数を書けるようになりました。

class OptionalParameterSample
{
    public static void Sample()
    {
        // 引数の省略(optional parameter)
        var s1 = Sum();     // Sum(0, 0, 0); と同じ意味。
        var s2 = Sum(1);    // Sum(1, 0, 0); と同じ意味。
        var s3 = Sum(1, 2); // Sum(1, 2, 0); と同じ意味。

        // 名前付きで引数を与える(named parameter)
        var s4 = Sum(x: 1, y: 2, z: 3); // Sum(1, 2, 3); と同じ意味。
        var s5 = Sum(z: 3);             // Sum(0, 0, 3); と同じ意味。
    }

    static int Sum(int x = 0, int y = 0, int z = 0)
    {
        return x + y + z;
    }
}

詳しくは、 「オプション引数・名前付き引数」 で説明します。

リンク先の余談、 「余談: なんでいまさら?」 で説明していますが、引数の規定値には「後から値を変えにくい」という問題があるので、仕様の際には注意が必要です。 とはいえ、後から値を変えることもそう多くなく、便利に使える機能です。

実引数と仮引数

引数という言葉は、「引数として渡す値」と「引数を受け取るための変数」という2つの意味で使われます。 区別が必要な場合、前者を実引数(actual parameter)、後者を仮引数(formal parameter)と呼びます。

例えば、先ほどの例で言うと、以下のように、Norm 関数に渡す 3, 4, 5 などの数値が実引数、 Norm 関数の定義側にある x, y, z などの変数が仮引数です。

static void Main()
{
    var norm = Norm(3, 4, 5); // 3, 4, 5 が実引数
}

static double Norm(double x, double y, double z) // x, y, z が仮引数
{
    return x * x + y * y + z * z;
}
余談: paramter と argument

少し余談になりますが、引数は、英語だと parameter 以外に、argument (ここでは「独立変数」の意味。数学用語としての argument)という単語を使うこともあります。 (ちなみに、parameter も、数学用語としては「媒介変数」という訳語になります。)

流儀によっては、実引数の意味で argument、仮引数の意味で parameter といように単語で呼び分けることもあるようです。 (どちらがどちらだったかわからなくなるので、あまり推奨はされません。結局、actual argument, formal argument という言い方もします。)

特殊な引数

C# には、いくつか特殊な引数があります。 詳しくは別項で説明しています。

関数のオーバーロード

関数を作る際、関数の名前が同じで引数リストだけが異なる関数を複数作ることが出来ます。 例えば、以下のように同じ名前の関数を作成することが出来ます。

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

expression-bodied な関数

Ver. 6.0 C# 6.0 では、関数本体の部分が1つの式だけからなる場合、以下のような書き方をすることができるようになりました。 これを、expression-bodied (本体が式の)関数(expression-bodied function)と呼びます。

例えば、先ほど例に出した2つの関数、Random と Norm は以下のように書くこともできます。

static ulong Random() => unchecked(seed = seed * 1566083941UL +  1 );

static double Norm(double x, double y, double z) => x * x + y * y + z * z;

演習問題

問題 1

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)

解答

問題 2

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)

解答

問題 3

配列に格納されている値の最大値を求める関数を、 int[] に対するものと double[] に対するものの2種類作成せよ。

解答

[お問い合わせ](q)