Значения структуры всегда копируются, когда вы присваиваете их из одной переменной в другую, когда вы передаете их в качестве аргументов при вызове метода или когда вы получаете их из возвращаемого значения при вызове метода. Это известно как семантика "по значению"; таким образом, рекомендуется , чтобы структуры были небольшими с точки зрения объема их памяти. Из этого правила есть интересное и полезное исключение: когда тело метода просто возвращает переменную экземпляра напрямую, копия удаляется, и к значению осуществляется прямой доступ. Давайте рассмотрим пример:
struct Location
property latitude = 0.0, longitude = 0.0
end
class Building
property gps = Location.new
end
building = Building.new
building.gps.latitude = 1.5
p store
В предыдущем примере мы создали структурный тип Location
, который имеет два свойства, и класс Building
, который имеет одно свойство. Макрос property gps
сгенерирует метод с именем def gps; @gps; end
для получателя - обратите внимание, что этот метод просто возвращает переменную экземпляра напрямую, что соответствует правилу исключения копирования. Если бы этот метод был каким-то другим, этот пример не сработал бы.
Строка building.gps.latitude = 1.5
вызывает метод gps
и получает результат, затем вызывает параметр latitude=setter
с значением 1.5
в качестве аргумента. Если бы возвращаемое значение gps
было скопировано, то средство настройки работало бы с копией структуры и не влияло бы на значение, хранящееся в переменной building
. Попробуйте поэкспериментировать с добавлением пользовательского определения для метода gps
.
Теперь, когда вы знаете, как создавать как классы, так и структуры, мы сделаем шаг вперед и узнаем о дженериках и о том, как эта новая концепция может помочь вам создавать более гибкие типы.
Общие (Generic) классы
Общий класс (или структура) создается на основе одного или нескольких неизвестных типов, которые определяются только позже, когда вы создаете экземпляр указанного класса. Это звучит сложно, но вы уже использовали некоторые общие классы раньше. Array
является наиболее распространенным: заметили ли вы, что нам всегда нужно указывать тип данных, которые содержит массив? Недостаточно сказать, что данная переменная является массивом — мы должны сказать, что это массив строк или Array(String)
. Универсальный класс Hash
аналогичен, но у него есть два параметра типа — типы ключей и типы значений.
Давайте посмотрим на простой пример. Предположим, вы хотите создать класс, который содержит значение в одной из переменных экземпляра, но это значение может быть любого типа. Давайте посмотрим, как мы можем это сделать:
class Holder(T)
def initialize(@value : T)
end
def get
@value
end
def set(new_value : T)
@value = new_value
end
end
Общие параметры, по соглашению, представляют собой одиночные заглавные буквы — в данном случае T
. В этом примере Holder
является универсальным классом, а Holder(Int32)
будет универсальным экземпляром этого класса: обычным классом, который может создавать объекты. Переменная экземпляра @value
имеет тип T
, независимо от того, какое T
будет позже. Вот как можно использовать этот класс:
num = Holder(Int32).new(10)
num.set 40
p num.get # Prints 40.
В этом примере мы создаем новый экземпляр класса Holder(Int32)
. Это как если бы у вас был абстрактный класс Holder
и наследуемый от него класс Holder_Int32
, созданный по требованию для T=Int32
. Объект можно использовать как любой другой. Методы вызываются и взаимодействуют с переменной экземпляра @value
.
Обратите внимание, что в этих случаях тип T
не обязательно указывать явно. Поскольку метод инициализации принимает аргумент типа T
, общий параметр можно вывести из использования. Давайте создадим Holder(String)
:
str = Holder.new("Hello")
p str.get # Prints "Hello".
Здесь T
считается строкой, поскольку Holder.new
вызывается с аргументом строкового типа.
Классы-контейнеры из стандартной библиотеки являются универсальными классами, как и определенный нами класс Holder
. Некоторые примеры: Array(T)
, Set(T)
и Hash(K, V)
. Вы можете поиграть с созданием собственных классов контейнеров, используя дженерики.
Далее давайте узнаем, как вызывать и обрабатывать исключения.
Исключения