355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Хэл Фултон » Программирование на языке Ruby » Текст книги (страница 4)
Программирование на языке Ruby
  • Текст добавлен: 24 сентября 2016, 06:40

Текст книги "Программирование на языке Ruby"


Автор книги: Хэл Фултон



сообщить о нарушении

Текущая страница: 4 (всего у книги 56 страниц) [доступный отрывок для чтения: 20 страниц]

1.2.1. Ключевые слова и идентификаторы

Ключевые (или зарезервированные) слова в Ruby обычно не применяются ни для каких иных целей. Вот их полный перечень:

BEGIN  END   alias  and    begin

break  case  class  def    defined?

do     else  elsif  end    ensure

false  for   if     in     module

next   nil   not    or     redo

rescue retry return self   super

then   true  undef  unless until

when   while yield

Имена переменных и других идентификаторов обычно начинаются с буквы или специального модификатора. Основные правила таковы:

• имена локальных переменных (и таких псевдопеременных, как self и nil) начинаются со строчной буквы или знака подчеркивания _;

• имена глобальных переменных начинаются со знака доллара $;

• имена переменных экземпляра (принадлежащих объекту) начинаются со знака «собачки» @;

• имена переменных класса (принадлежащих классу) предваряются двумя знаками @ (@@);

• имена констант начинаются с прописной буквы;

• в именах идентификаторов знак подчеркивания _ можно использовать наравне со строчными буквами;

• имена специальных переменных, начинающиеся со знака доллара (например, $1 и $/), здесь не рассматриваются.

Примеры:

• локальные переменные alpha, _ident, some_var;

• псевдопеременные self, nil, __FILE__;

• константы K6chip, Length, LENGTH;

• переменные экземпляра @foobar, @thx1138, @not_const;

• переменные класса @@phydeaux, @@my_var, @@not_const;

• глобальные переменные $beta, $B2vitamin, $not_const.

1.2.2. Комментарии и встроенная документация

Комментарии в Ruby начинаются со знака решетки (#), находящегося вне строки или символьной константы, и продолжаются до конца строки:

x = y + 5 # Это комментарий.

# Это тоже комментарий.

print "# А это не комментарий."

Предполагается, что встроенная документация будет извлечена из программы каким-нибудь внешним инструментом. С точки зрения интерпретатора это обычный комментарий. Весь текст, расположенный между строками, которые начинаются с лексем =begin и =end (включительно), игнорируется интерпретатором (этим лексемам не должны предшествовать пробелы).

=begin

Назначение этой программы – излечить рак

и установить мир во всем мире.

=end

1.2.3. Константы, переменные и типы

В Ruby переменные не имеют типа, однако объекты, на которые переменные ссылаются, тип имеют. Простейшие типы – это символ, число и строка.

Числовые константы интуитивно наиболее понятны, равно как и строки. В общем случае строка, заключенная в двойные кавычки, допускает интерполяцию выражений, а заключенная в одиночные кавычки интерпретируется почти буквально – в ней распознается только экранированная обратная косая черта.

Ниже показана «интерполяция» переменных и выражений в строку, заключенную в двойные кавычки:

а = 3

b = 79

puts "#{а} умноженное на #{b} = #{а*b}" # 3 умноженное на 79 = 237

Более подробная информация о литералах (числах, строках, регулярных выражениях и т.п.) приведена в следующих главах.

Стоит упомянуть особую разновидность строк, которая полезна прежде всего в небольших сценариях, применяемых для объединения более крупных программ. Строка, выводимая программой, посылается операционной системе в качестве подлежащей исполнению команды, а затем результат выполненной команды подставляется обратно в строку. В простейшей форме для этого применяются строки, заключенные в обратные кавычки. В более сложном варианте используется синтаксическая конструкция %x:

`whoami`

`ls -l`

%x[grep -i meta *.html | wc -l]

Регулярные выражения в Ruby похожи на символьные строки, но используются по-другому. Обычно в качестве ограничителя выступает символ косой черты.

Синтаксис регулярных выражений в Ruby и Perl имеет много общего. Подробнее о регулярных выражениях см. главу 3.

Массивы в Ruby – очень мощная конструкция; они могут содержать данные любого типа. Более того, в одном массиве можно хранить данные разных типов. В главе 8 мы увидим, что все массивы – это экземпляры класса Array, а потому к ним применимы разнообразные методы. Массив-константа заключается в квадратные скобки. Примеры:

[1, 2, 3]

[1, 2, "застегни мне молнию на сапоге"]

[1, 2, [3,4], 5]

["alpha", "beta", "gamma", "delta"]

Во втором примере показан массив, содержащий целые числа и строки. В третьем примере мы видим вложенный массив, а в четвертом – массив строк. Как и в большинстве других языков, нумерация элементов массива начинается с нуля. Так, в последнем из показанных выше примеров элемент "gamma" имеет индекс 2. Все массивы динамические, задавать размер при создании не нужно.

Поскольку массивы строк встречаются очень часто (а набирать их неудобно), для них предусмотрен специальный синтаксис:

%w[alpha beta gamma delta]

%w(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)

%w/am is are was were be being been/

Здесь не нужны ни кавычки, ни запятые; элементы разделяются пробелами. Если встречаются элементы, содержащие внутренние пробелы, такой синтаксис, конечно, неприменим.

Для доступа к конкретному элементу массива по индексу применяются квадратные скобки. Результирующее выражение можно получить или выполнить для него присваивание:

val = myarray[0]

print stats[j]

x[i] = x[i+1]

Еще одна «могучая» конструкция в Ruby – это хэш. Его также называют ассоциативным массивом или словарем. Хэш – это множество пар данных; обыкновенно он применяется в качестве справочной таблицы или как обобщенный массив, в котором индекс не обязан быть целым числом. Все хэши являются экземплярами класса Hash.

Хэш-константа, как правило, заключается в фигурные скобки, а ключи отделяются от значений символом =>. Ключ можно считать индексом для доступа к ассоциированному с ним значению. На типы ключей и значений не налагается никаких ограничений. Примеры:

{1=>1, 2=>4, 3=>9, 4=>16, 5 = >25, 6=>36}

{"cat"=>"cats", "ox"=>"oxen", "bacterium"=>"bacteria"}

{"водород"=>1, "гелий"=>2, "углерод"=>12}

{"нечетные"=>[1,3,5,7], "четные"=>[2,4,6,8]}

{"foo"=>123, [4,5,6]=>"my array", "867-5309"=>"Jenny"}

К содержимому хэша-переменной доступ осуществляется так же, как для массивов, – с помощью квадратных скобок:

print phone_numbers["Jenny"]

plurals["octopus"] = "octopi"

Однако следует подчеркнуть, что у массивов и хэшей много методов, именно они и делают эти контейнеры полезными. Ниже, в разделе «ООП в Ruby», мы раскроем эту тему более подробно.

1.2.4. Операторы и приоритеты

Познакомившись с основными типами данных, перейдем к операторам в языке Ruby. В приведенном ниже списке они представлены в порядке убывания приоритета:

::                  Разрешение области видимости

[]                  Взятие индекса

**                  Возведение в степень

+ – ! ~             Унарный плюс/минус, НЕ…

* / %               Умножение, деление…

+ –                 Сложение/вычитание

<< >>               Логические сдвиги…

&                   Поразрядное И

|| ^                Поразрядное ИЛИ, исключающее ИЛИ

> >= < <=           Сравнение

== === <=> != =~ !~ Равенство, неравенство…

&&                  Логическое И

||                  Логическое ИЛИ

.. ...              Операторы диапазона

= (also +=, -=, …)  Присваивание

?:                  Тернарный выбор

not                 Логическое отрицание

and or              Логическое И, ИЛИ

Некоторые из перечисленных символов служат сразу нескольким целям. Например, оператор << обозначает поразрядный сдвиг влево, но также применяется для добавления в конец (массива, строки и т.д.) и как маркер встроенного документа. Аналогично знак + означает сложение чисел и конкатенацию строк. Ниже мы увидим, что многие операторы – это просто сокращенная запись вызова методов.

Итак, мы определили большую часть типов данных и многие из возможных над ними операций. Прежде чем двигаться дальше, приведем пример программы.

1.2.5. Пример программы

В любом руководстве первой всегда приводят программу, печатающую строку Hello, world!, но мы рассмотрим что-нибудь более содержательное. Вот небольшая интерактивная консольная программа, позволяющая переводить температуру из шкалы Фаренгейта в шкалу Цельсия и наоборот.

print "Введите температуру и шкалу (С or F): "

str = gets

exit if str.nil? or str.empty?

str.chomp!

temp, scale = str.split(" ")

abort "#{temp} недопустимое число." if temp !~ /-?d+/

temp = temp.to_f case scale

 when "С", "с"

  f = 1.8*temp + 32

 when "F", "f"

  с = (5.0/9.0)*(temp-32)

 else

  abort "Необходимо задать С или F."

end

if f.nil?

 print "#{c} градусов Cn"

else

 print "#{f} градусов Fn"

end

Ниже приведены примеры прогона этой программы. Показано, как она переводит градусы Фаренгейта в градусы Цельсия и наоборот, а также как обрабатывает неправильно заданную шкалу или число:

Введите температуру и шкалу (С or F): 98.6 F

37.0 градусов С

Введите температуру и шкалу (С or F): 100 С

212.0 градусов F

Введите температуру и шкалу (С or F):

92 G Необходимо задать С или F.

Введите температуру и шкалу (С or F): junk F

junk недопустимое число.

Теперь рассмотрим, как эта программа работает. Все начинается с предложения print, которое есть не что иное, как вызов метода print из модуля Kernel. Данный метод выполняет печать на стандартный вывод. Это самый простой способ оставить курсор в конце строки.

Далее мы вызываем метод gets (прочитать строку из стандартного ввода) и присваиваем полученное значение переменной str. Для удаления хвостового символа новой строки вызывается метод chomp!.

Обратите внимание, что print и gets, которые выглядят как «свободные» функции, на самом деле являются методами класса Object (который, вероятно, наследует Kernel). Точно так же chomp! – метод, вызываемый от имени объекта str. При вызовах методов в Ruby обычно можно опускать скобки: print "foo" и print("foo") – одно и то же.

В переменной str хранится символьная строка, но могли бы храниться данные любого другого типа. В Ruby данные имеют тип, а переменные – нет. Переменная начинает существовать, как только интерпретатор распознает присваивание ей; никаких предварительных объявлений не существует.

Метод exit завершает программу. В той же строке мы видим управляющую конструкцию, которая называется «модификатор if». Он аналогичен предложению if, существующему в большинстве языков, только располагается после действия. Для модификатора if нельзя задать ветвь else, и он не требует закрытия. Что касается условия, мы проверяем две вещи: имеет ли переменная str значение (то есть не равна nil) и не является ли она пустой строкой. Если встретится конец файла, то будет истинно первое условие; если же пользователь нажмет клавишу Enter, не введя никаких данных, – второе.

Это предложение можно было бы записать и по-другому:

exit if not str or not str[0]

Эти проверки работают потому, что переменная может иметь значение nil, а nil в Ruby в логическом контексте вычисляется как «ложно». На самом деле как «ложно» вычисляются nil и false, а все остальное – как «истинно». Это означает, кстати, что пустая строка "" и число 0 – не «ложно».

В следующем предложении над строкой выполняется операция chomp! (для удаления хвостового символа новой строки). Восклицательный знак в конце предупреждает, что операция изменяет значение самой строки, а не возвращает новую. Восклицательный знак применяется во многих подобных ситуациях как напоминание программисту о том, что у метода есть побочное действие или что он более «опасен», чем аналогичный метод без восклицательного знака. Так, метод chomp возвращает такой же результат, но не модифицирует значение строки, для которой вызван.

В следующем предложении мы видим пример множественного присваивания. Метод split разбивает строку на куски по пробелам и возвращает массив. Двум переменным в левой части оператора присваиваются значения первых двух элементов массива в правой части.

В следующем предложении if с помощью простого регулярного выражения выясняется, введено ли допустимое число. Если строка не соответствует образцу, который состоит из необязательного знака «минус» и одной или более цифр, то число считается недопустимым и программа завершается. Отметим, что предложение if оканчивается ключевым словом end. Хотя в данном случае это не нужно. Мы могли бы включить перед end ветвь else. Ключевое слово then необязательно; в этой книге мы стараемся не употреблять его.

Метод to_f преобразует строку в число с плавающей точкой. Это число записывается в ту же переменную temp, в которой раньше хранилась строка.

Предложение case выбирает одну из трех ветвей: пользователь указал С, F или какое-то другое значение в качестве шкалы. В первых двух случаях выполняется вычисление, в третьем мы печатаем сообщение об ошибке и выходим.

Кстати, предложение case в Ruby позволяет гораздо больше, чем показано в примере. Нет никаких ограничений на типы данных, а все выражения могут быть произвольно сложными, в том числе диапазонами или регулярными выражениями.

В самом вычислении нет ничего интересного. Но обратите внимание, что переменные с и f впервые встречаются внутри ветвей case. В Ruby нет никаких объявлений – переменная начинает существовать только в результате присваивания. А это означает, что после выхода из case лишь одна из переменных elif будет иметь действительное значение.

Мы воспользовались этим фактом, чтобы понять, какая ветвь исполнялась, и в зависимости от этого вывести то или другое сообщение. Сравнение f с nil позволяет узнать, есть ли у переменной осмысленное значение. Этот прием применен только для демонстрации возможности: ясно, что при желании можно было бы поместить печать прямо внутрь предложения case.

Внимательный читатель заметит, что мы пользовались только «локальными» переменными. Это может показаться странным, так как, на первый взгляд, их областью видимости является вся программа. На самом деле они локальны относительно верхнего уровня программы. Глобальными они кажутся лишь потому, что в этой простой программе нет контекстов более низкого уровня. Но если бы мы объявили какие-нибудь классы или методы, то в них переменные верхнего уровня были бы не видны.

1.2.6. Циклы и ветвление

Потратим немного времени на изучение управляющих конструкций. Мы уже видели простое предложение if и модификатор if. Существуют также парные структуры, в которых используется ключевое слово unless (в них также может присутствовать необязательная ветвь else), а равно применяемые в выражениях формы if и unless. Все они сведены в таблицу 1.1.

Таблица 1.1. Условные предложения


ifunless
if x < 5 then statement1 end unless x >= 5 then statement1 end
if x < 5 then statement1 else statement2 end unless x < 5 then statement2 else statement1 end
statement1 if y == 3 statement1 unless y != 3
x = if a>0 then b else c end x = unless a<=0 then с else b end

Здесь формы с ключевыми словами if и unless, расположенные в одной строке, выполняют в точности одинаковые функции. Обратите внимание, что слово then можно опускать во всех случаях, кроме последнего (предназначенного для использования в выражениях). Также заметьте, что в модификаторах (третья строка) ветви else быть не может.

Предложение case в Ruby позволяет больше, чем в других языках. В его ветвях можно проверять различные условия, а не только сравнивать на равенство. Так, например, разрешено сопоставление с регулярным выражением. Проверки в предложении case эквивалентны оператору ветвящегося равенства (===), поведение которого зависит от объекта. Рассмотрим пример:

case "Это строка символов."

 when "одно значение"

  puts "Ветвь 1"

 when "другое значение"

  puts "Ветвь 2"

 when /симв/

  puts "Ветвь 3"

 else

  puts "Ветвь 4"

end

Этот код напечатает Ветвь 3. Почему? Сначала проверяемое выражение сравнивается на равенство с двумя строками: "одно значение" и "другое значение". Эта проверка завершается неудачно, поэтому мы переходим к третьей ветви. Там находится образец, с которым сопоставляется выражение. Поскольку оно соответствует образцу, то выполняется предложение print. В ветви else обрабатывается случай, когда ни одна из предшествующих проверок не прошла.

Если проверяемое выражение – целое число, то его можно сравнивать с целочисленным диапазоном (например, 3..8); тогда проверяется, что число попадает в диапазон. В любом случае выполняется код в первой подошедшей ветви.

В Ruby имеется богатый набор циклических конструкций. К примеру, while и until – циклы с предварительной проверкой условия, и оба работают привычным образом: в первом случае задается условие продолжения цикла, а во втором – условие завершения. Есть также их формы с модификатором, как для предложений if и unless. Кроме того, в модуле Kernel есть метод loop (по умолчанию бесконечный цикл), а в некоторых классах реализованы итераторы.

В примерах из таблицы 1.2 предполагается, что где-то определен такой массив list:

list = %w[alpha bravo charlie delta echo];

В цикле этот массив обходится и печатается каждый его элемент.

Таблица 1.2. Циклы


i=0 while i < list.size do print "#{list[i]} " i += 1 end i=0 until i == list.size do print "#{list[i]} " i += 1 end
for x in list do print "#{x} " end list.each do |x| print "#{x} " end
i = 0 n=list.size-1 loop do print "#{list[i]} " i += 1 break if i > n end i=0 n=list.size-1 loop do print "#{list[i]} " i += 1 break unless i <= n end
n=list.size n.times do |i| print "#{list[i]} " end n=list.size-1 0.upto(n) do |i| print "#{list[i]} " end
n=list.size-1 for i in 0..n do print "#{list[i]} " end list.each_index do |x| print "#{list[x]} " end

Рассмотрим эти примеры более подробно. Циклы 1 и 2 – «стандартные» формы циклов while и until; ведут они себя практически одинаково, только условия противоположны. Циклы 3 и 4 – варианты предыдущих с проверкой условия в конце, а не в начале итерации. Отметим, что использование слов begin и end в этом контексте – просто грязный трюк; на самом деле это был бы блок begin/end (применяемый для обработки исключений), за которым следует модификатор while или until. Однако для тех, кто желает написать цикл с проверкой в конце, разницы нет.

На мой взгляд, конструкции 3 и 4 – самый «правильный» способ кодирования циклов. Они заметно проще всех остальных: нет ни явной инициализации, ни явной проверки или инкремента. Это возможно потому, что массив «знает» свой размер, а стандартный итератор each (цикл 6) обрабатывает такие детали автоматически. На самом деле в цикле 5 производится неявное обращение к этому итератору, поскольку цикл for работает с любым объектом, для которого определен итератор each. Цикл for – лишь сокращенная запись для вызова each; часто такие сокращения называют «синтаксической глазурью», имея в виду, что это не более чем удобная альтернативная форма другой синтаксической конструкции.

В циклах 5 и 6 используется конструкция loop. Выше мы уже отмечали, что хотя loop выглядит как ключевое слово, на самом деле это метод модуля Kernel, а вовсе не управляющая конструкция.

В циклах 7 и 8 используется тот факт, что у массива есть числовой индекс. Итератор times исполняется заданное число раз, а итератор upto увеличивает свой параметр до заданного значения. И тот, и другой для данной ситуации приспособлены плохо.

Цикл 9 – это вариант цикла for, предназначенный специально для работы со значениями индекса при помощи указания диапазона. В цикле 10 мы пробегаем весь диапазон индексов массива с помощью итератора each_index.

В предыдущих примерах мы уделили недостаточно внимания вариантам циклов while и loop с модификаторами. Они довольно часто используются из-за краткости. Вот еще два примера, в которых делается одно и то же:

perform_task() until finished

perform_task() while not finished

Также из таблицы 1.2 осталось неясным, что циклы не всегда выполняются от начала до конца. Число итераций не всегда предсказуемо. Нужны дополнительные средства управления циклами.

Первое из них – ключевое слово break, встречающееся в циклах 5 и 6. Оно позволяет досрочно выйти из цикла; в случае вложенных циклов происходит выход из самого внутреннего. Для программистов на С это интуитивно очевидно.

Ключевое слово retry применяется в двух случаях: в контексте итератора и в контексте блока begin-end (обработка исключений). В теле итератора (или цикла for) оно заставляет итератор заново выполнить инициализацию, то есть повторно вычислить переданные ему аргументы. Отметим, что к циклам общего вида это не относится.

Ключевое слово redo – обобщение retry на циклы общего вида. Оно работает в циклах while и until, как retry в итераторах.

Ключевое слово next осуществляет переход на конец самого внутреннего цикла и возобновляет исполнение с этой точки. Работает для любого цикла и итератора.

Как мы только что видели, итератор – важное понятие в Ruby. Но следует отметить, что язык позволяет определять и пользовательские итераторы, не ограничиваясь встроенными.

Стандартный итератор для любого объекта называется each. Это существенно отчасти из-за того, что позволяет использовать цикл for. Но итераторам можно давать и другие имена и применять для разных целей.

В качестве примера рассмотрим многоцелевой итератор, который имитирует цикл с проверкой условия в конце (как в конструкции do-while в С или repeat-until в Pascal):

def repeat(condition)

 yield

 retry if not condition

end

В этом примере ключевое слово yield служит для вызова блока, который задается при таком вызове итератора:

j=0

repeat (j >= 10) do

 j += 1

 puts j

end

С помощью yield можно также передать параметры, которые будут подставлены в список параметров блока (между вертикальными черточками). В следующем искусственном примере итератор всего лишь генерирует целые числа от 1 до 10, а вызов итератора порождает кубические степени этих чисел:

def my_sequence

 for i in 1..10 do

  yield i

 end

end

my_sequence {|x| puts x**3 }

Отметим, что вместо фигурных скобок, в которые заключен блок, можно написать ключевые слова do и end. Различия между этими формами есть, но довольно тонкие.


    Ваша оценка произведения:

Популярные книги за неделю