Офіційний FAQ Ruby
If you wish to report errors or suggest improvements for this FAQ, please go to our GitHub repository and open an issue or pull request.
Змінні, константи та аргументи
Чи створює присвоєння нову копію об’єкта?
Усі змінні та константи посилаються (вказують) на якийсь об’єкт.
(За винятком неініціалізованих локальних змінних, які ні на що не
посилаються. При використанні вони викликають виняток NameError.)
Коли ви присвоюєте значення змінній або ініціалізуєте константу, ви
встановлюєте об’єкт, на який вона посилається.
Отже, саме по собі присвоєння ніколи не створює нову копію об’єкта.
Є трохи глибше пояснення для певних особливих випадків. Екземпляри
Fixnum, NilClass, TrueClass і FalseClass зберігаються безпосередньо
в змінних або константах — посилання не використовується. Змінна, що
містить число 42, або константа true фактично містить значення, а не
посилання на нього. Відповідно, присвоєння фізично створює копію об’єктів
цих типів. Детальніше про це — у
Immediate and Reference Objects.
Яка область видимості локальної змінної?
Нова область видимості для локальної змінної з’являється у: (1) верхньому рівні (main), (2) визначенні класу (або модуля), або (3) визначенні методу.
var = 1 # (1)
class Demo
var = 2 # (2)
def method
var = 3 # (3)
puts "in method: var = #{var}"
end
puts "in class: var = #{var}"
end
puts "at top level: var = #{var}"
Demo.new.method
Виведе:
in class: var = 2
at top level: var = 1
in method: var = 3
(Зверніть увагу, що визначення класу — це виконуваний код: повідомлення виводиться під час визначення класу).
Блок ({ ... } або do ... end) майже створює нову область видимості ;-)
Локальні змінні, створені всередині блоку, недоступні поза блоком.
Однак, якщо локальна змінна в блоці має те саме ім’я, що й існуюча локальна
змінна в області виклику, нова змінна не створюється, і ви зможете
звертатися до цієї змінної поза блоком.
a = 0
1.upto(3) do |i|
a += i
b = i*i
end
a # => 6
# b is not defined here
Це стає важливим при використанні потоків — кожен потік отримує свою власну копію змінних, локальних для блоку потоку:
threads = []
["one", "two"].each do |name|
threads << Thread.new do
local_name = name
a = 0
3.times do |i|
Thread.pass
a += i
puts "#{local_name}: #{a}"
end
end
end
threads.each {|t| t.join }
Може дати (якщо планувальник перемикає потоки, як підказує Thread.pass;
це залежить від ОС і процесора):
one: 0
two: 0
one: 1
two: 1
one: 3
two: 3
while, until і for — це керувальні структури, а не блоки, тому
локальні змінні всередині них доступні в зовнішньому середовищі.
loop, натомість, є методом і пов’язаний блок вводить нову область видимості.
Коли локальна змінна стає доступною?
Насправді питання краще сформулювати так: “у який момент Ruby вирішує,
що щось є змінною?” Проблема в тому, що простий вираз a може бути або
змінною, або викликом методу без параметрів. Щоб визначити, що це,
Ruby шукає оператори присвоєння. Якщо десь у джерелі до використання a
він бачить присвоєння, він вирішує трактувати a як змінну; інакше
розглядає це як метод. Як дещо патологічний випадок, розгляньте фрагмент
коду, спочатку надісланий Clemens Hintze:
def a
puts "method `a' called"
99
end
[1, 2].each do |i|
if i == 2
puts "a = #{a}"
else
a = 1
puts "a = #{a}"
end
end
Виведе:
a = 1
method `a' called
a = 99
Під час парсингу Ruby бачить використання a у першому puts і, оскільки
ще не бачив присвоєння a, вважає це викликом методу. Але до другого
puts він уже бачив присвоєння, і тому трактує a як змінну.
Зверніть увагу: присвоєння не обов’язково має виконатися — Ruby достатньо просто побачити його. Ця програма не викликає помилку:
a = 1 if false; a # => nil
Ця особливість зі змінними зазвичай не є проблемою. Якщо вона вас
турбує, спробуйте додати присвоєння a = nil перед першим доступом до
змінної. Це також має додаткову перевагу — пришвидшує доступ до локальних
змінних, які потім використовуються у циклах.
Яка область видимості константи?
Константа, визначена у класі або модулі, може бути доступна безпосередньо в межах визначення цього класу або модуля.
Ви можете напряму звертатися до констант зовнішніх класів і модулів зсередини вкладених класів і модулів.
Також можна напряму звертатися до констант суперкласів і включених модулів.
Окрім цих випадків, доступ до констант класів і модулів здійснюється через
оператор ::, ModuleName::CONST1 або ClassName::CONST2.
Як передаються аргументи?
Фактичний аргумент присвоюється формальному аргументу під час виклику методу. (Дивіться присвоєння для деталей семантики присвоєння.)
def add_one(number)
number += 1
end
a = 1
add_one(a) # => 2
a # => 1
Оскільки ви передаєте посилання на об’єкти, метод може змінити вміст змінюваного об’єкта, який йому передали.
def downer(string)
string.downcase!
end
a = "HELLO" # => "HELLO"
downer(a) # => "hello"
a # => "hello"
Еквівалента семантиці pass-by-reference інших мов немає.
Чи впливає присвоєння формальному аргументу на фактичний аргумент?
Формальний аргумент — це локальна змінна. Усередині методу присвоєння формальному аргументу просто змінює посилання на інший об’єкт.
Що відбувається, коли я викликаю метод через формальний аргумент?
Усі змінні Ruby (включно з аргументами методів) діють як посилання на об’єкти. Ви можете викликати методи цих об’єктів, щоб отримувати або змінювати стан об’єкта і змушувати об’єкт щось робити. Це можна робити з об’єктами, переданими в методи. Будьте обережні з цим, адже такі побічні ефекти можуть ускладнювати розуміння програм.
Що означає *, додана перед аргументом?
Якщо використовується у списку формальних параметрів, зірочка дозволяє передати довільну кількість аргументів у метод, збираючи їх у масив і присвоюючи цей масив параметру зі зірочкою.
def foo(prefix, *all)
all.each do |element|
puts "#{prefix}#{element}"
end
end
foo("val = ", 1, 2, 3)
Виведе:
val = 1
val = 2
val = 3
Коли * використовується у виклику методу, вона розгортає масив,
передаючи його елементи як аргументи.
a = [1, 2, 3]
foo(*a)
Ви можете ставити * перед останнім аргументом у:
- Лівій частині множинного присвоєння.
- Правій частині множинного присвоєння.
- Визначенні формальних аргументів методу.
- Фактичних аргументах у виклику методу.
- У
when-гілці конструкціїcase.
Наприклад:
x, *y = [7, 8, 9]
x # => 7
y # => [8, 9]
x, = [7, 8, 9]
x # => 7
x = [7, 8, 9]
x # => [7, 8, 9]
Що означає &, додана перед аргументом?
Якщо останній формальний аргумент методу має префікс амперсанда (&),
блок після виклику методу буде перетворено на об’єкт Proc і присвоєно
формальному параметру.
Якщо останній фактичний аргумент під час виклику методу — це об’єкт Proc,
ви можете додати перед його ім’ям амперсанд, щоб перетворити його на блок.
Метод потім може викликати його через yield.
def meth1(&b)
puts b.call(9)
end
meth1 {|i| i + i }
def meth2
puts yield(8)
end
square = proc {|i| i * i }
meth2 {|i| i + i }
meth2 &square
Виведе:
18
16
64
Як задати значення за замовчуванням для формального аргументу?
def greet(p1="hello", p2="world")
puts "#{p1} #{p2}"
end
greet
greet("hi")
greet("morning", "mom")
Виведе:
hello world
hi world
morning mom
Значення за замовчуванням (яким може бути довільний вираз) обчислюється під час виклику методу. Воно обчислюється в області видимості методу.
Як передавати аргументи в блок?
Формальні параметри блоку розташовуються між вертикальними рисками на початку блоку:
proc {|a, b| a <=> b }
Ці параметри — локальні змінні. Якщо на момент виконання блоку існує локальна змінна з таким самим ім’ям, вона буде змінена викликом блоку. Це може бути як добре, так і не дуже.
Зазвичай аргументи передаються в блок через yield (або ітератор, що
викликає yield), або через метод Proc.call.
Чому мій об’єкт несподівано змінився?
A = a = b = "abc"
b.concat("d") # => "abcd"
a # => "abcd"
A # => "abcd"
Змінні тримають посилання на об’єкти. Присвоєння A = a = b = "abc"
поміщає посилання на рядок "abc" у A, a і b.
Коли ви викликаєте b.concat("d"), ви викликаєте метод concat на цьому
об’єкті, змінюючи його з "abc" на "abcd". Оскільки a і A також
посилаються на той самий об’єкт, їхні видимі значення також змінюються.
На практиці це менш проблемно, ніж може здаватися.
Крім того, усі об’єкти можна заморозити, захистивши їх від змін.
Чи змінюється значення константи?
Константа — це змінна, ім’я якої починається з великої літери. Константи не можна переприсвоювати зсередини методів екземпляра, але в інших місцях їх можна змінювати за бажанням. Коли константі присвоюється нове значення, видається попередження.
Чому не можна завантажити змінні з окремого файлу?
Припустімо, що file1.rb містить:
var1 = 99
а інший файл завантажує його:
require_relative "file1"
puts var1
Виведе:
prog.rb:2:in `<main>': undefined local variable or method `var1' for main:Object (NameError)
Виникає помилка, оскільки load і require зберігають локальні змінні
у окремому анонімному просторі імен, фактично відкидаючи їх. Це зроблено,
щоб захистити ваш код від «забруднення».