Константы
В языке Go, термин константа используется для представления постоянных, неизменяемых значений, таких как 4, 1.23, true, «Привет» и так далее.
Литералы являются константами
Литерал — лексема, которая непосредственно представляет некоторое значение [ГОСТ 28397-89]
Все литералы в Go, будь то целочисленный литерал, например 2 или 2000, литерал с плавающей точкой, например 2.34, 1.78, логический литерал, например true и false или строковые литералы, например «Привет» и «Golang», являются константами.
Тип | Описание | Пример |
integer | Целочисленный | 5000, 123456 |
floating-point | Число с плавающей точкой | 7.89, 456.987 |
boolean | Логический | true, false |
rune | 4-байтовое unicode представление символов UTF-8 |
‘A’, ‘ä’ |
complex | Комплексное число | 2.7i, 1 + 3i |
string | Строка | «Привет», «Hello» |
Объявление констант
Литералы, по сути, являются константами без имени. Для объявления константы с определенным именем, можно использовать ключевое слово const. Пример:
const myFavLanguage = "Python" const sunRisesInTheEast = true
Или же, можно объявить переменную с явным указанием типа:
const a int = 1234 const b string = "Hi"
Так же, возможно множественное определение констант в одном выражении:
const country, code = "India", 91 const ( employeeId string = "E101" salary float64 = 50000.0 )
Как можно догадаться, константы не могут быть переопределены. Это означает. что нельзя присвоить константе другое значение после её объявления.
const a = 123 a = 321 // Ошибка компиляции (Cannot assign to constant)
Типизированные и нетипизированные константы
Константы в Go отличаются устройством и поведением от констант в других языках программирования. Для того, чтобы понять эти отличия в устройстве и поведении, необходимо разобраться в некоторых особенностях системы типов Go.
Go является статически-типизированным языком программирования. Это означает, что тип каждой переменной определяется компилятором во время компиляции.
Если смотреть глубже, система типов не позволяет выполнять операции, в которых используются разные типы данных. Например, запрещено складывать значения типа float64 и int , или даже in64 и int .
var myFloat float64 = 21.54 var myInt int = 562 var myInt64 int64 = 120 var res1 = myFloat + myInt // Запрещено (Ошибка компиляции) var res2 = myInt + myInt64 // Запрещено (Ошибка компиляции)
В таких случаях необходимо использовать явное приведение типов
var res1 = myFloat + float64(myInt) // Работает var res2 = myInt + int(myInt64) // Работает
Такое поведение Go отличает его от других языков со статической типизацией, как C, C++ или Java, которые позволяют автоматически конвертировать меньшие типы в большие при совместном использовании их в одном выражении. Например int может быть автоматически сконвертирован в long, float или double.
Причина отсутствия неявного приведения типов в Go объясняется его разработчиками следующим образом:
Удобство автоматической конвертации между типами в C перечеркивается путанницей, которую она вносит. Когда выражение является беззнаковым? На сколько значение является большим? Было ли переполнение? Является ли результат платформонезависимым? Так же, это усложняет компилятор: «обычные арифметические преобразования» не так просты в реализации и реализации могут различаться в зависимости от архитектуры. В угоду переносимости, мы решили сделать эти вещи прозрачными и простыми, что привело к необходимости использования явных преобразований типов в коде.
Итак, Go не поддерживает неявное приведение типов и требует от разработчика явно приводить типы при использовании разных типов в одном выражении.
Но как система типов Go работает с константами? Рассмотрим следующие выражения, которые являются валидными в Go.
var myInt32 int32 = 10 var myInt int = 10 var myFloat64 float64 = 10 var myComplex complex64 = 10
Какой тип имеет константа со значением 10 в приведенных выражениях? Более того, если в Go нет неявного приведения типов, почему нет необходимости использовать выражения такого вида?
var myInt32 int32 = int32(10) var myFloat64 float64 = float64(10) // И так далее ...
Ответы на эти вопросы кроются в том, каким образом Go обрабатывает константы.
Нетипизированные константы
Все константы в Go, именованные или неименованные, являются нетипизированными до тех пор, пока тип не будет задан явно. Например, все эти константы являются нетипизированными:
1 // нетипизированная целочисленная константа 4.5 // нетипизированная константа с плавающей точкой true // нетипизированная логическая константа "Hello" // нетипизированная строковая константа
Они остаются нетипизированными даже после того, как им назначены имена:
const a = 1 const f = 4.5 const b = true const s = "Привет"
Несмотря на использование терминов целочисленная, логическая и других, константы остаются нетипизированными. Дело в том, что значение 1 является целым числом, 4.5 — числом с плавающей точкой, «Привет» — строкой, но это, всего лишь, значения. Они еще не имеют определенного типа, например int32, float64 или string, что заставило бы их подчинятся строгим правилам типизации Go.
По факту, значение 1 является нетипизированным и может быть присвоено любой переменной, тип которой совместим с целыми числами.
var myInt int = 1 var myFloat float64 = 1 var myComplex complex64 = 1
Не смотря на то, что значение 1 является не типизированным, оно является нетипизированным целочисленным. Поэтому оно может быть использовано только там, где допустимо использование целых чисел. Нельзя присвоить строковое значение переменной с логическим типом.
Аналогично, нетипизированная переменная с плавающей точкой 4.5 может быть использована в любых выражениях, где разрешено использование чисел с плавающей точкой.
var myFloat32 float32 = 4.5 var myComplex64 complex64 = 4.5
Рассмотрим пример использования нетипизированных строковых констант.
В языке Go возможно создание псевдонимов типов при помощи ключевого слова type.
type RichString string // Псевдоним типа `string`
Следуя строгой типизации Go, невозможно присвоить переменной типа RichString значение переменной типа string.
var myString string = "Привет" var myRichString RichString = myString // Не работает.
Однако, переменной типа RichString возможно присвоить значение строковой нетипизированной константы. Это обусловлено тем, что тип RichString является совместимым со строковым.
const myUntypedString = "Hello" var myRichString RichString = myUntypedString // Работает
Константы и вывод типов: тип по умолчанию
Язык Go поддерживает вывод типов. Это означает, что тип переменной может быть выведен из типа значения, которое используется для инициализации этой переменной. Так что, возможно объявление переменной с указанием её начального значения и без указания типа, в таком случае, Go автоматически определит тип.
var a = 5 // Компилятор Go выведет тип переменной `a`
Как это работает? Если учесть, что константы в Go являются нетипизированными, какой тип будет присвоит переменной a? Вариантов не мало: int8, int16, int32, int64 и int.
Каждая нетипизированная константа в Go имеет свой тип по умолчанию. Типы по умолчанию используются когда значение константы присваивается переменной без явного указания типа.
В языке Go используются следующие типы констант по умолчанию:
Константа | Тип по умолчанию |
Целое число (12, 89) | int |
Число с плавающей точкой (2.71, 9.87) | float64 |
Комплексное число (2+4i) | complex128 |
Символ (‘а’, ‘®’) | rune |
Логические значение (true, false) | bool |
Строка («Привет») | string |
Итак, в примере выше, var a = 5, в виду отсутствия явного указания типа, для переменной a будет использован тип по умолчанию int.
Типизированные константы
Язык Go позволяет определять типизированные константы.
const typedInt int = 1 // Типизированная константа
Аналогично переменным, правила строгой типизации Go действуют и на типизированные константы.
var myFloat64 float64 = typedInt // Ошибка компилятора
Использование типизированных констант лишает гибкости в их использовании. Значение типизированных констант нельзя присваивать переменным с совместимым типом или использовать в математических выражениях совместно с константами других типов без явного приведения типов. Типизированные константы нужно использовать разумно. В большинстве случаев, достаточно нетипизированных констант.
Числовые выражения, включающие константы
Нетипизированные константы могут совместно использоваться в любых числовых выражениях. Это работает без каких-либо подводных камней.
package main import "fmt" func main() { var result = 7.5/5 fmt.Printf("Результат: %v имеет тип: %T\n", result2, result2) }
# Вывод Результат: 1.5 имеет тип: float64
В примере выше, 7.5 является нетипизированной константой с плавающей точной, а 5 нетипизированной целочисленной константой. Результат вычисления 1.5 является нетипизированной константой с плавающей точкой, типом по умолчанию для которой является float64. Соответственно, переменной result был присвоен тип float64.
Другой пример
package main import "fmt" func main() { var result = 25/2 fmt.Printf("Результат: %v имеет тип: %T\n", result2, result2) }
Каким будет результат выполнения программы?
Если взять обычный калькулятор и вычислить значение выражения по математическим правилам, получим 12.5.
Но в результате выполнения программы мы получим 12.
Так, как оба операнда 25 и 2 являются нетипизированными целочисленными константами, то и значение выражения было приведено к целому числу.
Корректного результата вычисления можно добиться одним из следующих способов.
// Использовать значение с плавающей точкой в числителе или знаменателе var result = 25.0/2
// Использовать явное преобразование типов числителя или знаменателя var result = float64(25)/2
Еще один пример
package main import "fmt" func main() { var result = 4.5 + (10 - 5) * (3 + 2)/2 fmt.Println(result) }
Каким будет результат в этот раз?
Ожидаемым результатом для большинства других языков и калькулятора будет 17. В случае с Go, результатом будет 16.5.
Для того, чтобы понять, как получился такой результат, рассмотрим этапы вычисления значения выражения.
4.5 + (10 - 5) * (3 + 2)/2 ↓ 4.5 + (5) * (3 + 2)/2 ↓ 4.5 + (5) * (5)/2 ↓ 4.5 + (25)/2 ↓ 4.5 + 12 ↓ 16.5
Для получения корректного результата, можно использовать уже знакомые приёмы.
// Использовать значение с плавающей точкой в числителе или знаменателе var result = 4.5 + (10 - 5) * (3 + 2)/2.0
// Использовать явное преобразование типов числителя или знаменателя var result = 4.5 + float64((10 - 5) * (3 + 2))/2
Заключение
Нетипизированные константы являются интересным архитектурным решением, принятым авторами Go. Не смотря на то, что в Go существует строгая система типов, нетипизированные константы добавляют гибкости в совместном использовании разных типов в любых выражениях.
Оригинал: Working with Constants in Golang.