これまでのプログラムでは,print,getsのような関数的メソッド, 配列のeach,push,あるいは文字列のto_iのようなオブジェクトのメソッドなど, さまざまなメソッドを利用してきました. メソッドを呼び出すと,メソッドで定められている処理が行われます. たとえば,printメソッドは画面にオブジェクトの内容を表示するものであり, 配列のeachメソッドは,配列の各要素に関してブロックの内容を実行するものです.
メソッドとは,データ(オブジェクト)に対する一連の処理をまとめたものです. メソッドを起動することで,メソッドの処理内容を実行して,その処理の結果を利用することができます.
メソッドはRubyに用意されているものを使うだけでなく,自分で定義することもできます. 新しいメソッドを定義すれば,プログラムに新しい機能を追加できます. ここでは,関数的メソッドを対象として, メソッドを定義して使う意義と,メソッドを定義する方法について説明します.
もくじ
例題
プログラムの仕様
プログラムの流れは,次の通りになるでしょう.
- 出発時刻と到着時刻を入力する.入力が正しくない場合は,入力をやり直す.
- 出発時刻と到着時刻の差から所要時間を計算する. 時刻を0時00分からの分単位の経過時間に変換しておくと計算が簡単になる.
- 得られた所要時間を表示する.
メソッドの利用
これまでに学んだ範囲の知識で, 上に書いた仕様にそのまま従ってプログラムを作成することはできます. その場合は,次の参考プログラムのようになるでしょう.
このプログラムでは,時刻を入力する部分が2回, 入力された時刻を0:00からの分単位の経過時間に変換する部分が2回, それぞれ繰り返されています.同じことを何度も書くのは面倒ですし,プログラムが長くなります. また間違いがあったら,すべて修正する必要があります. 同じ処理を行っている部分をうまくまとめられないでしょうか. このようなときにメソッドを定義して利用します.
すでに述べた通り, メソッドとは,データ(オブジェクト)に対する一連の処理をまとめたものです. メソッドを起動することで,メソッドの処理内容を実行して,その処理の結果を利用することができます. メソッドを定義して使うことには,次のようなメリットがあります.
- 同じような処理を(別々の時点で)何回も行う必要があるとき, メソッドにしておけば同じ処理を何度も書かなくてもすむ. また,そうしておけば,間違いがあったときにも訂正しやすい.
- まとまった処理をメソッドにしておくことで, プログラム全体の見通しがよくなる.
- メソッドを使う際には,その利用法だけが分かっていればよい. メソッドがどう定義されているか気にする必要がない.
例題のプログラムの場合は,まず,時刻の入力処理と入力された時刻の変換処理 をメソッドにすることが考えられます. そうすることで,同じ処理を2回書く代わりに,定義を(一度)書いておいて, それを2回起動するように書き直すことができます. こうすれば,間違いがあっても,修正するのはメソッドの定義部分だけですみます.
プログラム
例題のプログラムをRubyで記述すると, 以下のようになります. なお,各行の左端の数字は説明のために付けた行の番号で, プログラムには,この行番号は含まれません.
[行番号つきプログラムを別のウィンドウで開く] [行番号なしのプログラム]
今回の例題のプログラムは,これまでのプログラムと違って, 前半でメソッドを定義して,後半で定義したメソッドを利用して実際に処理を行 うという構成になっています.
このプログラム(duration.rb)は次のように実行します.
出発時刻と到着時刻から所要時間を計算します
時刻は3桁あるいは4桁の整数で指定して下さい
出発時刻(000--2359)を入力して下さい: 948
到着時刻(000--2359)を入力して下さい: 1321
所要時間は3時間33分です.
メソッドの起動
メソッドの定義について説明する前に,プログラムの後半部分について 先に説明します. まず最初に52行〜55行で出発時刻と到着時刻の入力処理を行っています.
51 # 出発時刻と到着時刻の取得
52 print "出発時刻と到着時刻から所要時間を計算します\n"
53 print "時刻は3桁あるいは4桁の整数で指定して下さい\n"
54 dep = get_time("出発")
55 arr = get_time("到着")
54,55行では,それぞれ「"出発"」,「"到着"」を引数として, メソッドget_timeを起動して,その結果(返り値)を,変数dep,arrに代入しています. ここでget_timeは,23行〜38行で定義されているメソッドです.
以前に「変数とデータ入力」の資料の 「オブジェクトとメソッド再び」 でも説明した通り, メソッドの結果の値を返り値といいます. get_timeでは,キーボードからデータを読みこんで,それを 3桁あるいは4桁の整数として返しています.
57 # 所要時間(分)
58 duration_min = duration_minutes(dep,arr)
59 # 所要時間が0未満の場合はエラーとする.
60 if duration_min < 0
61 print "到着時刻が出発時刻より前になっています\n"
62 exit
63 end
64
65 # 所要時間の表示
66 display_duration_hours_and_minutes(duration_min)
同様に58行めでduration_minutesというメソッドによって, 所要時間を計算しています. duration_minutesは,出発時刻と到着時刻の二つの引数をとります. 返り値は,出発時刻と到着時刻の差を分単位で表した値です. duration_minutesは6〜18行で定義されています.
最後に66行めで display_duration_hours_and_minutesというメソッドで, 所要時間の表示を行っています. このメソッドは,画面に結果を表示するもので,特に返り値を利用しないので, 他のメソッド呼び出しの場合と異なって,返り値の代入を行っていません.
メソッドの定義
例題のプログラムの前半で, 後半部分の処理で利用しているメソッドが定義されています. メソッド定義の形式は次のようになっています.
def メソッド名(仮引数の記述)
メソッドの本体(処理内容,返り値は「return 式」と書く)
end
メソッド定義は,def〜endの中に記述します. 最初のdefの行には,メソッドの名前と,それにつづいてメソッドがとる引数を,カッコでくくって記述します. メソッド定義に使われる引数を仮引数といいます. 2行め以降にメソッドの処理内容を記述します.
たとえば,例題のプログラムの中のduration_minutesは次のようになっています.
4 # 出発時刻dと到着時刻a(いずれも3桁か4桁の整数で与えられる)の差を
5 # 分単位で計算する.
6 def duration_minutes(d,a)
7 min_d = time_in_minutes(d)
8 min_a = time_in_minutes(a)
9 return min_a-min_d
10 end
さて,これまで,メソッドを起動するときには, (必要に応じて)引数を渡すという説明をしてきました. このメソッドを起動するときに与える引数のことを正確には 実引数といいます. 上に書いてあるメソッドの仮引数とは, メソッドに渡された実引数のそれぞれに対応づけてメソッド内部で使う変数です. 仮引数は実引数と同様に必要な数だけ,「,」で区切って並べます.

58行めで,実引数dep,arr(に対応づけられている整数)を渡してduration_minutesを起動しています. すると,6行めの仮引数に実引数のそれぞれが対応づけられます. つまり,dがdepの値,aがarrの値と対応づけられるわけです.
d = dep
a = arr
この対応のもとで,メソッドの本体が順番に処理されます.
メソッドで「return 式」と記述すると, 「式」の値がメソッドの返り値になります. メソッド呼び出しが,代入式の右辺になっていれば, この返り値が,代入式の左辺の変数に対応づけられます.
なお,複数の式を返したい場合には,returnの後に, それらを「,」で区切って並べます. またそのようなメソッドから返される複数の値を代入で受け取るには, 代入の左辺に,返り値の数だけ,変数を「,」で区切って並べます. このような仕組みを多重代入といいます.
# 「y/x」と「y%x」を返す
def div_mod(y,x)
return y/x,y%x
end
# qに17/3(17を3で割った商),rに17%3(17を3で割った余り)が代入される
q,r = div_mod(17,3)
p q # ==> 5
p r # ==> 2
多重代入の詳細については, リファレンスマニュアルの代入の項を参照して下さい.
ローカル変数 --- メソッドの中の変数
メソッドの中の変数は, メソッドの内部のみで有効で,外部では利用できません.
def foo(x)
y = x*x #
z = y+2*x+1 # x,y,zはメソッドfooの内部のみで有効
return z #
end
a=foo(2)
a = a + x # ==> エラー (xは定義されていない)
変数の有効範囲をスコープといいます. メソッドの中の変数のスコープは,メソッドの中で, その変数が代入式の左辺として出現した後になります (その代入式が変数の定義となります). このようにスコープが制限されている変数をローカル変数といいます. なお,メソッドの外側で定義される変数もローカル変数です. すなわち,そのような変数もいつでも有効なわけではありません.
参考: 引数のデフォルト値の指定
Rubyでは,メソッド定義において,引数にデフォルト値(default value)を指定することができます. メソッドの仮引数のデフォルト値とは, メソッド呼び出しで,実引数が与えられなかったときに, その仮引数に対応づけられる値のことです. 引数のデフォルト値を指定するには,メソッド定義の先頭行で 「仮引数=デフォルト値」 のように記述します.
# aのn乗を返す.ただし引数が1個の場合には2乗を返す
def pow_n_or_squ(x,n=2) # 第2引数nが与えられない場合は,n=2とする.
return a**n
end
p pow_n_or_squ(2,3) # ==> 8
p pow_n_or_squ(2.5,-3) # ==> 0.064
p pow_n_or_squ(5) # ==> 25
p pow_n_or_squ(Math::PI) # ==> 9.869604401 (π2)
参考: returnの省略
メソッドにreturnがない場合は,メソッドの最後まで処理を行って, 最後に評価した式の値が返り値になります. したがって,returnを明示的に書かなくてもよい場合もあります. たとえば,duration_minutesの場合は,次のようにも書けます.
5 # 分単位で計算する.
6 def duration_minutes(d,a)
7 min_d = time_in_minutes(d)
8 min_a = time_in_minutes(a)
9 min_a-min_d #この式が返り値となる.
10 end
ただし,条件分岐があるときなど,returnを省略できないときもあります. メソッドの返り値を代入などで利用する場合は, returnを書くようにした方がよいでしょう.
メソッドの定義と起動の順序
変数を定義する前に,その変数の値を参照できないように, メソッドを起動するときには,そのメソッドが定義済でなければなりません.
a = foo(2) # ==> エラー (fooは定義されていない)
def foo(x)
y = x*x
z = y+2*x+1
return z
end
例題のプログラムでは,メソッドduration_minutesの中で, その後で定義されているメソッドtime_in_minutesを起動しています. メソッドの定義の際には,そこまでで定義されていないメソッドを 記述しても問題にはなりません. メソッドの定義では,実際にメソッドの内部の処理が実行されるわけではないからです. メソッドduration_minutesが起動されるのは,58行めで, この時点では,duration_minutes,time_in_minutesは, 定義済になっていますので, duration_minutesからtime_in_minutesを問題なく起動することができます. ここで,仮に,11行めでduration_minutesを起動したとすると, 今度はエラーとなります. これは,その時点では,time_in_minutesが定義されていないためです.
5 # 分単位で計算する.
6 def duration_minutes(d,a)
7 min_d = time_in_minutes(d)
8 min_a = time_in_minutes(a)
9 return min_a-min_d
10 end
11
12 # 3桁あるいは4桁の整数で与えられる時刻tを
13 # 0:00からの経過時間(分)に直す.
14 def time_in_minutes(t)
15 h = t/100
16 m = t%100
17 return h*60+m
18 end
:
:
58 duration_min = duration_minutes(dep,arr)
参考: if修飾子
例題のプログラムの45行めでif修飾子による条件分岐を行っています.
41 def display_duration_hours_and_minutes(m)
42 hour = m/60
43 min = m-hour*60
44 print "所要時間は"
45 print hour,"時間" if hour > 0 # if修飾子を利用した条件分岐
46 print min,"分です.\n"
47 end
if修飾子は,次のような形式になっています.
式 if 条件式
これは,次のif式と同様に利用することができます.
if 条件式
式
end