今回はRubyが持つ様々な種類の変数を取り上げてちょくちょく調べながら、各変数のスコープをまとめてメモ代わりにしたいと思います。
#Q.スコープとは?
Rubyを独学する前に大学でCとJavaを学んだのですが、「スコープ」と言う表現を初めて聞いたので、そこで少し引っかかってしまいぱぱっと調べました。スコープとは、ズバリ「変数にアクセスできる範囲」です。Javaでprivateやpublicを区別しよーとか言ってたあれと同じでしたね。僕はスコープの説明をよく読まず「変数がアクセスできる範囲」だと勘違いしてこんがらがってしまいました。
ここから下が本題です。いろんな変数がどんなスコープを持っているのか、整理しながら調べていきましょう。
#ローカル変数
まずはローカル変数です。ローカル変数は「局所変数」とも呼ばれ、非常に狭い範囲で使える変数となります。この後出てくるグローバル変数などもそうですが、Rubyは(Rubyに限らないかもしれないけど)変数の名前によってスコープの範囲がなんとなく掴めていいですね。
ローカル変数は、変数名の最初はアルファベットの小文字orアンダーバー(_)にするという決まりがあります。(追記:非ASCII文字も変数名として扱えるようです)
さて、肝心のスコープですが、狭いと言うよりは「限られている」という方が近いのでしょうか。ブロック定義やクラス定義によってスコープが異なってくるのです。一つずつ見ていきます。
##ブロックでのスコープ
こちらのコードを見てください。
local_val1 = 400 #ローカル変数
number = [500, 600, 1000] #まあこれもローカル変数
number.each do |num|
puts local_val1 + num
end
local_val1をブロックの外側で宣言し、用意してある配列の要素を1個ずつ取り出してlocal_val1と足しあわせて表示するプログラムです。Array#eachメソッドは配列を1つ1つ取り出し、要素の数だけ繰り返し処理を行うブロック付きメソッドです。出力はこうなります。
900
1000
1400
きちんと使えてますね。このように、ブロックの外で定義されたローカル変数はブロックの中でも使用可能です。逆はどうでしょう。
local_val = 400
number = [500]
number.each do |num|
sum = local_val + num #ローカル変数sum
puts sum #ブロック内で表示
end
puts sum #ブロックの外で表示
numberの要素を1個にして繰り返しを1回にしたうえで、今度は足した値をブロックの中で宣言したローカル変数sumに代入します。これをブロックの中と外で表示させると…
900
main.rb:8:in `<main>': undefined local variable or method
`sum' for main:Object (NameError)
NameErrorが出てきてしまいました。エラー文を見てみると、「sumと言うローカル変数かメソッドは定義されていませんよ」と書いてあります。つまり、ブロックの中で定義したローカル変数はブロックの外からは使えないのです。定義したことが見えてないので。まとめると
- ブロックの外で宣言→中で使用 OK
- ブロックの中で宣言→外で使用 NG
一方通行なわけですね。
##メソッド定義でのスコープ
def 〜 endで定義されるメソッド定義でのスコープは、ブロックでのスコープとは少し違います。実際にプログラムを見てみましょう。
greeting = "Hello" #ローカル変数
def sayHello
puts greeting #メソッド定義の外で宣言したローカル変数を呼び出し
end
sayHello #メソッド呼び出し
実行するとこのような出力になります。
main.rb:4:in `sayHello': undefined local variable or method
`greeting' for main:Object (NameError)
from main.rb:7:in `<main>'
ブロックでのスコープと同じ文を持つNameErrorが出てきました。つまり、メソッド定義の外で宣言したローカル変数はメソッドの中では使えません。ここがブロックと違う点です。また、プログラムは割愛しますがメソッド定義の中で宣言したローカル変数はメソッド定義の外では使えません。これはブロックと同じです。まとめると、
- メソッド定義の外で宣言→中で使用 NG
- メソッド定義の中で宣言→外で使用 NG
となります。つまり、メソッド定義ではローカル変数は「壁を超えることが出来ない」のです。これは、クラス・モジュール定義でのローカル変数のスコープも同じです。
##トップレベルでのスコープ
(ここは筆者もあまりよくわかっていません)
トップレベルとは、クラス定義の外側の部分のことを指します。例えば、
puts "greeting." #ここはトップレベル
class User #クラス定義
def sayHi
puts "hi!"
end
end
tom = User.new #ここもトップレベル
tom.sayHi
そして当たり前といえば当たり前なのですが、トップレベルで宣言したローカル変数は、プログラムの最後まで有効です。同じトップレベルという場所であればどこでも使えます。
#グローバル変数
グローバル変数は「大域変数」とも呼ばれ、広い範囲で使える変数です。グローバル変数のスコープはプログラム全域です。同じプログラム内であればどこでも参照、代入できます。変数をグローバル変数として宣言するには変数名の先頭に$をつけます。
$global_var = "THIS IS GLOBAL VARIABLE."
#定数
変化しない変数(?)は定数と言います。これも特殊ですが変数に数えておきます。定数は原則としてアルファベットの大文字から始めます。
Const = 1000
基本的に定数は一度代入したオブジェクトをずっと変えずに参照することが目的なので、定数に再代入しようとすると警告が表示されます。
Const = 1000
puts Const
Const = 2000 #再代入
puts Const
出力
1000
main.rb:3: warning: already initialized constant Const
main.rb:1: warning: previous definition of Const was here
2000
警告は表示されるものの、きちんと再代入はできています。また、メソッド定義の中で定数を宣言することは出来ません。SyntaxErrorが表示されます。定義された定数はトップクラスor定義されたクラス(orモジュール)に属するということですね。
さて、定数のスコープもすこし複雑です。次のプログラムを見てください。
Const1 = 1000 #トップレベルで宣言した定数
class Sample
Const2 = 2000 #クラス内で宣言した定数
Const1 = 3000 #クラス内で宣言した、トップクラスのものと同じ名前の変数
def print_const
p Const1
p Const2
p ::Const1
end
end
Sample.new.print_const
トップレベルでConst1を宣言、クラスSample内でConst2を宣言、更にクラス内でトップレベルで宣言した定数と同じ名前の定数Const1を宣言した後、すべての定数を表示するメソッドprint_constを定義して呼び出しています。出力はこちらです。
3000
2000
1000
基本的に、クラス定義内で下位のネストレベルであれば、上位で定義した定数を呼び出すことが出来ます。また、クラス内にトップレベルで宣言したものと同じ名前の定数がある場合は、::をつけることでトップレベルで宣言した方を呼びだすことができます。
#クラス変数
クラスが持つ変数をクラス変数と言います。クラス変数は、クラスから複数のインスタンスが生成された場合、その全ての変数から参照することができ、全てのインスタンスで共有されます。クラス変数は、@2つから始まる名前で表記します。
@@class_var = "THIS IS CLASS VARIABLE."
クラス変数のスコープは、クラス定義内全体とそのクラスのインスタンスです。
class SampleClass
@@class_var = "THIS IS CLASS VARIABLE." #クラス変数
def print_cvar
puts @@class_var
end
end
my_object1 = SampleClass.new
my_object2 = SampleClass.new #複数のインスタンスを生成
my_object1.print_cvar
my_object2.print_cvar #複数のインスタンス間で値を共有できる
出力
THIS IS CLASS VARIABLE.
THIS IS CLASS VARIABLE.
#インスタンス変数
クラスから生成されたインスタンスが持つ変数をインスタンス変数と言います。クラス変数と違ってインスタンス変数生成された各インスタンスによって固有の変数で、互いに共有することはありません。インスタンス変数は、@から始まる名前で表記します。
@instance_var = "THIS IS INSTANCE VARIABLE."
インスタンス変数のスコープはクラスのメソッド定義内と狭いです。これでは外部からインスタンス変数を参照することができなくなってしまうため、インスタンス変数にアクセスするためのメソッドattr_accessorを用いることが通例となっています。(追記:scivolaさんによる解説がコメント欄にあります。ありがとうございました!)また、インスタンスを生成するときに初期化するためにinitializeという名前のメソッドを定義します。javaで言うコンストラクタですね。
class Calc
attr_accessor :x, :y #外部からインスタンス変数にアクセス可能にするメソッド
def initialize(x = 0, y = 0) #初期化
@x = x
@y = y
end
def add
sum = @x + @y
puts sum
end
end
Calc.new(2, 4).add
Calc.new(3, 8).add
出力
6
11
#最後に
スコープの話だけしようと思ってたんですがとても長くなってしまいました。少しでもわかりやすくーと思いながら書いていましたが最後になるにつれて色々乱雑になっていると思います。間違いや加筆修正などありましたらどんどんよろしくお願いします。最後までご覧頂きありがとうございました。