Перечисления — это входной билет в объектно-ориентированное программирование. Теперь пришло время познакомиться с еще более функциональными и интересными конструкциями — структурами.
Вы уже давно используете структуры при написании кода. Все фундаментальные типы данных — это структуры. Они в некоторой степени похожи на перечисления и во многом сходны с классами (с ними мы познакомимся в скором времени).
Знакомство со структурами начнем с рассмотрения примера. Перед вами стоит задача описать в вашей программе сущность «игрок в шахматы», включая характеристики: имя и количество побед. Для решения этой задачи можно использовать кортежи и хранить в переменной имя и количество побед игрока (листинг 23.1).
Листинг 23.1
var playerInChess = (name: "Василий", wins: 10)
Таким образом мы, конечно же, решим поставленную задачу, но если количество характеристик будет увеличиваться, то кортеж станет чересчур сложным.
Соответственно, нам требуется механизм, позволяющий гибко описывать даже самые сложные сущности, учитывая все возможные параметры. Структуры как раз и являются таким механизмом. Они позволяют создать «скелет» сущности. Например, структура Int описывает сущность «целое число».
Синтаксис
struct ИмяСтруктуры {
// свойства и методы структуры
}
• Структуры объявляются с помощью ключевого слова struct, за которым следует имя создаваемой конструкции. Требования к имени предъявляются точно такие же, как и к имени перечислений: оно должно писаться в верхнем верблюжьем регистре.
• Тело структуры заключается в фигурные скобки и может содержать свойства и методы, подобные тем, что используются в перечислениях, но более функциональные.
ПРИМЕЧАНИЕ Объявляя структуру, вы определяете новый тип данных.
Объявим структуру, которая будет описывать сущность «игрок в шахматы» (листинг 23.2).
Листинг 23.2
struct PlayerInChess {}
var oleg = PlayerInChess()
type(of:oleg) //PlayerInChess.Type
Так как структура PlayerInChess является типом данных, то можно объявить новое хранилище (переменную) данного типа.
ПРИМЕЧАНИЕ Обратите внимание на то, что напротив функции type(of:) в области результатов в указании на тип присутствует префикс вида __lldb_expr_141 (число может отличаться). Это особенность Xcode Playground, данный префикс определяет модуль, в котором структура определена, то есть к какому пространству имен она относится. Вы можете не волноваться по этому поводу и не обращать на префикс никакого внимания.
Созданная структура PlayerInChess пуста — она не описывает какие-либо характеристики игрока. Для их реализации можно использовать свойства, с которыми мы уже встречались при изучении перечислений.
Синтаксис
struct ИмяСтруктуры{
var свойство1: ТипДанных
let свойство2: ТипДанных
// остальные свойства и методы
}
• свойство: Any — свойство структуры может быть любого типа данных.
Свойство может быть представлено как в виде переменной, так и в виде константы. Количество свойств в структуре не ограничено.
В структуру PlayerInChess добавим два свойства, описывающие имя и количество побед (name и wins) (листинг 23.3).
Листинг 23.3
struct PlayerInChess {
var name: String
var wins: UInt
}
Структуры, так же как и перечисления, имеют встроенный инициализатор (метод с именем init), который не требуется объявлять. Данный инициализатор принимает на входе значения всех свойств структуры, производит их инициализацию и возвращает экземпляр данной структуры (листинг 23.4).
Листинг 23.4
var harry = PlayerInChess.init(name: "Гарри", wins: 32)
В результате будет создан новый экземпляр структуры PlayerInChess, содержащий свойства с определенными в инициализаторе значениями.
Внимание При создании экземпляра структуры всем свойствам обязательно должны быть инициализированы значения. Пропустить любое из них недопустимо! Если значение какого-либо из свойств не будет указано, Xcode сообщит об ошибке.
Инициализатор автоматически вызывается всегда при создании нового экземпляра, поэтому можно опустить его имя и передавать значения свойств сразу после имени структуры (листинг 23.4а).
Листинг 23.4а
var harry = PlayerInChess(name: "Гарри", wins: 32)
Как вы можете видеть, при объявлении параметра не используется имя инициализатора.
Для свойств можно задавать значение по умолчанию. При этом Swift автоматически создает новый инициализатор, который позволяет создавать экземпляр без указания значений свойств. Вы сможете увидеть все доступные инициализаторы структуры в окне автодополнения во время создания экземпляра.
На рис. 23.1 изображены два разных инициализатора, доступных при создании экземпляра: один не требует указывать значения свойств, поскольку использует их значения по умолчанию, другой, наоборот, требует указать эти значения. Инициализатор, который не требует указывать какие-либо значения, называется пустым инициализатором.
Рис. 23.1. Два инициализатора в окне автодополнения
Значения по умолчанию указываются вместе с объявлением свойств точно так же, как вы указываете значение любой переменной или константы. При этом если вы решили дать значение по умолчанию хотя бы одному свойству, то должны указывать его и для всех остальных свойств.
Объявим значения по умолчанию для структуры PlayerInChess (листинг 23.5).
Листинг 23.5
struct PlayerInChess {
var name: String = "Игрок"
var wins: UInt = 0
}
var john = PlayerInChess(name: "Джон", wins: 32)
var player = PlayerInChess()
В обоих случаях создается экземпляр структуры PlayerInChess. Если для создания экземпляра выбирается пустой инициализатор, параметрам name и wins присваиваются их значения по умолчанию.
Структура образует отдельное пространство имен, поэтому для доступа к элементам этого пространства имен необходимо в первую очередь получить доступ к самому пространству.
В предыдущем примере была создана структура PlayerInChess с двумя свойствами. Каждое из свойств имеет некоторое значение, но от них не будет никакого толку, если не описать механизмы доступа к данным свойствам.
Доступ к элементам структур происходит с помощью экземпляров данной структуры (листинг 23.6).
Листинг 23.6
john.name // "Джон"
player.name // "Игрок"
Данный способ доступа обеспечивает не только чтение, но и изменение значения свойства экземпляра структуры (листинг 23.7).
Листинг 23.7
john.wins // 32
john.wins += 1
john.wins // 33
ПРИМЕЧАНИЕ Если свойство структуры или ее экземпляр указаны как константа (let), при попытке изменения значения свойства Xcode сообщит об ошибке.
Как говорилось ранее, инициализатор — это специальный метод, который носит имя init. Если вас не устраивают инициализаторы, которые создаются для структур автоматически, вы имеете возможность определить собственные.
ПРИМЕЧАНИЕ Автоматически созданные встроенные инициализаторы удаляются при объявлении первого собственного инициализатора.
Вам необходимо постоянно придерживаться правила: «все свойства структуры должны иметь значения при создании экземпляра данной структуры». Вы можете создать инициализатор, который принимает в качестве входного параметра значения не для всех свойств, тогда остальным свойствам должны быть назначены значения либо внутри данного инициализатора, либо через значения по умолчанию. Несмотря на то что инициализатор — метод, он объявляется без использования ключевого слова func. При этом одна структура может содержать произвольное количество инициализаторов, каждый из которых должен иметь уникальный набор входных параметров. Доступ к свойствам экземпляра внутри инициализатора осуществляется с помощью оператора self.
Создадим инициализатор для структуры PlayerInChess, который принимает значение только для свойства name (листинг 23.8).
Листинг 23.8
struct PlayerInChess {
var name: String = "Игрок"
var wins: UInt = 0
//инициализатор
init(name: String){
self.name = name
}
}
var helga = PlayerInChess(name: "Ольга")
helga.wins // 0
// следующий код должен был бы вызвать ошибку
// var newPlayer = PlayerInChess()
Инициализатор принимает значение только для свойства name, при этом свойству wins будет проинициализировано значение по умолчанию. При создании экземпляра вам будет доступен исключительно разработанный вами инициализатор.
Помните, что создавать собственные инициализаторы для структур не обязательно, так как они уже имеют встроенные инициализаторы.
Внимание Если экземпляр структуры хранится в константе, модификация его свойств невозможна. Если же он хранится в переменной, то возможна модификация тех свойств, которые объявлены с помощью оператора var.
Внимание Структуры — это типы-значения. При передаче экземпляра структуры от одного параметра в другой происходит его копирование. В следующем примере создаются два независимых экземпляра одной и той же структуры:
var olegMuhin = PlayerInChess(name: "Олег")
var olegLapin = olegMuhin
Помимо свойств, структуры, как и перечисления, могут содержать методы. Синтаксис объявления методов в структурах аналогичен объявлению методов в перечислениях. Они, как и обычные функции, могут принимать входные параметры. Для доступа к собственным свойствам структуры используется оператор self.
Реализуем метод description(), который выводит справочную информацию об игроке в шахматы на консоль (листинг 23.9).
Листинг 23.9
struct PlayerInChess {
var name: String = "Игрок"
var wins: UInt = 0
init(name: String){
self.name = name
}
func description(){
print("Игрок \(self.name) имеет \(self.wins) побед")
}
}
var andrey = PlayerInChess(name: "Андрей")
andrey.description()
Консоль
Игрок Андрей имеет 0 побед
По умолчанию методы структур, кроме инициализаторов, не могут изменять значения свойств, объявленные в тех же самых структурах. Для того чтобы обойти данное ограничение, перед именем объявляемого метода необходимо указать модификатор mutating.
Создадим метод wins, который будет изменять значение свойства wins (листинг 23.10).
Листинг 23.10
struct PlayerInChess {
var name: String = "Игрок"
var wins: UInt = 0
init(name: String){
self.name = name
}
func description(){
print("Игрок \(self.name) имеет \(self.wins) побед")
}
mutating func win( count: UInt = 1 ){
self.wins += count
}
}
var harold = PlayerInChess(name: "Гарольд")
harold.wins // 0
harold.win()
harold.wins // 1
harold.win( count: 3 )
harold.wins // 4
ПРИМЕЧАНИЕ Структура может изменять значения свойств только в том случае, если экземпляр структуры хранится в переменной.