Easy📖Теория7 min

Переменные и типы данных

Объявление переменных, базовые типы, нулевые значения, преобразование типов, константы и iota

Переменные и типы данных

Объявление переменных

Go предоставляет несколько способов объявления переменных. Каждый способ уместен в определённом контексте.

Полное объявление с var

package main

import "fmt"

func main() {
    // Explicit type, assigned later
    var name string
    name = "Go"

    // Explicit type with initialization
    var age int = 15

    // Type inferred from value
    var language = "Golang"

    // Multiple variables of the same type
    var x, y, z int
    x, y, z = 1, 2, 3

    // Multiple variables with different types (block form)
    var (
        host    string = "localhost"
        port    int    = 8080
        debug   bool   = false
        version        = "1.0.0" // type inferred as string
    )

    fmt.Println(name, age, language, x, y, z)
    fmt.Println(host, port, debug, version)
}

Краткое объявление :=

Оператор := объявляет и инициализирует переменную одновременно. Тип выводится автоматически.

func main() {
    // Short variable declaration (type inferred)
    name := "Go"
    count := 42
    pi := 3.14159
    isReady := true

    // Multiple short declarations
    a, b, c := 1, "hello", true

    // At least one variable must be NEW on the left side
    a, d := 10, 20 // 'a' is reassigned, 'd' is new — OK
    _ = d

    fmt.Println(name, count, pi, isReady, a, b, c)
}

Важно: Оператор := можно использовать только внутри функций. На уровне пакета допускается только var.

Сравнение способов объявления

Способ Где используется Пример
var x T Когда нулевое значение достаточно var count int
var x T = v Когда тип и значение явные var count int = 10
var x = v На уровне пакета с выводом типа var name = "Go"
x := v Внутри функций (самый частый) count := 10

Правила именования

package main

// Exported (public) — starts with uppercase
var MaxRetries = 3

// Unexported (private to package) — starts with lowercase
var defaultTimeout = 30

func main() {
    // Local variable — short, descriptive
    i := 0           // loop counter — short is fine
    user := getUser() // clear meaning
    db := openDB()    // common abbreviation

    // Avoid stuttering in package context
    // user.User — BAD
    // user.Profile — GOOD
    _ = i
    _ = user
    _ = db
}

Правила именования переменных в Go:

  • Начинается с буквы или _
  • Может содержать буквы, цифры, _
  • Регистрозависимые (name и Name — разные переменные)
  • Name (с заглавной) — экспортируемое имя (видно за пределами пакета)
  • name (со строчной) — неэкспортируемое имя

Базовые типы

Целочисленные типы

Тип Размер Диапазон
int8 8 бит -128 ... 127
int16 16 бит -32768 ... 32767
int32 32 бит -2,147,483,648 ... 2,147,483,647
int64 64 бит -9.2 * 10^18 ... 9.2 * 10^18
uint8 8 бит 0 ... 255
uint16 16 бит 0 ... 65535
uint32 32 бит 0 ... 4,294,967,295
uint64 64 бит 0 ... 18.4 * 10^18
int 32 или 64 бит Зависит от платформы
uint 32 или 64 бит Зависит от платформы
uintptr Размер указателя Для хранения адресов
package main

import (
    "fmt"
    "math"
)

func main() {
    // Platform-dependent int (64-bit on modern systems)
    var count int = 42

    // Fixed-size integers
    var small int8 = 127
    var medium int32 = math.MaxInt32
    var large int64 = math.MaxInt64

    // Unsigned integers
    var positive uint = 100
    var byte_ uint8 = 255

    // Integer literals in different bases
    decimal := 42
    binary := 0b101010    // Binary (Go 1.13+)
    octal := 0o52         // Octal with prefix (Go 1.13+)
    octalOld := 052       // Octal (old style)
    hex := 0x2A           // Hexadecimal

    // Underscore separator for readability (Go 1.13+)
    billion := 1_000_000_000
    hexColor := 0xFF_FF_FF

    fmt.Println(count, small, medium, large, positive, byte_)
    fmt.Println(decimal, binary, octal, octalOld, hex)
    fmt.Println(billion, hexColor)
}

Числа с плавающей точкой

Тип Размер Точность
float32 32 бит ~7 значащих цифр
float64 64 бит ~15 значащих цифр
complex64 64 бит float32 + float32
complex128 128 бит float64 + float64
func main() {
    // float64 is the default for floating point literals
    pi := 3.14159265358979
    var f32 float32 = 3.14

    // Scientific notation
    avogadro := 6.022e23
    planck := 6.626e-34

    // Complex numbers
    c1 := complex(3, 4)      // 3+4i
    c2 := 2 + 3i             // Literal syntax
    realPart := real(c1)      // 3
    imagPart := imag(c1)      // 4

    fmt.Println(pi, f32, avogadro, planck)
    fmt.Println(c1, c2, realPart, imagPart)
}

Совет: Всегда используйте float64, если нет веской причины использовать float32. Литералы с плавающей точкой в Go по умолчанию имеют тип float64.

Логический тип

func main() {
    var isReady bool         // Zero value: false
    isActive := true
    isEmpty := false

    // Boolean operations
    result := isActive && !isEmpty  // AND, NOT
    either := isActive || isEmpty   // OR

    // Comparison results are bool
    isEqual := 5 == 5        // true
    isGreater := 10 > 5      // true

    fmt.Println(isReady, isActive, result, either, isEqual, isGreater)
}

Строки и символы

package main

import (
    "fmt"
    "strings"
    "unicode/utf8"
)

func main() {
    // Strings are immutable sequences of bytes (UTF-8 encoded)
    greeting := "Hello, Мир!"
    raw := `This is a raw string literal.\n No escape sequences.`

    // String length
    byteLen := len(greeting)                    // Byte length (not rune count!)
    runeLen := utf8.RuneCountInString(greeting) // Character count

    fmt.Printf("Bytes: %d, Runes: %d\n", byteLen, runeLen)
    // Output: Bytes: 14, Runes: 11 (Cyrillic chars take 2 bytes)

    // Byte vs Rune
    var b byte = 'A'     // byte is alias for uint8
    var r rune = 'Я'     // rune is alias for int32 (Unicode code point)

    fmt.Printf("byte: %d (%c), rune: %d (%c)\n", b, b, r, r)

    // Iterating over strings
    for i, ch := range greeting {
        fmt.Printf("index=%d, rune=%c, unicode=%U\n", i, ch, ch)
    }

    // String building (efficient)
    var sb strings.Builder
    for i := 0; i < 100; i++ {
        sb.WriteString("Go ")
    }
    result := sb.String()
    _ = result
    _ = raw
}
Тип Размер Описание
byte 8 бит Алиас для uint8, один байт
rune 32 бит Алиас для int32, кодовая точка Unicode
string 16 байт (header) Неизменяемая последовательность байт

Нулевые значения (Zero Values)

В Go каждый тип имеет нулевое значение по умолчанию. Неинициализированные переменные всегда имеют определённое значение — нет понятия «мусора в памяти».

Тип Нулевое значение
int, int8...int64 0
uint, uint8...uint64 0
float32, float64 0.0
complex64, complex128 (0+0i)
bool false
string "" (пустая строка)
byte (uint8) 0
rune (int32) 0
Указатель (*T) nil
Слайс ([]T) nil
Карта (map[K]V) nil
Канал (chan T) nil
Интерфейс nil
Функция nil
Структура Все поля имеют свои нулевые значения
func main() {
    var (
        i    int
        f    float64
        b    bool
        s    string
        p    *int
        sl   []int
        m    map[string]int
        ch   chan int
        fn   func()
    )

    fmt.Printf("int: %d\n", i)         // 0
    fmt.Printf("float64: %f\n", f)     // 0.000000
    fmt.Printf("bool: %t\n", b)        // false
    fmt.Printf("string: %q\n", s)      // ""
    fmt.Printf("pointer: %v\n", p)     // <nil>
    fmt.Printf("slice: %v\n", sl)      // []
    fmt.Printf("map: %v\n", m)         // map[]
    fmt.Printf("channel: %v\n", ch)    // <nil>
    fmt.Printf("function: %v\n", fn)   // <nil>
}

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

Go не имеет неявного преобразования типов (implicit conversion). Все преобразования явные.

package main

import (
    "fmt"
    "strconv"
)

func main() {
    // Numeric conversions (explicit only!)
    var i int = 42
    var f float64 = float64(i)    // int -> float64
    var u uint = uint(f)          // float64 -> uint

    // This will NOT compile:
    // var f float64 = i  // ERROR: cannot use i (type int) as type float64

    // Precision loss is silent!
    bigFloat := 3.999
    truncated := int(bigFloat)    // 3 (truncated, NOT rounded)

    // int8 overflow (wraps around)
    big := 256
    small := int8(big)            // 0 (overflow!)

    fmt.Println(i, f, u, truncated, small)

    // String conversions
    numStr := strconv.Itoa(42)           // int -> string: "42"
    num, err := strconv.Atoi("42")       // string -> int: 42
    if err != nil {
        fmt.Println("conversion error:", err)
    }

    // float <-> string
    floatStr := strconv.FormatFloat(3.14, 'f', 2, 64)   // "3.14"
    floatVal, _ := strconv.ParseFloat("3.14", 64)        // 3.14

    // bool <-> string
    boolStr := strconv.FormatBool(true)   // "true"
    boolVal, _ := strconv.ParseBool("true") // true

    // String <-> []byte
    s := "Hello"
    bytes := []byte(s)     // String to byte slice
    s2 := string(bytes)    // Byte slice to string

    // String <-> []rune
    runes := []rune("Привет") // String to rune slice
    s3 := string(runes)       // Rune slice to string

    // int -> string: converts to Unicode character, NOT number!
    // string(65) == "A" (Unicode code point 65)
    // Use strconv.Itoa(65) for "65"

    fmt.Println(numStr, num, floatStr, floatVal, boolStr, boolVal)
    fmt.Println(s2, s3)
}

Ловушка: string(65) вернёт "A" (символ с кодом 65), а НЕ "65". Для числа в строку используйте strconv.Itoa() или fmt.Sprintf().

Псевдонимы типов и определения типов

package main

import "fmt"

// Type definition — creates a NEW type
type Celsius float64
type Fahrenheit float64
type UserID int64

// Type alias — same type, different name (Go 1.9+)
type Float = float64  // Float IS float64

func main() {
    var temp Celsius = 36.6
    var tempF Fahrenheit = 97.88

    // Cannot mix defined types without conversion!
    // temp = tempF  // ERROR: cannot use tempF (Fahrenheit) as Celsius

    // Explicit conversion required
    temp = Celsius(tempF)

    // Type alias works without conversion
    var f Float = 3.14
    var f64 float64 = f  // OK: Float IS float64
    _ = f64

    // Defined types can have methods
    fmt.Printf("Temperature: %.1f°C\n", temp)

    id := UserID(12345)
    fmt.Printf("User ID: %d\n", id)
}

// Methods on defined types
func (c Celsius) ToFahrenheit() Fahrenheit {
    return Fahrenheit(c*9/5 + 32)
}

func (f Fahrenheit) ToCelsius() Celsius {
    return Celsius((f - 32) * 5 / 9)
}
Механизм Синтаксис Новый тип? Методы?
Type definition type X T Да Да
Type alias type X = T Нет Нет (наследуются)

Константы

Объявление констант

package main

import "fmt"

// Simple constants
const Pi = 3.14159265358979
const E = 2.71828

// Block declaration
const (
    AppName    = "MyApp"
    AppVersion = "1.0.0"
    MaxRetries = 3
)

// Typed constants
const TypedPi float64 = 3.14

// Untyped constants — more flexible
const UntypedPi = 3.14  // Can be used where float32 or float64 is expected

func main() {
    // Untyped constant adapts to context
    var f32 float32 = UntypedPi  // OK: untyped adapts to float32
    var f64 float64 = UntypedPi  // OK: untyped adapts to float64
    // var f32typed float32 = TypedPi  // ERROR: TypedPi is float64

    fmt.Println(f32, f64)

    // Constants must be known at compile time
    // const x = someFunction() // ERROR: not a constant expression
}

Нетипизированные константы

Нетипизированные константы в Go обладают произвольной точностью и адаптируются к контексту:

const (
    // This number is too large for any integer type,
    // but works as an untyped constant
    Huge = 1 << 100

    // Can be used in expressions with other constants
    Half = Huge / 2
)

func main() {
    // Huge cannot be stored in any variable directly
    // var x int = Huge  // ERROR: overflows int

    // But it works in constant expressions
    fmt.Println(Huge / (1 << 90))  // 1024
}

iota — генератор последовательностей

iota — специальный идентификатор, который используется в блоках const для генерации последовательных значений:

package main

import "fmt"

// Simple enumeration (starts at 0)
const (
    Sunday    = iota  // 0
    Monday           // 1
    Tuesday          // 2
    Wednesday        // 3
    Thursday         // 4
    Friday           // 5
    Saturday         // 6
)

// Start from 1
const (
    January = iota + 1  // 1
    February             // 2
    March                // 3
)

// Bitmask pattern (powers of 2)
const (
    ReadPermission   = 1 << iota  // 1   (1 << 0)
    WritePermission               // 2   (1 << 1)
    ExecutePermission             // 4   (1 << 2)
)

// Skip values with blank identifier
const (
    _  = iota  // Skip 0
    KB = 1 << (10 * iota)  // 1 << 10 = 1024
    MB                      // 1 << 20 = 1,048,576
    GB                      // 1 << 30
    TB                      // 1 << 40
)

// Custom string representation
type Color int

const (
    Red   Color = iota  // 0
    Green                // 1
    Blue                 // 2
)

func (c Color) String() string {
    names := [...]string{"Red", "Green", "Blue"}
    if c < Red || c > Blue {
        return "Unknown"
    }
    return names[c]
}

func main() {
    fmt.Println(Sunday, Monday, Saturday)        // 0 1 6
    fmt.Println(January, February, March)        // 1 2 3
    fmt.Println(ReadPermission, WritePermission) // 1 2

    // Combining permissions with bitwise OR
    perm := ReadPermission | WritePermission
    fmt.Printf("Permissions: %03b\n", perm) // 011

    // Check permission with bitwise AND
    hasRead := perm&ReadPermission != 0
    fmt.Println("Has read:", hasRead) // true

    fmt.Println(KB, MB, GB) // 1024 1048576 1073741824

    fmt.Println(Red, Green, Blue) // Red Green Blue
}

Важно: iota сбрасывается в 0 в каждом новом блоке const. Внутри блока iota увеличивается на 1 для каждой строки (даже если строка пустая или содержит комментарий).

Перечисления (Enums) в Go

Go не имеет встроенных enum, но iota с определённым типом создаёт аналогичный паттерн:

type Status int

const (
    StatusPending  Status = iota  // 0
    StatusActive                   // 1
    StatusInactive                 // 2
    StatusDeleted                  // 3
)

// Validate checks if status is valid
func (s Status) Validate() bool {
    return s >= StatusPending && s <= StatusDeleted
}

// IsActive checks if status means active
func (s Status) IsActive() bool {
    return s == StatusActive
}

Проверь себя

🧪

Какое значение будет у `iota` в третьей строке блока `const`?

🧪

Что вернёт `string(65)` в Go?

🧪

Чем отличается `type Celsius float64` от `type Celsius = float64`?

🧪

Где можно использовать оператор краткого объявления `:=`?

🧪

Какое нулевое значение имеет переменная типа `string`?