2-8 基本的なメソッド定義と呼び出し
2-8-1 基本的なメソッドの構文
メソッドは、一連の手続き(処理の流れ)を記述するブロックで、ほかのプログラミング言語では、プロシージャ、関数などとよばれているものです。今までのサンプルプログラムでも取り上げた「Main メソッド」もメソッドの 1 つです。
C# では、メソッドは必ずクラスの中に記述され、1 つのクラスには複数のメソッドを記述することができます。1 本のプログラムを作成するとき、すべての処理を Main メソッドに記述するのではなく、複数の手続きのブロックとして複数のメソッドに分割することで、可読性や再利用性も高まります(もっとも、一般的に C# などのオブジェクト指向言語におけるソースコードの再利用の単位はクラスですが、大きな手続きを 1 つのメソッドにまとめるよりは、細かくメソッドに分割したほうがメソッドの流用性は高まります)。
以下の例は、1 つのクラスに Main メソッドのほかに、A メソッドと B メソッドが記述してある例です。7 行目から 9 行目では、Main メソッド内から A メソッドの手続きを 2 回、B メソッドの手続きを 1 回実行しています。
[例] 2 つのメソッド定義を追加
1:namespace MySapce1
2:{
3: public class MyApp
4: {
5: public static void Main()
6: {
7: A(); //A の実行
8: B(); //B の実行
9: A(); //A の実行
10: }
11: public static void A()
12: {
13: // 任意の手続きの記述
14: }
15: public static void B()
16: {
17: // 任意の手続きの記述
18: }
19: }
20:}
前記の例にある A メソッドと B メソッドの定義を一般的な形式にすると、以下のようになります。
修飾句 戻り値 メソッド名 (引数リスト)
{
}
このうち、修飾句にあたる部分が「public static」ですが、この意味を理解するためには**第 4 章「クラスの定義と実装」**で扱う「クラス」の登場を待たなければなりません。そのため、第 4 章に入るまでの間、本書では約束事としてメソッドには「public static」をつけることにします。
また、「戻り値」と「引数リスト」については、次項で扱います。
前述のプログラムの 7 行目~ 9 行目で、メソッドを実行している箇所、つまり「メソッドを呼び出している」箇所では、単にメソッド名を書いて、その後ろに丸括弧 () を書きます。これだけでメソッドを呼び出すことができます。
今までのメソッド呼び出しの例では、「System.Console.WriteLine」のように、「WriteLine」メソッドの前に、ネームスペース「System」を特定する記述や、クラス「Console」を特定する記述が、ドットで区切ってされていましたが、このプログラムの例では、Main メソッドと A メソッド、B メソッドは同一ネームスペースの同一クラスに存在するので、ドットでクラスを特定するような記述は必要ありません。
なお、このようなドットを使ってクラスを特定する方法は、**第 4 章「クラスの定義と実装」**で詳しく説明します。
2-8-2 引数と戻り値
C# でも、ほかのプログラミング言語と同様に、メソッドを呼び出すときメソッドにデータを与え(つまり引数を渡し)、そのデータをメソッドに処理させて、処理結果(つまり戻り値)をもらうことができます。
メソッドに与えるデータである引数や、処理結果である戻り値を扱うためには、どんな引数や戻り値を使うかあらかじめ定義しておく必要があります。戻り値は 1 つのメソッドに最大 1 つであり、メソッドの引数は 0 個以上、複数もつことができます。
修飾句 戻り値 メソッド名( 引数型 名前 , 引数型 名前 , ... )
{
}
以下は、int 型引数が 2 つあり、戻り値 int 型のメソッド Add の例です(前項でも触れたように、**第 4 章「クラスの定義と実装」**に入るまでは、メソッドの修飾句は「public static」を常に使うことにします)。
[例] Add メソッド
public static int Add(int x, int y)
{
int w;
w = x + y;
return w;
}
この例では、引数 x、y が宣言されています(厳密には、仮引数といいます)。この引数 x、y は、メソッド Add にとってローカルな変数として機能します。メソッド内であれば、x と y は自由に利用できます。メソッドの呼び出し元へ処理結果のデータを返すには、return キーワードの後ろに戻り値を記述します。この return の後ろに記述された値の型は、メソッドの Add の冒頭で宣言された戻り値の型(ここでは int )に合わせます(もし型が合わない場合、暗黙的なキャストが起こるか、コンパイルエラー、または実行エラーのどれかになります)。もし、このメソッドで戻り値を扱わないのならば、メソッド名の冒頭に戻り値型の代わりに void をつけます。
このメソッドを呼び出すときは、以下のプログラムの 3 行目のように同じ数の引数を渡す必要があります(渡す方の引数を実引数といいます)。戻り値を受けるかどうかは任意です。2 つ以上の引数がある場合、この順番にメソッドに渡されます。そして、メソッド側で計算処理をおこない、その結果「return w;」で返る戻り値は、以下のプログラムの 3 行目の変数 result で受け取ります。
なお、引数を渡す方の引数(実引数)と、受ける側のメソッドの引数(仮引数)の名前を合わせる必要はありませんし、単に引数として定数を渡すこともできます。
[例]引数 2 つの Add メソッドを呼び出し、戻り値を受ける
1: int x=30;
2: int result;
3: result = Add(x, 10); // 引数は 2 つ
なお、C# の引数には、前述の記述以外にも「ret」と「out」いう修飾子をともなうものもあります。これらについては、**第 4 章「クラスの定義と実装」**で説明します。
● for VB6 C# のメソッド呼び出しでは「名前付き引数」を使って、引数を渡すことはできません。つまり、2 つ以上の実引数がメソッドの仮引数にどう渡るかは、引数リストに記述された引数の順番で決まります。 |
以下、メソッド呼び出しの完成品を掲示しておきます。
1:namespace MySpace
2:{
3: public class MyApp
4: {
5: public static void Main()
6: {
7: int x=10;
8: int result;
9: result = Add(x, 100);
10: System.Console.WriteLine("result=" + result);
11: }
12: public static int Add(int a, int b)
13: {
14: int w;
15: w = a + b;
16: return w;
17: }
18: }
19:}
(実行結果)
result=110
2-8-3 値型の引数と参照型の引数
メソッドに引数を用いるとき、値型の引数を使う場合と参照型の引数を用いる場合とでは、そのデータの渡り方に違いがあるので注意が必要です。
以下の例では、値型である int 型引数 a と参照型である配列 dat や、参照型の string 型引数 str という、3 つの引数をもつメソッド accum があります。
[例]値型引数と参照型引数
public void accum(int a, int [] dat, string str)
{
System.Console.WriteLine("a=" + a);
System.Console.WriteLine("dat[0]=" + dat[0]);
System.Console.WriteLine("str=" + str);
}
このメソッドを呼び出しているのが以下の例です。配列の場合は、配列の変数名だけ渡します。
int myVal = 1000;
int [] myArr = new int[]{ 700, 800, 900 };
string myStr = "Hello!";
accum(myVal, myArr, myStr); // 呼び出し
まず、注意すべき点は、上記のメソッドの a、dat、str という仮引数のメモリ領域と、呼び出し側の myVal、myArr、myStr という実引数のメモリ領域は別であるという点です。つまり、この例の引数を渡すときは、上記の実引数である myVal、myArr、myStr 自体の値が、メソッド側の仮引数である a、dat、str に「コピー」されます。
ただし、参照型引数でコピーされるのは、データの実体でない点に注意してください。値型の実引数 myVal では、1000 という値がメソッド側の仮引数 a にコピーされます。しかし、参照型の myArr や myStr では、データ実体がメソッド側の仮引数にコピーされる訳ではなく、「参照情報」がコピーされます。つまり、配列 myArr の各要素はコピーされません。呼び出し元の myArr と、呼び出された先の dat とは、同じ配列実体を参照しています。文字列についても同様で、文字列データの実体はコピーされていませんが、実引数 myStr と仮引数 str は、同じ文字列の実体を参照しています。
これらの関係を図示すると、以下のようになります(図 2-10 )。
●図 2-10 ● 値型と参照型の引数
図 2-10 が示すとおり、参照型の引数では、引数として渡るのはあくまで参照情報であるので、配列がどんなに多くの要素をもっていたとしても、また文字列がどんなに長くとも、データ実体の大きさが引数を渡す際のオーバーヘッドになることはありません。配列の要素が 3 個でも 5,000 個でも、引数として渡すのは、参照情報 1 つだけなので、データ実体の大きさによるオーバーヘッドはありません。
配列では同じ要素を参照できるので、前記において「myArr[0] = 10」という代入と、「dat[0] = 10」という代入は、全く同じ配列の要素に作用します。
ただし、引数が値型にせよ参照型にせよ、実引数と仮引数は異なるメモリ領域のため、上記のメソッド accum 内で、a、dat、str 自体の値を変更しても、呼び出し元の実引数 myVal、myArr、myStr 自体の値は変えられません。
つまり、以下のような書き換えは、呼び出し元の変数には作用しません。
[例]メソッド側で引数の値を変えてみる
1: a = 500; // 呼び出し元の myVal は変化しない
2: dat[0] = 100; // 同じ実体を参照するので myArr[0] も変化する
3: dat = null; //myArr 自体の参照情報は変化しない
4: str = null; //myStr 自体の参照情報は変化しない
ここまでの内容をまとめたサンプルを、以下に提示します。
1:namespace MySpace
2:{
3: public class MyApp
4: {
5: public static void Main()
6: {
7: int myVal = 1000;
8: int [] myArr = new int[]{ 700, 800, 900 };
9: string myStr = "Hello!";
10: accum(myVal, myArr, myStr); // 呼び出し
11: System.Console.WriteLine("myVal=" + myVal);
12: System.Console.WriteLine("myArr[0]=" + myArr[0]);
13: System.Console.WriteLine("myStr=" + myStr);
14: }
15: public static void accum(int a, int [] dat,
16: string str)
17: {
18: System.Console.WriteLine("before ---- accum");
19: System.Console.WriteLine(" a=" + a);
20: System.Console.WriteLine(" dat[0]=" + dat[0]);
21: System.Console.WriteLine(" str=" + str);
22: a = 500; // 仮引数の値を変えてみる
23: dat[0] = 100;
24: dat = null;
25: str = null;
26: System.Console.WriteLine("after ---- accum");
27: System.Console.WriteLine(" a=" + a);
28: }
29: }
30:}
(実行結果)
before ---- accum
a=1000
dat[0]=700
str=Hello!
after ---- accum
a=500
myVal=1000
myArr[0]=100
myStr=Hello!
● for VB6 C# で値型引数を渡すことは、VB の「ByVal x As Long」のように値を渡すことに相当し、C# で参照型引数を渡すことは、「ByVal ob As Object」のように、参照情報を値として渡すことに相当します。VB の「ByRef」にあたる記述として、C# では「ref」や「out」修飾子があります。 これについては別途、第 4 章「クラスの定義と実装」で扱います。 ● for C++ C# で値型引数を渡すことは、C++ で「int x」のように普通に値を渡すことに相当し、C# で参照型引数を渡すことは、「int *x」のように、アドレスを値として渡すことに相当します。C++ の「int &x;vと書く「参照渡し」や、C/C++ の「int **x」に相当する記述として、C# では「ref」や「out」修飾子があります。 これについては別途、第 4 章「クラスの定義と実装」で扱います。 |