Доступ к элементам строки и руны


1. Доступ по индексу: s[i] возвращает байт

word := "Go"

fmt.Printf("word[0] = %d\n", word[0])  // 71 (байт)
fmt.Printf("word[0] = %c\n", word[0])  // G (символ)

Важно: s[i] возвращает тип byte (псевдоним uint8), не символ.


2. Проблема с многобайтовыми символами

ASCII работает корректно

word := "Go"
firstByte := word[0]  // Тип: byte

fmt.Printf("%c\n", firstByte)  // G — корректно

1 символ = 1 байт → всё работает.


Кириллица НЕ работает

russian := "Го"

fmt.Printf("russian[0] = %d\n", russian[0])  // 208
fmt.Printf("russian[0] = %c\n", russian[0])  // Ð — некорректно!

Проблема: Символ "Г" занимает 2 байта в UTF-8.


3. Почему s[0] не работает с кириллицей

Разбор на байты

s := "Г"
bytes := []byte(s)

fmt.Println(bytes)  // [208 147]

UTF-8 представление "Г":

Символ: Г
Байты:  [0xD0, 0x93] = [208, 147]

russian[0] возвращает только первый байт (208), не весь символ.


4. Решение: использование рун

Преобразование в []rune

russian := "Го"
ruRunes := []rune(russian)

fmt.Printf("Первая руна: %c\n", ruRunes[0])  // Г — корректно!
fmt.Printf("Вторая руна: %c\n", ruRunes[1])  // о

[]rune корректно работает с любыми Unicode-символами.


5. Что такое rune

type rune = int32  // Псевдоним (алиас) для int32

Руна — это Unicode code point, представляющий один символ.

Объявление руны

char := 'А'  // Одинарные кавычки — тип rune

fmt.Printf("char = %c\n", char)       // А
fmt.Printf("Тип: %T\n", char)         // int32
fmt.Printf("Код: %d\n", char)         // 1040 (Unicode)
fmt.Printf("Hex: %U\n", char)         // U+0410

Одинарные кавычки '...' создают руну, не строку.


6. Сравнение byte vs rune

Тип Псевдоним Размер Применение
byte uint8 1 байт ASCII, байты
rune int32 4 байта Unicode символы

7. Примеры с разными символами

ASCII

s := "Go"

// Способ 1: по байтам (работает)
fmt.Printf("%c\n", s[0])  // G

// Способ 2: через руны (избыточно, но работает)
runes := []rune(s)
fmt.Printf("%c\n", runes[0])  // G

Кириллица

s := "Го"

// ❌ НЕ работает
fmt.Printf("%c\n", s[0])  // Ð (мусор)

// ✅ Работает
runes := []rune(s)
fmt.Printf("%c\n", runes[0])  // Г

Эмодзи

s := "😀"

// ❌ НЕ работает
fmt.Printf("%c\n", s[0])  // � (мусор, 1 из 4 байт)

// ✅ Работает
runes := []rune(s)
fmt.Printf("%c\n", runes[0])  // 😀

8. Итерация по строке

❌ По байтам (некорректно для Unicode)

s := "Привет"

for i := 0; i < len(s); i++ {
    fmt.Printf("%c ", s[i])  // Мусор!
}

Выведет байты, не символы.


✅ По рунам через for range

s := "Привет"

for _, r := range s {
    fmt.Printf("%c ", r)  // П р и в е т
}

for range автоматически итерирует по рунам.


✅ Через []rune

s := "Привет"
runes := []rune(s)

for i := 0; i < len(runes); i++ {
    fmt.Printf("%c ", runes[i])  // П р и в е т
}

9. Получение конкретного символа

Неправильно (по байтам)

s := "Привет"
char := s[0]  // byte, не символ!

fmt.Printf("%c\n", char)  // Ð (первый байт 'П')

Правильно (через руны)

s := "Привет"
runes := []rune(s)
char := runes[0]  // rune

fmt.Printf("%c\n", char)  // П

10. Типы при обращении

word := "Go"
russian := "Го"

// По умолчанию: byte
b := word[0]      // Тип: byte (uint8)
fmt.Printf("Тип: %T\n", b)  // uint8

// Явное преобразование в rune
r := rune(word[0])
fmt.Printf("Тип: %T\n", r)  // int32

// Для многобайтовых — ТОЛЬКО через []rune
runes := []rune(russian)
r2 := runes[0]
fmt.Printf("Тип: %T\n", r2)  // int32

Важно: Присваивание char := s[0] даёт тип byte, не rune.


11. Практические рекомендации

Задача Способ
ASCII строки s[i] допустимо
Unicode строки []rune(s)[i]
Итерация for _, r := range s
Получение одного символа runes := []rune(s); runes[i]
Работа с байтами (сеть, файлы) []byte(s)[i]

12. Итоги

s[i] возвращает байт (тип byte), не символ
✅ Для ASCII s[i] работает корректно
❌ Для Unicode (кириллица, эмодзи) s[i] даёт первый байт, не символ
rune — алиас для int32, представляет Unicode code point
[]rune(s) — правильное преобразование для работы с символами
for range s итерирует по рунам, не байтам
Одинарные кавычки 'A' создают rune, двойные "A"string
⚠️ Будьте внимательны: byte vs rune — разные типы

Ключевое правило:

s[i]           → byte (первый байт, может быть некорректным для Unicode)
[]rune(s)[i]   → rune (правильный Unicode-символ)
for range s    → итерация по рунам

Практический паттерн:

// ❌ Неправильно для Unicode
for i := 0; i < len(s); i++ {
    fmt.Printf("%c", s[i])  // Мусор для кириллицы
}

// ✅ Правильно
for _, r := range s {
    fmt.Printf("%c", r)  // Работает для всех символов
}