Константы в golang

Константы

В языке 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.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *