Перейти к содержимому
Теория · Переменные и типы · урок 4 из 7

Преобразование типов

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

Вот задача из магазина: товар стоит 2.5, вы берёте 3 штуки. Количество — целое число, цена — дробное. Чтобы их перемножить, количество сначала перекладывают в коробку для дробных чисел:

SWIFT
let count = 3
let price = 2.5
let total = Double(count) * price
print(total)  // 7.5

Давайте разберём пример по строкам:

  1. count получает тип Int: у числа 3 нет точки.
  2. price получает тип Double.
  3. Double(count) берёт 3 из count и делает из него дробное 3.0. Оба множителя теперь Double, и 3.0 * 2.5 даёт 7.5.
  4. print(total) печатает 7.5.

Что будет, если убрать Double(…)?

SWIFT
print(count * price) // ✗
TEXT
error: binary operator '*' cannot be applied to operands of type 'Int' and 'Double'

Знак * умеет умножать Int на Int и Double на Double, но не Int на Double. Swift не переводит одно в другое сам, даже когда это кажется безопасным.

Этот вопрос часто задают на собеседовании: «Есть ли в Swift неявное преобразование числовых типов?» Ответ — нет. Значения разных числовых типов не смешиваются в одной операции, пока одно из них не преобразуют явно.

Явное преобразование

Преобразование типа записывают как имя нужного типа и значение в скобках. Запись Double(count) состоит из двух частей:

  • Double — тип, в который перекладывают значение;
  • (count) — значение, которое перекладывают.

Результат — новое значение нужного типа. Сама count при этом не меняется и остаётся Int.

Три направления

Из Int в Double

Double(…) делает из целого числа дробное, и ничего не теряется: 3 становится 3.0. Это направление выбирают, когда в расчёте участвует дробное число, как цена в примере выше.

Из Double в Int

В коробке для целых чисел нет места для дробной части. Int(…) отбрасывает её, а не округляет. У отрицательного числа она отбрасывается так же, и результат оказывается ближе к нулю:

SWIFT
let height = 2.9
let temperature = -2.9
print(Int(height), Int(temperature))  // 2 -2
  1. Int(height) отрезает дробную часть от 2.9 и даёт 2, а не 3.
  2. Int(temperature) отрезает дробную часть от -2.9 и даёт -2, а не -3.

Здесь картинка с коробками перестаёт работать. Вещь, которая не влезла в коробку, остаётся лежать рядом целой. А дробная часть после Int(…) пропадает насовсем, и Swift не предупреждает об этом ни при компиляции, ни при запуске.

Из числа в строку

String(…) превращает число в текст: String(count) даёт строку "3", а String(price) — строку "2.5". Печатаются число и строка одинаково, но для программы это разные вещи. С числом считают, а текст можно приклеить к другому тексту: "Count: " + String(count) даёт Count: 3.

Числа, записанные в коде

Число, записанное прямо в коде, получает тип по месту. В let count = 3 оно становится Int. А в print(3 * 2.5) рядом стоит дробное число, поэтому Swift сразу делает 3 дробным, и print печатает 7.5. У константы count такой свободы нет: её тип Int выбран при объявлении.

Частая ошибка

Преобразование обернули вокруг всего умножения:

SWIFT
print(Double(count * price)) // ✗
TEXT
error: binary operator '*' cannot be applied to operands of type 'Int' and 'Double'

Компилятор сначала проверяет то, что стоит в скобках: count * price. Там по-прежнему Int и Double, а умножать их друг на друга * не умеет. Поэтому программа не компилируется, и Double(…) снаружи не помогает. Преобразуйте само значение count: Double(count) * price.

Главное

  • Swift не преобразует типы сам: Int и Double не смешиваются в одной операции.
  • Преобразование пишут как имя типа со значением в скобках: Double(count), а сама count остаётся Int.
  • Int(…) отбрасывает дробную часть, а не округляет: Int(2.9) — это 2, а Int(-2.9) — -2.
  • String(…) превращает число в текст, который можно приклеить к другому тексту.