Swift: Типы и операции. Часть 2

image

Добро пожаловать во вторую часть мини-серии по изучению языка программирования Swift, в которой вы познакомитесь со строками, преобразованием типов и кортежами. Данный материал является переводом tutorial с сайта raywenderlich.com.

Эта часть является продолжением Swift: выражения, переменные и константы. Часть 1. Мы рекомендуем Вам начать с первой части, чтобы не пропустить и не потерять.

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

В этом учебном пособии вы научитесь работаь с различными типами которые есть в Swift. Вы научитесь конвертировать типы один в другой, а также познакомитесь с выводом типа, который сделает вашу жизнь как программиста намного проще.

В конце вы познакомитесь с кортежами (tuples), которые позволяют создавать собственные множественные значения любого типа.

С чего начать

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

var integer: Int = 100
var decimal: Double = 12.5
integer = decimal

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

'Cannot assign value of type 'Double' to type 'Int'`

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

Помните, что компьютеры полагаются на программистов, в том, что касается постановки задач и способов их решения. В Swift для исключения возможности появления ошибок включено явное преобразование типов. Если вы хотите чтобы преобразование произощло, вы должны это объявить!

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

var integer: Int = 100
var decimal: Double = 12.5
integer = Int(decimal)

Присвоение в третьей строке кода теперь говорит Swift'у недвусмысленно, что вы хотите преобразовать переменную типа Double в новый тип Int.

Примечание. В этом случае назначение десятичного значения целому числу приводит к потере точности: переменная integer заканчивается значением 12 вместо 12,5. Вот почему важно в данном случае конвертировать типы явным образом. Swift хочет убедиться что вы знаете, что делаете, и что вы понимаете, что таким образом можете потерять данные выполняя преобразование типа.

Операции с разными типами

Не так давно вы изучили числа и производили операции отдельно с целыми, отдельно с десятичными числами. Но, что если у вас будет целое число и вы захотите умножить его на десятичное?

Вы может быть думает, что сможете сделать так:

let hourlyRate: Double = 19.5
let hoursWorked: Int = 10
let totalCost: Double = hourlyRate * hoursWorked

Если вы попытаетсь это сделать, вы получите ошибку в последней строке:

`Binary operator '*' cannot be applied to operands
of type 'Double' and 'Int'`

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

Swift заставляет вас быть явным в вопросе, касающемся того, понимаете ли вы что делаете, когда умножатете переменную типа Int на переменую типа Double, потому, что результатом такой операции может быть только один тип. Хотите, чтобы результатом умножения был Int - конвертируйте Double в Int, до того, как выполнится умножение. Хотите, чтобы результатом операции был Double - конвертируйте Int в Double до выполнения операции умножения.

В этом примере вы хотите. чтобы результат имел тип Double. Вы не хотите, чтобы результатом был Int, потому что в этом случае Swift преобразует константу hourlyRate в Int, чтобы выполнить умножение, округляя ее до 19 и теряя точность Double.

Для этого вам нужно сказать Swift, что вы хотите, чтобы он считал константу hoursWorked Double:

let hourlyRate: Double = 19.5
let hoursWorked: Int = 10
let totalCost: Double = hourlyRate * Double(hoursWorked)

В результате каждый операнд будет иметь тип Double когда Swift пермножит их, таким образом константа totalCost будет иметь тип Double.

Вывод типа

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

let integer: Int = 42
let double: Double = 3.14159

Вы можете спросить себя: "Почему я должен всегда писать : Int и : Double, когда в правой части выражения также есть Int или Double"? Такая избыточность нужна для дополнительной страховки. Вы скоро сможете убедиться, что это можно и не делать.

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

Поэтому вы можете просто удалить тип данных в большинстве мест кода, где вы его видите.

Например, рассмотрим следующее объявление константы:

let typeInferredInt = 42

Иногда полезно проверять предполагаемый тип переменной или константы. Вы можете сделать это в playground, удерживая клавишу Option и нажав имя переменной или константы. Xcode отобразит следующее:

Inferred Int

Xcode сообщает вам вывод типа предоставляя вам декларацию о том, что вы bcgjkmpetnt, если не было вывода типа. В этом случае тип Int.

Это же справедливо и для других типов::

let typeInferredDouble = 3.14159

Клик по этому параметру показывает следующее:

Inferred Double

Вывод типа не является чем-то магическим. Swift просто делает то, что без труда делает ваш мозг. Языки программирования, которые не используют вывод типа, часто требуют большей "многословности" при написании кода, потому что вам нужно указывать явно тип каждый раз, когда вы объявляете переменную или константу.

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

let wantADouble = 3

Здесь Swift указывает тип wantADouble как Int. Но что, если вы хотите вместо этого Double?

Вы можете сделать следующее:

let actuallyDouble = Double(3)

Это тоже самое, что вы делали раньше для конвертации типов.

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

let actuallyDouble: Double = 3

Есть еще третий способ:

let actuallyDouble = 3 as Double

Здесь используется нновый оператор, которого вы раньше не видели. as, также выполняет конвертацию типа.

Вообще говоря, символ 3 сам по себе не имеет типа. Это только при использовании в выражении или присваивая этот символ константе или переменной, Swift назначает тип.

Примечание: Буквенные значения не имеют типа. Только при использовании их в

выражениях или присвоения их константе или переменной, для которых Swift требует определить тип.

Также может использоваться литеральное числовое значение, которое не содержит десятичной точки как Int и как Double. Вот почему вам разрешено присваивать значение 3 константе actuallyDouble.

Значения литеральных чисел, которые содержат десятичную точку, не могут быть целыми числами.

Это означает, что вы могли бы избежать всего этого обсуждения, если бы написали:

`let wantADouble = 3.0`

Строки

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

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

Как компьютер представляет строки

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

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

Unicode comic

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

Unicode

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

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

Примечание: Вы можете прочитать больше об Unicode на официальном сайте

Рассмотрим слово cafe. Стандарт Unicode говорит нам, что буквы этого слова должны быть закодированы цифрами следующим образом:

cafe

Число, связанное с каждым символом, называется кодовой точкой. Итак, в пример выше, c использует кодовую точку 99, a использует кодовую точку 97 и так далее.

Конечно, Unicode предназначен не только для простых латинских символов, используемых в английском языке, например, c, a, f и e. Он также позволяет вам отображать символы из языков во всем мире. Слово кафе происходит от французского, в котором написано как cafe. Unicode отображает эти символы так:

cafe_with_accent

И вот пример использования китайских иероглифов (это, согласно Google переводчику, означает "Computer Programming"):

computer_programming_chinese

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

poo_face

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

Примечание: слово «emoji» происходит от японского: «e» означает изображение, а «moji» означает буквы.

Строки в Swift

Swift, как и любой хороший язык программирования, может работать непосредственно и с буквами, и со строками. Он делает это через типы данных Character и String, соответственно. В этом разделе вы узнаете об этих типах данных и о том, как работать с ними.

Characters and Strings

Character это тип данных, экземпляр которого может хранить одну букву (один символ). Например:

let characterA: Character = "a"

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

let characterDog: Character = "🐶"

Но этот тип данных разработан для хранения только одного символа. А вот тип String может хранить несколько символов. Например:

let stringDog: String = "Dog"

Правая сторона этого выражения представляет собой то, что известно под названием строковый литерал. Это синтаксис Swift для представления строк.

Конечно, вывод типа применяется по отношению к строкам также, как к числам. Если вы удалите тип из объявления константы stringDog тогда Swift справедливо назначит этой константе тип String.

let stringDog = "Dog" // Inferred to be of type String

Примечание: В Swift нет такого понятия, как символьный литерал. Символ представляется строкой с длинной равной единице. Однако Swift указывает тип любой строки литералом String, поэтому, если вы хотите вместо этого получить тип Character, вы должны явно назначить этот тип.

Конкатенация строк

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

В Swift это делается довольно просто: с помощью оператора сложения. Так же, как вы можете сложить числа, вы можете сложить строки:

var message = "Hello" + " my name is "
let name = "Lorenzo"
message += name // "Hello my name is Lorenzo"

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

Также возможно добавить символы в строку. Однако строгая типизация Swift означает, что типы должны быть явными, так же, точно так же как это было показано на примере чисел.

Чтобы добавить символ в строку, можно сделать следующее:

let exclamationMark: Character = "!"
message += String(exclamationMark) // "Hello my name is Lorenzo!"

С помощью этого кода вы явно преобразуете Character в строку String до того, как добавите его в message.

Интерполяция строк

Вы также можете создать строку, используя интерполяцию, которая является специальным cинтаксисом Swift и позволяет создавать строку таким способом, который легко читать:

let name = "Lorenzo"
let messageInOne = "Hello my name is \(name)!" // "Hello my name is Lorenzo!"

Такой код читается гораздо легче, чем пример из предыдущего раздела. Это расширение синтаксиса строкового литерала, посредством которого вы заменяете определенные части строки другими значениями.

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

let oneThird = 1.0 / 3.0
let oneThirdLongString = "One third is \(oneThird) as a decimal."

В данном примере вы добавляете в строку константу Double способом интерполяции. В результатае последняя строка кода - константа oneThirdLongString будет содержать следующее:

One third is 0.3333333333333333 as a decimal.

Разумеется, на самом деле длинна oneThird практически бесконечна, потому что это повторяющееся десятичное число. Строчная интерполяция с помощью Double не дает возможности контролировать точность получаемой строки.

Это следствие использования интерполяции строк - просто для использования, но не дает возможности настраивать вывод. Имейте это ввиду.

Многострочные строки

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

Делается это так:

let bigString = """
    Вы можете создавать строки  
    которые имеют несколько  
    строк  
    делая это
    таким способом.
    """
print(bigString)

Три двойные кавычки означают, что это многострочная строка. Первая и последняя строки кода не становятся частью строки. Это делает его более гибким.

В приведенном выше примере будет напечатано следующее:

Вы можете создавать строки  
которые имеют несколько  
строк  
делая это
таким способом.

Кортежи (Tuples)

Иногда данные формируются парами или тройками. Примером этого является пара (x, y) в двумерной сетке. Аналогично, набор координат в трехмерной сетке состоит из значения x, y-значения и z-значения.

В Swift вы можете представлять такие связанные данные очень простым способом через использование кортежа.

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

let coordinates: (Int, Int) = (2, 3)

Тип coordinates - это кортеж, содержащий два значения Int. Типы значений внутри кортежа, в этом случае Int, разделяются запятыми окруженный круглыми скобками. Код для создания кортежа - тое же, с каждым значением, разделенным запятыми и окруженным скобками.

Вывод типа может также определять типы кортежей:

let coordinatesInferred = (2, 3) // вывод типа (Int, Int)

Таким же образом можно создать кортеж, состоящий из знчений Double:

let coordinatesDoubles = (2.1, 3.5) // Вывод типа (Double, Double)

Или вы можете включать в кортеж различные типы:

let coordinatesMixed = (2.1, 3) // Вывод типа (Double, Int)

А вот как происходит доступ к данным внутри кортежа:

let coordinates = (2, 3)
let x1 = coordinates.0
let y1 = coordinates.1

Вы можете получить каждый элемент в кортеже по его индексу в кортеже, начиная с нуля. Итак, в этом примере x1 будет равен 2, а y1 будет равен 3.

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

В предыдущем примере может быть не сразу очевидно, что первое значение в индексе 0 - это координата x, а второе значение - по индексу 1, является y-координатой. Это еще одна демонстрация того, почему важно всегда называть переменные таким образом, чтобы избежать путаницы.

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

let coordinatesNamed = (x: 2, y: 3) 
// Вывод типа (x: Int, y: Int)

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

let x2 = coordinatesNamed.x
let y2 = coordinatesNamed.y

Это гораздо понятнее. Полезно именовать компоненты ваших кортежей.

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

let coordinates3D = (x: 2, y: 3, z: 1)
let (x3, y3, z3) = coordinates3D

Объявлене трех новых констант: x3, y3 и z3 и присваивание каждую часть кортежа соответствующим константам. Код эквивалентен следующему:

let coordinates3D = (x: 2, y: 3, z: 1)
let x3 = coordinates3D.x
let y3 = coordinates3D.y
let z3 = coordinates3D.z

Если вы хотите игнорировать определенный элемент кортежа, вы можете заменить соответствующую часть декларации нижним дефисом. Например, если вы выполняли 2D-расчет и хотели игнорировать z-координату coord3D, то вы должны написать следующее:

let (x4, y4, _) = coordinates3D

Эта строка кода объявляет только x4 и y4. _ является особым знаком и означает, что вы сейчас игнорируете эту часть.

Примечание: Вы увидите, что вы можете использовать символ нижнего дефиса - также называемый оператором подстановочного знака - во всем Swift, чтобы игнорировать значение.

Числовые типы

Вы используете Int для представления целых чисел. Int представлен 64-битным на большинстве современных аппаратных средств и 32-битным на устаревших или имеющих ограниченные ресурсы. На самом деле Swift предоставляет гораздо больше числовых типов, которые используют разные объемы памяти для хранения значений. Для целых чисел вы можете использовать явное объявление типов Int8,Int16, Int32 и Int64. Эти типы занимают 1, 2, 4 и 8 байтов памяти, соответственно. Каждый из этих типов использует 1 бит для хранения одного знака.

Если вы имеете дело только с неотрицательными значениями, существует набор явных беззнаковые типов, которые вы можете использовать. К ним относятся UInt8, UInt16, UInt32 и UInt64.

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

int_sizes

Вы использовали Double для представления дробных чисел. Swift предлагает Float, который имеет меньше разрядов и точность чем Double, но требует вдвое меньше места для хранения. Современное аппаратное обеспечение как правило оптимизировано для Double, поэтому Double это тот тип, который следует использовать, если, вы хотите получить максимальную точность десятичного числа.

float_sizes

В большинстве случаев вы будете использовать Int и Double для представления чисел, но время от времени, вы можете столкнуться с другими типами. Вы уже знаете как с ними бороться. Например, предположим, что необходимо сложить INT16 с uint8 и int32. Вы можете сделать это следующим образом:

let a: Int16 = 12
let b: UInt8 = 255
let c: Int32 = -100000

let answer = Int(a) + Int(b) + Int(c) // Answer is an Int

Взгляд за кулисы: протоколы

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

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

В случае целых чисел, этот принцип представлен на диаграмме:

Integer Protocols

Стрелки указывают на соответствие (иногда называемое адоптация) протоколу. Не смотря на то, что этот график не показывает все протоколы, которым соответствуют целочисленные типы, это дает вам представление о том, как все организовано.

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

Что дальше?

Вы можете загрузить финальный файл playground. Там же вы найдете мини-упражнения для выполнения. Если вы застряли или вам нужна помощь, не стесняйтесь воспользоваться сопутствующими решениями.

В этом уроке вы узнали, что типы являются фундаментальной частью программирования. Это то, что позволяет правильно хранить данные. Кроме этого вы познакомились со строками и кортежами, а также кучей числовых типов.

В следующей части вы узнаете о Булевой логике и простом управлении потоком передачи данных.