Преобразование типов
В прошлом уроке тип был формой коробки. Но что делать, если значение лежит в коробке одной формы, а нужно оно в другой? Его перекладывают, и в Go это всегда делают вручную.
Переменные разных типов нельзя смешивать в одном выражении. Цена товара — дробное число, количество — целое, и перемножить их напрямую не получится:
package main
import "fmt"
func main() {
count := 3
price := 2.5
fmt.Println(count * price) // ✗
}invalid operation: count * price (mismatched types int and float64)Некоторые языки в таком случае сами превращают целое число в дробное. Go так не делает никогда: оба значения в арифметической операции должны быть одного типа. Программа не соберётся, пока типы не совпадут.
Этот вопрос часто задают на собеседовании: «Преобразует ли Go типы сам, неявно?» Ответ — нет. Сумма переменных типов int и float64 не скомпилируется, пока одно из значений не преобразуют явно.
Явное преобразование
Преобразование типа записывается как вызов функции. Запись float64(count) состоит из двух частей:
float64— имя типа, в который нужно переложить значение;(count)— значение в скобках, которое перекладывают.
Результат — новое значение нужного типа. Сама переменная count при этом не меняется: она остаётся int.
Преобразовать можно и целое выражение: float64(a + b) сначала складывает целые числа, а потом превращает сумму в дробную.
Два направления
Между int и float64 значение перекладывают в обе стороны. Направление выбирают так, чтобы ничего не потерять. Поэтому Go и требует писать преобразование явно: так в коде видно место, где может потеряться часть значения.
Из целого в дробное
float64(count) перекладывает целое значение в дробную коробку. Это преобразование исправляет пример с ценой:
count := 3
price := 2.5
total := float64(count) * price
fmt.Println(total) // 7.5Давайте разберём пример по строкам:
count := 3— целая коробка с3.price := 2.5— дробная коробка с2.5.total := float64(count) * price— здесьfloat64(count)кладёт копию3в дробную коробку, и получается3.0. Оба множителя теперь типаfloat64, и3.0 * 2.5даёт7.5.fmt.Println(total)печатает7.5.
Это направление выбирают, когда в выражении участвует дробное число и дробная часть результата нужна.
Из дробного в целое
В целой коробке нет места для дробной части. int(x) отбрасывает её, а не округляет. У отрицательного числа она отбрасывается так же, и результат ближе к нулю.
x := 2.9
fmt.Println(int(x), int(-x)) // 2 -2int(x)отрезает от2.9дробную часть и даёт2.int(-x)делает то же с-2.9и даёт-2, а не-3.
Чтобы округлить положительное число до ближайшего целого, прибавьте к нему 0.5 до преобразования: int(x + 0.5) для 2.9 даёт 3.
Ловушка этого направления — потеря на полпути. Запись int(price) * count тоже компилируется, но даёт 6, а не 7.5: дробная часть цены пропадает до умножения.
Здесь картинка с коробками перестаёт работать. Вещь, которая не влезает в коробку, остаётся снаружи целой. А при int(x) лишняя часть числа пропадает насовсем, и Go не предупреждает об этом при запуске.
Числа, записанные в коде
Числа, записанные прямо в коде, ведут себя иначе, чем переменные. У такого числа нет постоянного типа, пока оно не попало в переменную. Сравните два случая:
fmt.Println(3 + 1.5)компилируется и печатает4.5;- переменная
a := 3уже имеет типint, и сложить её сfloat64можно только после преобразования.
Частая ошибка
В int преобразовали дробное число, записанное прямо в коде:
fmt.Println(int(2.9)) // ✗cannot convert 2.9 (untyped float constant) to type intКомпилятор видит, что дробная часть такого числа потеряется, и отказывается собирать программу. Отбросить её можно только у значения в переменной, как в примере с int(x).
Главное
- Go никогда не преобразует типы сам: в арифметической операции оба значения одного типа.
- Преобразование пишут как вызов,
float64(count), а исходная переменная не меняется. int(x)отбрасывает дробную часть, а не округляет: дляx := 2.9получится2.- Число, записанное прямо в коде, не имеет постоянного типа, поэтому
3 + 1.5компилируется.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.