Глава 3. Типизация переменных и управление ходом исполнения. — КиберПедия 

Своеобразие русской архитектуры: Основной материал – дерево – быстрота постройки, но недолговечность и необходимость деления...

История развития пистолетов-пулеметов: Предпосылкой для возникновения пистолетов-пулеметов послужила давняя тенденция тяготения винтовок...

Глава 3. Типизация переменных и управление ходом исполнения.

2022-09-29 29
Глава 3. Типизация переменных и управление ходом исполнения. 0.00 из 5.00 0 оценок
Заказать работу

Ваш черёд № 1

Преобразование типа среди целочисленных типов:

Crystal чрезвычайно пунктуален относительно типов, но до сих пор вы использовали to_i только для преобразования каких-то чисел в целочисленные значения. Crystal предлагает, между прочим, множество различных типов целых чисел от 8-разрядных до 64-разрядных, как со знаком, так и без знака.

Как выполнять преобразования между этими типами? И выходит, что
вы не делаете преобразования очевидным образом, но есть такие
особо мудрёные биты, завершающие этот процесс. Компилятор Crystal позволяет смешивать целочисленные типы, но в результате по умолчанию будет использоваться тип первого числа, а не тип наиболь-шего используемого значения. Как по-вашему, какие результаты дадут
два примера суммирования разнородных целочисленных:

  p int8 = 1_i8 # 8-bit signed integer

p int16 = 16_i16 # 16-bit signed integer

p int32 = 132_i32 # 32-bit signed integer

 p int64 = 164_i64 # 64-bit signed integer

 p uns64 = 264_u64 # 64-bit unsigned integer

 

1)  p int64 + int32 + uns64=;

2)  p int8 + int64=  ;

 

 

Сцепливание методов

По-видимому, вам нравится красивый код — в противном случае вы
бы не продолжали чтение этой книги. Преобразование данных часто требует предсказуемой последовательности методов. Одна из прелестей языка Crystal в том, что вы можете соединять несколько методов в
цепочку для создания более удобочитаемого кода. Метод может быть вызван для любого объекта, относительно которого он определен, и этот объект может быть результатом другого вызова какого-то метода.

Взгляните на следующие примеры:

types_and_control_flow/chaining.cr

 result = (42..47).to_a # => [42, 43, 44, 45, 46, 47]          (1)

 .sort { |n, m| m <=> n } # => [47, 46, 45, 44, 43, 42]     (2)

 .reject { |n| n.odd? } # => [46, 44, 42]                     (3)

 .map { |n| n * n } # => [2116, 1936, 1764]                  (4)

 .select { |n| n % 4 == 0 } # => [2116, 1936, 1764]            (5)

 .tap { |arr| puts "#{arr.inspect}" } # => [2116, 1936, 1764]       (6)

 .sort! # => [1764, 1936, 2116]                                       (7)

 .any? { |num| num > 2000 } # => true                                (8)

 

 1) Диапазон преобразуется в массив с "to_a".

 2) Массив сортируется в обратном порядке (< = > это оператор сравнения).

 3) Все нечетные числа исключаются с помощью "reject".

 4) Функция "map" принимает другую функцию в качестве аргумента, чтобы возвести все элементы в квадрат.

 5) Выбираются все числа, делящиеся на 4.

 6) Метод "tap" касание передает объект в блок и возвращает его — полезно для отладки или сжатия кода.

 7) Массив сортирует себя.

 8) Проверка функцией "any?" содержит ли массив число больше 2000.

 

 

Вы также имеете в своем распоряжении "reduce", который накапливает результат выполнения вычислений над элементами коллекции, в приведенном ниже случае — сумму (sum) всех чисел массива arr:

types_and_control_flow/chaining.cr

sum = (42..47).to_a

.reduce(0) { |sum, num| sum + num }

# => 267 (= 42 + 43 +... + 47)

Метод reduce привносит функциональный подход в математические вычисления, производимые в Crystal.

Вы можете именовать свои методы в той самой манере, характерной для ЯП Ruby:

• Методы, которые заканчиваются на "!" (вроде sort!), по условию изменяют объект, для которого они вызваны. (Обычно метод "pp!" показывает более полное выражение, чем "pp".)

• Методы, которые заканчиваются на "?" (такие как any?), задают логический вопрос и возвращают true или false.

 

Ваш черёд № 2

1) Разбейте следующую строку названий минералов на массив, где все названия представлены заглавными буквами:

gold;topaz;apatite;wolframite;calcite;diamond. (золото; топаз; апатит; вольфрамит; кальцит; алмаз.)

(Подсказка: используйте метод "map", о котором вы узнали в разделе §"Сцепливание методов").

 

2) Строки в сущности — просто последовательности символов в кодировке UTF-8. Если вы любитель кошек, то можете запросто ска-
зать: "hi 猫".

Выполните итерацию над этой строкой и напечатайте поочередно каждый символ, "кодовый указатель" (англ. codepoint — определяет сегмент в диапазоне значений) и байт. Для получения информации о доступных методах обратитесь к документации —

https://crystal-lang.org/api/master/String.html;

• Что за кодовый указатель Юникода соответствует китайскому символу для кошки, и сколько байт занимает этот символ?

3) object_id; Покажите, что две постоянные строки с одинаковым значением ссылаются на один и тот же объект в "куче". (Подсказка: используйте метод object_id;)

Почему же Crystal реализует это именно так?

Главная причина вот в чём: в тех случаях, когда два объекта имеют совпадающие object_id, оператор == возвращает значение true, как и метод "same?". Что происходит, когда вы запрашиваете object_id для целочисленного или булева (Boolean) значения?

 

Ваш черёд № 3

Есть ли разница между символом и строкой, несущей такое же
(точную копию) значение? Обоснуйте ответ.

 

 

Использование перечислений

Иногда вам хочется ограничить функциональность кода не только символами, но и меньшим набором возможностей. Это может оказаться весьма удобным — группировать переменные в разрозненный ряд значений, таких как цвета светофора или указания компаса, в специфический тип. Crystal поддерживает перечисления (Enums) для группирования связанных значений, в особенности — когда число отдельных значений не слишком велико:

types_and_control_flow/enums.cr

enum Direction

 

North # value 0

East # value 1

South # value 2

West # value 3

  end

 

Direction::South # South

Direction::South.value # => 2

 

Перечисления хранятся внутри в виде целых чисел, но отображение
их имён в коде делает его более понятным для людей. В струк-турированной Вселенной Кристалла все перечисления наследуются от базового класса Enum, и вы можете даже задавать методы для них.
У перечислений есть ещё одно важное преимущество: их проверяет компилятор. Но если вы неправильно написали символ, он будет
сохранен как новый символ.

Direction::Eest # Error: undefined constant Direction::Eest

:gold

:goold

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

 

 

Использование регулярных выражений

Иногда требуется гибкость строк, но вам нужно еще и эффективно их обрабатывать. Поиск и замена внутри строк также может затратить
много циклических операций. Регулярные выражения ("регексы" — от англ. regex) могут значительно снизить затраты на обработку строк, благодаря высоко-оптимизированным моделям обработки данных.

Crystal использует синтаксис PCRE. Это реализовано как привязка к
C-библиотеке PCRE. (Хотя Ruby использует другой механизм regex,
он также использует синтаксис PCRE.) Шаблоны обычно создаются с помощью синтаксического литерала /pattern/.

Если у вас есть регекс, содержащий косую черту (слэш), которую вам нежелательно удалять, вы можете создать шаблон с помощью "%r(pattern)". Лишь после создания шаблона его можно применять к строкам. Используйте сочетание "=~" или метод match, чтобы про-
верить совпадают ли две строки и с какой начальной позиции.

types_and_control_flow/regex.cr

str = "carbonantimonygolddiamond"

pattern = /gold/

    p str =~ pattern # => 14

В этом случае gold появляется в начальной позиции 14. Если же "gold" отсутствует в строке, проверка соответствия (match) вернет ноль (nil). Поскольку ноль несет значение "не истина", можно легко проверить совпадения с помощью конструкции "if materials =~ pattern", запускающей выполнение некоторых задач, если есть совпадение, либо каких-то иных задач — если совпадение отсутствует.

Вы можете также извлечь дополнительные сведения о строке и о её
связи с вашим регулярным выражением:

types_and_control_flow/regex.cr

materials = "carbonantimonygolddiamond"

pat = /(.+)gold(.+)/ # searches for gold

str = "carbonantimonygolddiamond"

str =~ pat # => 0

$1 # => "carbonantimony"

$2 # => "diamond"

 

str.match pat # =>

# <Regex::MatchData "carbonantimonygolddiamond"

                 # 1:"carbonantimony" 2:"diamond">

 

В этом примере начальное положение оглашено как "ноль" (и также истинно, что будет удобным для использования if), поскольку мы учитываем префикс (.+). Методы из класса Regex позволяют вам глуб-
же заглянуть в результаты сопоставления.

Crystal позволяет использовать синтаксис интерполяции строк (#{}) в литералах регулярных выражений, давая вам потенциально мощные возможности на стадии выполнения кода. Конечно, это также позволяет вам случайно создавать исключения времени выполнения, если ва-
шей вставкой интерполируется грязный синтаксис, так что будьте осторожны!

 

 

Ваш черёд № 4

Деструктуризация: Каковы значения переменных левее знака = (присвоение)?

var1, var2, var3 = [78, 56, 42] # array

var1, var2, var3 = {78, 56, 42} # tuple

Это хороший способ вывести значения из массива или кортежа. Мы можем использовать его в нашей программе "Конвертер валют" для замены:

  arr = input.split(" - ")

curr = arr[0]

rate = arr[1]

  более компактной записью:

curr, rate = input.split(" - ")

 

Из предыдущих примеров ясно, что Массив, Кортеж, Хэш и Набор могут принимать различные типы данных в качестве своих элементов, в то время как их методы работают со всеми этими типами. Вы можете заметить, что они используют некий тип (T, или K, или V) в качестве параметра.

Например, Array (T), Hash (K, V) и так далее, где T может означать
Int32, String, Char или любой другой тип. Другими словами: это общие (или универсальные) типы.

В главе 6 вы увидите, как определять собственные общие классы,
которые способны работать с любым типом.

 

Nil-типы

Как нам объявить переменную определенного типа, но также могущую принять нулевое значение (nil), т.н. «nilable»? Вы видели раньше, что в определенных ситуациях Crystal присваивает объекту тип объединения
во время компиляции.

Можно объявить тип nilable как тип объединения, но вы можете
также объявить его тип, приписав в конце "?", как показано здесь:

types_and_control_flow/type_nil.cr

n: Int32 | Nil

n = 42

p typeof(n) # => Int32

 

a: Int32?

a = 42

p typeof(a) # => Int32

 

b: Int32?

b = nil

p typeof(b) # => Nil

 

a.try { p a + 1 } # => 43

b.try { p b + 1 } # => no error!

При использовании nil-типов метод "try" может оказаться очень кстати: компилятор не будет сигнализировать об ошибках тогда, когда значе-
ние может оказаться нулевым. Но имейте в виду, что если вы таким способом заблокируете компилятор, то дальше вы сами по себе!

Теперь для вас пришло время работать с типами объединения самостоятельно:

 

Ваш черёд № 5

1. Типы объединений.

Какому типу соответствует переменная mineral в следующем фрагменте кода? Напишите конструкцию if для отображения названия минерала
в каждом возможном случае:

arr = ["anadite", "humite", "roselite"]

mineral = arr[4]?

2. Вот два пустых хэша, в которых присутствуют типы объединения. Каковы типы ключа и значения? Добавьте к ним пары "ключ-значение" так, чтобы использовать все типы, а также добавьте пару, которую
нельзя скомпилировать.

h1 = Hash(Int32 | Char, Int32).new

h2 = {} of String | Int32 => Bool | String

3. Каков тип времени компиляции и тип времени выполнения у переменной var1, вызываемой после выражений if, следующих ниже, и почему это так?

if rand < 0.5

var1 = 7

end # branches that are not present  return nil;

#

var1 = 42

if rand < 0.5

var1 = "Crystal"

end

var1 = 42

if rand < 0.7

var1 = "Crystal"

else

var1 = true

end

#

if rand < 0.5

var1 = 42

else

var1 = "Crystal"

end

var2 = var1 * 2

var3 = var1 - 1 # <= What does this return?

 

 

Ваш черёд № 6

1) Какое значение у var1 после этого if?

if var1 = 1

puts "inside if "

var1 = 2

puts var1

end

Здесь ход выполнения отличается от характерного для многих других языков программирования!

2) Какой вывод порождает следующая программа?

if (num = 9) < 0

puts "#{num}, is negative"

elsif num < 10

puts "#{num}, has 1 digit"

else

puts "#{num}, has multiple digits"

end

3) Проверьте этот фрагмент кода и объясните его поведение. Вы ви-
дите способ улучшить его?

begin

a = 4 + 6

rescue

puts "an ex occurred"

ensure

puts a + 42

end

Подсказка: компилятор учитывает вероятность возникновения исключительной ситуации.

Заключение

В этой главе мы глубже изучили некоторые базовые вещи Кристаль-
ного Программирования, такие как символы, кортежи и оператор case. Для Руби-кодера, возможно, это все не в новинку, но вы теперь лучше знакомы с компилятором Crystal и с тем, как он использует типы объединения, чтобы сделать ваш код более безопасным.

Объединенные (или комбинированные) типы составляют крае-
угольный камень типовой системы Кристалла. Вооружившись этим пониманием, давайте приступим к изучению методов и процедур
(а также посмотрим, как здорово Crystal использует перегрузку) в
нашей следующей главе.

 

 

Возврат значений

 

Метод возвращает результирующее значение своего последнего вызова, поэтому нет необходимости напрямую запрашивать это значение или объявлять его тип. Однако, при необходимости документирования или непосредственного управления этим возвращаемым типом, вы можете особо указать тип, как в этом примере:

methods_and_procs/methods.cr

def typed_method: Array(Int32)

(42..47).to_a.select { |n| n % 4 == 0 }

end

 

typed_method # => [44]

 

Также можно явно использовать Nil в качестве возвращаемого типа,
если необходимо указать, что это "сопутствующий побочный метод" (важность имеет лишь то, что он делает, а не значение, которое он возвращает). Разработчикам ПО, использующим ваш метод, нужно
быть готовым к этому или, по крайней мере, это не должно быть сюрпризом.

Если необходимо вернуть несколько значений, их можно упаковать в кортеж или массив. Как распаковать значение с помощью деструкции (вернитесь к примеру Ваш черёд №4, "Извлечение данных из структуры вроде массива"):

methods_and_procs/methods.cr

# Multiple return values

def triple_and_array(s)

{s * 3, s.split}

end

  # unpacking:

ret = triple_and_array("42") # => {"424242", ["42"]}

ret[0] # => "424242"

ret[1] # => ["42"]

# or:

num, arr = triple_and_array("gold")

num # => "goldgoldgold"

arr # => ["gold"]

 

 

Использование аргумента SPLAT *

Если вы хотите, чтобы ваш метод принимал переменное количество параметров, то вы не можете дать имя каждому из ваших аргументов. Вместо этого используйте один аргумент с префиксом *, чтобы создать совместно вызываемый сплат-аргумент. И тогда метод может при-
нимать от нуля до неограниченного числа параметров, потому что
все они преобразованы в один кортеж.

К примеру, вам требуется рассчитать зарплаты для неизвестного числа сотрудников:

methods_and_procs/methods.cr

def salaries(*employees)

employees.each do |emp|

# calculate salary

puts "#{emp}'s salary is: 2500"

 end

end

salaries() # =>

salaries("Jones") # => Jones's salary is: 2500

salaries("Baudelaire", "Rogers", "Gandhi")

# =>

  # Baudelaire's salary is: 2500

  # Rogers's salary is: 2500

  # Gandhi's salary is: 2500

 

Использование сплат-аргумента не означает, что вы сами должны разбираться со всеми аргументами. Crystal предлагает другой способ, используя лишь одиночный *, чтобы указать, что все аргументы после символа * нужно снабдить именными метками. Такой подход рабо-
тает следующим образом:

def display(n, *, height, width)

"The shape has height #{height} and width #{width}"

end

 

display 3, height: 2, width: 5

  # => "The shape has height 2 and width 5"

Можно даже присвоить именованному параметру дополнительное название (вроде домашнего прозвища) для использования в теле
метода, чтобы ваш код читался более естественно:

methods_and_procs/methods.cr

def increment(number, by value)

number + value

end

p increment(10, by: 10) # => 20

Другой классической ситуацией является вульгарная проблема созда-
ния строки значений, разделенных неким символом, но так, чтобы этот символ не мог появиться после финального значения. Например, мы хотим "1-2-3 " но не "1-2-3- ".

Приведенное ниже решение принимает изменчивое количество значений args, размечая их разделителем вида "with joiner"; joiner — внутреннее имя, используемое лишь внутри метода, а "with" — это
обычное имя, используемое при вызове метода для передачи значения параметра:

methods_and_procs/methods.cr

def join(*args, with joiner)

String.build do |str|

args.each_with_index do |arg, index|

str << joiner if index > 0

str << arg

end

 end

end

join 1, 2, 3, with: "-" # => "1-2-3"

join 1, 2, 3, 4, 5, with: "*" # => "1*2*3*4*5"

Ваш черёд № 1

1. Итак: Напишите метод "total" для вычисления суммы произвольного количества чисел. Измените total так, чтобы суммирование начиналось с предопределенного значения.

2. Сплат-кортеж:

Вы также можете распаковать кортеж (который мы уже исследовали в упражнении Ваш черёд №4) прямиком в аргументы метода.

Предположим, что вы хотите сделать это:

def add(n, m)

n + m

end

tpl = {42, 108}

add tpl

Это работает? Можете ли вы объяснить это?

Вам следует назвать это так: добавление *tpl, которое иногда назы-
вается раскладыванием кортежа. Если tpl является именованным кортежом и вы хотите использовать именованные аргументы, вам придется применить двойной сплат **. Пробуйте этот прием.

Другой трюк состоит в использовании двойного сплат-аргумента — "**argument" — для захвата произвольного числа именованных параметров в именованный кортеж.

 

Ваш черёд № 2

Синтаксический сахар:

• Вычислите третью степень (возведение в куб) чисел от 1 до 10. (Подсказка: используйте **.)

• Сортируйте массив langs (из предыдущего примера) по размеру строк (Подсказка: использовать метод sort_by).

• Выполните обратную сортировку всех букв из названий языков в
langs. Результат должен быть: ["vaaJ", "oG", "ytsrlaC"]. (Подсказка: Используйте формирование цепочки — chaining.)

 

 

Иcпользование Procs

В предыдущем разделе вы видели, что захваченный блок кода (как вы помните — &block) фактически является объектом, называемым процедурой (Proc), или лямбда-функцией, или анонимной функцией; вы можете думать о Proc как о функциональном объекте с методом call. Знайте, что процедуры можно создавать с помощью нескольких различных подходов:

1) Двухсимвольная запись "->" позволяет создать литерал процедуры. Здесь вы запишете свой метод add для двух чисел как процедуру:

methods_and_procs/procs.cr

fn = ->(n: Int32, m: Int32) { n + m }

typeof(fn) # => Proc(Int32, Int32, Int32)

fn.call(42, 108) # => 150

Имя процедуры находится слева от знака =. Затем оператор "->" указывает список параметров. Предупреждаем, что здесь вам необ-
ходимо использовать типы. Дальше следует фрагмент кода между фигурных скобок {}. Во второй строке компилятор делает вывод относительно числового типа, возвращаемого процедурой.

2) Вы можете создать процедуру из существующего метода "add", используя аналогичную запись:

methods_and_procs/procs.cr

def add(n, m)

n + m

end

fn = ->add(Int32, Int32)

fn.call(42, 108) # => 150

3) Тот факт, что Proc является классом в стандартной библиотеке Кристалла, позволяет вам также использовать метод new:

methods_and_procs/procs.cr

fn = Proc(Int32, Int32, Int32).new { |n, m| n + m }

fn.call(42, 108) # => 150

Как и во многих других языках, процедуры могут работать как "замыкатели", здесь подразумевается, что они могут перехватить область контроля над переменными, которые использовались в момент создания этой процедуры. Вот как это работает на примере с процедурными литералами:

methods_and_procs/procs.cr

n = 42

fn = ->(m: Int32) { n + m }

fn.call(108) # => 150

n = 20

fn.call(108) # => 128

Нетрудно заметить — значение "n" известно внутри процедуры, и оно
так же верно опознается, когда переменной "n" придают иное значе-
ние. Захваченные блоки имеют схожее с переменными поведение:

methods_and_procs/procs.cr

def capture(&block: Int32 -> Int32)

block

end

 

 n = 42

 proc = capture { |m| n + m }

 proc.call(108) # => 150

n = 20

proc.call(108) # => 128

 

Это также демонстрирует, что захваченный блок может сопровождаться примечанием, описывающим типы данных: к примеру "Int32 -> Int32" означает, что число "Int32" будет принято в качестве параметра и возвращен будет тип Int32. Чтобы взять два таких параметра, следует написать: "Int32, Int32 -> Int32".

 

Ваш черёд № 3

ReturnProc: Напишите метод возрастания (incr), который инициализирует счетчик цифрой "ноль" и возвращает инкрементное значение посредством процедуры Proc. Напечатайте его тип и вызовите его несколько раз. Что происходит, когда вы даете методу новое имя и вызываете его с этим именем несколько раз?

Ваша голова не идет кругом? Всевозможные синтаксические
комбинации блоков кода и процедур иногда кажутся чрезвычайно избыточными. Но существуют и весьма находчивые решения, такие
как хранение процедуры в переменной экземпляра для имитации
обработчика событий (см. §"Определение обратных вызовов").

Вознаградите себя чашкой кофе или хорошим фильмом!

 

Ваш черёд № 4

Пузырьковая сортировка:

Реализация алгоритма "Bubblesort" для сортировки массива (в виде программного псевдокода) найдется в Википедии. В вашей программе организуйте копирование вводимых данных с помощью метода dup. Напишите две версии алгоритма "Bubblesort", для сортировки по возрастанию и убыванию, используя "yield" и блок кода для каждой версии.

Заключение

Мы надеемся, вы уже убедились, что Crystal весьма универсален и
гибок в работе с аргументами метода. И он не испытывает затруднений, принимая и возвращая большие объемы числовых значений. Пере-
грузка с ограничением типов делает ваш код более надежным и часто более эффективным, в то время как разумное использование "Proc"
может сделать его более адаптивным.

В следующей главе мы поговорим о классах и структурах. Поглядим,
что Кристалл предложит вашему вниманию там.

 

 

Структурирование класса

В предыдущем параграфе и в разделе "Организация кода в классах и модулях" вы видели простой класс Минералы. Вот исходный код этого класса, так сказать, собственной персоной, без всякой дополнительной логики:

class Mineral

getter name: String

getter hardness: Float64

getter crystal_struct: String

def initialize(@name, @hardness, @crystal_struct) # constructor

end

end

Этот класс имеет три переменные экземпляра, доступные только для чтения "name", "hardness" и "crystal_struct". Предоставление им типов навязано компилятором Crystal. Но также вы можете расставить типы данных, используя метод initialize:

classes_and_structs/classes.cr

class Mineral

getter name, hardness, crystal_struct

 

 def initialize(@name: String, @hardness: Float64, @crystal_struct: String)

 end

end

 

Значения по умолчанию можно распределить следующим образом:

def initialize(@name: String = "unknown",...)

end

Некоторые люди используют символы вроде:hardness для имено-
вания свойства, но это не обязательно. Свойство с неустановленным
типом должно иметь значение по умолчанию. Или вы можете дать
ему значение при инициализации (initialize). Вам не нужно определять переменные в начале класса.

Здесь метод new создаёт объект Mineral:

min1 = Mineral.new("gold", 1.0, "cubic")

min1 # => #<Mineral:0x271cf00 @crystal_struct="cubic",

      # => @hardness=1.0, @name="gold">

min1.object_id # => 41012992 == 0x271cf00

typeof(min1) # => Mineral # compile-time type

min1.class # => Mineral # run-time type

Mineral.class # => Class # all classes have type Class

Запомните, new — это метод класса, который создается автоматически для каждого класса. Он резервирует место в оперативной памяти, вызывает метод initialize, а затем возвращает вновь созданный объект. Объект создается в так называемой "куче" (общем массиве свободной
ОП), и он снабжен уникальным идентификатором (object_id), сообщающим адрес ячейки памяти. Когда он получает новое имя или передается методу, фактически передается лишь ссылка на объект.

Это означает, что объект сам изменится в том случае, если будет
изменен в методе. Если вы не уверены, какие типы данных примет
ваш метод "initialize", вы также можете использовать групповые типы вроде "T", как это делается в классе Mineralg:

classes_and_structs/classes.cr

class Mineralg(T)

getter name

 

def initialize(@name: T)

 end

end

 

min = Mineralg.new("gold")

min2 = Mineralg.new(42)

min3 = Mineralg(String).new(42)

 # => Error: no overload matches 'Mineralg(String).new' with type Int32

 

Именование переменных экземпляра сопровождается приписыванием префикса "@". Для переменных класса используйте символы "@@", например @@planet, откуда и происходят наши минеральные породы. Все объекты, построенные с помощью этого класса, будут иметь общий доступ к этой переменной, и ее значение будет одинаковым для каждого такого объекта. (Впрочем подклассы, которые вы увидите в следующем разделе, все получают собственную копию переменной класса со значением, общим для всех членов отдельного подкласса.)

Чтобы дать имя свойству, которое может изменяться, например, "quantity" (количество) в следующем примере, припишите перед ним ключевое слово "property". Для свойств, которые доступны лишь для записи и потому не считываются, используйте префикс "setter", на-
пример, id в нижеследующем исходном коде.

Наша попытка вывести на дисплей нечитаемое свойство завершается ошибкой:

classes_and_structs/classes.cr

class Mineral

@@planet = "Earth"

 

getter name, hardness, crystal_struct

setter id

property quantity: Float32

 

def initialize(@id: Int32, @name: String, @hardness: Float64,

  @crystal_struct: String)

@quantity = 0f32

end

 

def self.planet

@@planet

end

end

min1 = Mineral.new(101, "gold", 1.0, "cubic")

min1.quantity = 453.0f32 # => 453.0

min1.id # => Error: undefined method  'id' for Mineral

Mineral.planet # => "Earth"

 

min2 = min1.dup

min1 == min2 # =>  false

 

Необходимо удостовериться, что ваши свойства всегда будут инициализированы либо в методе инициализации (initialize), либо
после вызова метода new. Названия методов, вызываемых в самом
классе, предваряют префиксом "self.", как это и было сделано в вышеприведенном примере с методом "planet".

Используйте метод dup для создания "упрощенной" копии объекта: копия min2 является другим объектом, но если оригинал содержит поля, которые сами являются объектами, то они не войдут в состав копии.
Если же вам нужна подробная копия, вам следует определить метод
clone. Вы можете также дополнительно написать метод "finalize" для класса, который будет автоматически вызываться, когда объект подбирается мусоросборщиком:

def finalize

puts  "Bye bye from this #{self}!"

end

Но это создает лишнюю работу для процесса сбора мусора. Вы должны использовать его, только если вы хотите освободить занятые внешними библиотеками ресурсы, которые сборщик мусора Crystal не освободит самостоятельно.

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

 

loop do

Mineral.new(101, "gold", 1.0, "cubic")

end

 

Как и в Ruby или в C#, вы можете повторно открыть класс, что оз-
начает создание дополнительных определений этого класса: ведь все
они объединены в один класс. Это также работает для встроенных
классов. Насколько это круто — определять свои собственные новые методы для существующих классов, таких как String или Array? (Да, иногда такой подход дерзко называют "обезьяний патчинг", и это не всегда хорошая идея.)

 

 

Ваш черёд № 1

 

1. Сотрудник: Создайте класс Employee с извлекателем (т.н. "getter")
name и свойством age. Создайте объект класса Employee и попытайтесь изменить его имя.

2. Приращение: Создайте класс Increment со свойством amount и
двумя версиями метода increment: один, который добавляет 1 к
amount, и другой, который добавляет значение "inc_amount".

 

Применение наследования

 

Как и во всех объектно-ориентированных языках и прежде всего в
Ruby, в Crystal предусмотрено одиночное наследование, обозначаемое: подкласс < суперкласс. Перемещение свойств и методов, объединен-
ных в несколько классов, в один суперкласс позволяет всем им
совместно использовать функциональные возможности. Таким обра-
зом, в подклассе вы можете использовать все переменные экземпляра
и все методы суперкласса, включая конструкторы.

Это можно увидеть в следующем примере, где PDFDocument наследует initialize, "name" и print из класса Document:

classes_and_structs/inheritance.cr

class Document

property name

 

def initialize(@name: String)

end

 

def print

puts "Hi, I'm printing #{@name}"

end

end

 

class PDFDocument < Document

end

 

doc = PDFDocument.new("Salary Report Q4 2018")

doc.print # => Hi, I'm printing Salary Report Q4 2018

Вы можете также переопределить любой унаследованный метод в подклассе. Если подкласс определяет собственные методы initialize для инициализации, они больше не наследуются. Если вы хотите исполь-зовать функциональность суперкласса после его переопределения,
можно вызвать любой метод суперкласса с помощью "super":

classes_and_structs/inheritance.cr

class PDFDocument < Document

def initialize(@name: String, @company: String)

end

 

def print

super

puts "From company #{@company}"

   end

end

 

# doc = PDFDocument.new("Salary Report Q4 2018")

 # => Error: wrong number of arguments for 'PDFDocument.new' (given 1,

            # => expected 2)

doc = PDFDocument.new("Salary Report Q4 2018", "ACME")

doc.print

 

# => Hi, I'm printing Salary Report Q4 2018

# From company ACME

 

Система типов языка Crystal дает вам больше возможностей здесь.
Вместо перестановки типов можно формировать специализированные методы с использованием ограничений для типов данных, например "print" в PDFDocument:

classes_and_structs/inheritance.cr

class PDFDocument < Document

def initialize(@name: String, @company: String)

end

 

def print(date: Time)

puts "Printing #{@name}"

puts "From company #{@company} at date #{date}"

 end

end

doc = PDFDocument.new("Salary Report Q4 2018", "ACME")

doc.print(Time.now)

 

# => Printing Salary Report Q4 2018

# From company ACME at date 2017-05-25 12:12:45 +0200

 

 

Использование абстрактных классов и виртуальных типов

ЯП Ruby, в отличие от Java или C#, не имеет собственной поддержки интерфейсов и абстрактных классов. И в Ruby, и в Crystal концепция интерфейса реализована посредством модулей, как вы увидите в следующей главе. Но в Кристалле также существует понятие абстракт-
ного класса, так что если вы Руби-кодер, то ожидающий впереди
материал будет вам в новинку.

Не всем классам суждено производить объекты, и абстрактные классы являются хорошим примером. Вместо этого они служат концеп-
туальным планом для подклассов, чтобы через них реализовать свои методы. Здесь вы видите класс Rect (описывающий прямоугольники), вынужденный реализовать все абстрактные методы из класса Shape:

classes_and_structs/inheritance.cr

abstract class Shape

abstract def area

abstract def perim

end

class Rect < Shape

def initialize(@width: Int32, @height: Int32)

end

 

def area

@width * @height

end

 

def perim

2 * (@width + @height)

 end

end

s = Shape.new # => can't instantiate abstract class Shape

Rect.new(3, 6).area # => 18

 

Если один из методов (скажем, perim) не реализован, компилятор
выдает ошибку, похожую на следующее:

«error:  abstract `def Shape#perim()` must be implemented by Rect»

Это позволяет вам создавать иерархии классов, в которых можно быть уверенным, что все необходимые методы проработаны должным
образом. Так же легко вы можете создавать более сложные структуры.
В следующем примере класс Document можно назвать виртуальным типом, потому что он сочетает различные типы из определенной
иерархии типов — в данном случае это разные документы:

classes_and_structs/virtual.cr

class Document

end

class PDFDocument < Document

def print

puts "PDF header"

end

end

 

class XMLDocument < Document

def print

puts "XML header"

end

end

 class Report

getter doc

 

def initialize(@name: String, @doc: Document)

end

end

salq4 = Report.new "Salary Report Q4", PDFDocument.new

taxQ1 = Report.new "Tax Report Q1", XMLDocument.new

Этот виртуальный тип указывается компилятором как тип Document+, подразумевая тем самым, что все типы наследуют от класса Document,
в том числе и сам Document. Он вступает в игру в ситуациях вроде
той, что приводится ниже, где вы ожидаете, что "d" относится к объединенному типу данных (PDFDocument | XMLDocument):

  if 4 < 5

d = PDFDocument.new

else

d = XMLDocument.new

end

typeof(d) # => Document

Но, вопреки ожиданиям, d соответствует типу Document. Внутри компилятор использует его как виртуальный тип Document+ вместо объединенного типа (PDFDocument | XMLDocument), поскольку типы объединений быстро становятся очень сложными в классовых
иерархиях. Если вызвать метод в подклассе, созданном для Document, возникает ошибка:

 «salq4.doc.print # => Error: undefined method  'print' for Document»

Чтобы устранить эту ошибку, просто сделайте класс Document абстрактным.

classes_and_structs/virtual.cr

abstract class Document

end

salq4.doc.print # => PDF header

Ваш черёд № 2

Фигура: Подкласс Shape с классами Square и Circle. (Подсказка: Примените PI из модуля Math, используя запись: "include Math".)

 

Управление видимостью

Видимые объекты можно прочитать или даже изменить с помощью внешнего кода, и это является заурядной причиной ошибок и сюр-
призов. Ограничение видимости (т.н. инкапсуляция кода) очень часто применяется в объектно-ориентированных языках. Все это связано с концепцией пространства имен в программном коде — некой (номенклатурной) области в коде, которая обозначена определенным именем. Класс является простым примером: всё внутри описания класса формирует часть его пространства имен.

По умолчанию объект действительно будет узнаваемым для всего пространства имен, в котором он определен: он является обще-
доступным в пределах этого пространства имен, но при этом неви-
димым и недоступным вне этого пространства имен. Мы поговорим об этом более подробно в следующей главе, посвященной модулям.

В Crystal методы по умолчанию являются публичными. То есть они пригодны для использования внутри и снаружи того класса, в котором
они определены. Чтобы ограничить видимость, вы можете подставить к формулировке метода специальное слово: либо "private" (намекает на приватность), либо "protected" (то есть защищенный).

 

 

Объявляем метод частным

Частные методы больше похожи на вспомогательные: их можно использовать только внутри самого класса или в его подклассах, и их нельзя вызвать для объекта.

classes_and_structs/private.cr

class Document

property name

 

def initialize(@name: String)

end

 

privat


Поделиться с друзьями:

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

Биохимия спиртового брожения: Основу технологии получения пива составляет спиртовое брожение, - при котором сахар превращается...

Археология об основании Рима: Новые раскопки проясняют и такой острый дискуссионный вопрос, как дата самого возникновения Рима...

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



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

0.446 с.