Итерация по строке: правильный и неправильный способ


1. ❌ Неправильно: итерация по индексу

incorrect := "Го"

// ❌ НЕПРАВИЛЬНО для Unicode
for i := 0; i < len(incorrect); i++ {
    fmt.Printf("[%d] = %c\n", i, incorrect[i])
}

Вывод:

[0] = Ð
[1] = �
[2] = Ð
[3] = �

Проблема: incorrect[i] возвращает байт, не символ.


2. Детальный разбор проблемы

incorrect := "Го"

fmt.Printf("Строка: %s\n", incorrect)
fmt.Println("Итерация по байтам:")

for i := 0; i < len(incorrect); i++ {
    fmt.Printf("[%d] = %d ('%c')\n", i, incorrect[i], incorrect[i])
}

Вывод:

Строка: Го
Итерация по байтам:
[0] = 208 ('Ð') — НЕКОРРЕКТНО!
[1] = 147 ('�') — НЕКОРРЕКТНО!
[2] = 208 ('Ð') — НЕКОРРЕКТНО!
[3] = 190 ('�') — НЕКОРРЕКТНО!

Объяснение:

  • "Г" → байты [208, 147]
  • "о" → байты [208, 190]
  • Итерация проходит 4 раза (по байтам), не 2 (по символам)

3. ✅ Правильно: for range

correct := "Го"

// ✅ ПРАВИЛЬНО для Unicode
for index, runeValue := range correct {
    fmt.Printf("[%d] = %c (код: %d)\n", index, runeValue, runeValue)
}

Вывод:

[0] = Г (код: 1043)
[2] = о (код: 1086)

Механизм: for range итерирует по рунам, не байтам.


4. Важная особенность индексов в for range

text := "Го"

for index, runeValue := range text {
    fmt.Printf("index=%d, символ=%c\n", index, runeValue)
}

Вывод:

index=0, символ=Г
index=2, символ=о

Обратите внимание: Индексы 0, 2, а не 0, 1.


5. Почему индексы не последовательные?

index в for range — это байтовый offset, не номер символа:

Строка: "Го"
Байты:  [208, 147, 208, 190]
         ↑         ↑
      index=0   index=2

Символ 'Г' начинается с байта 0
Символ 'о' начинается с байта 2

Вывод: index указывает на начало символа в байтах.


6. Сравнение методов итерации

Метод Что возвращает Индексы Применение
for i := 0; i < len(s); i++ Байты 0, 1, 2, 3… ❌ Некорректно для Unicode
for i, r := range s Руны Байтовые offset ✅ Правильно для текста

7. Примеры для разных строк

ASCII (работают оба способа)

ascii := "Go"

// Способ 1: по индексу (OK для ASCII)
for i := 0; i < len(ascii); i++ {
    fmt.Printf("%c ", ascii[i])  // G o
}

// Способ 2: for range (тоже OK)
for _, r := range ascii {
    fmt.Printf("%c ", r)  // G o
}

ASCII: 1 символ = 1 байт → оба способа работают.


Кириллица (только for range)

cyrillic := "Го"

// ❌ Неправильно
for i := 0; i < len(cyrillic); i++ {
    fmt.Printf("%c ", cyrillic[i])  // Ð � Ð �
}

// ✅ Правильно
for _, r := range cyrillic {
    fmt.Printf("%c ", r)  // Г о
}

Эмодзи (только for range)

emoji := "😀🎉"

// ❌ Неправильно (8 байт)
for i := 0; i < len(emoji); i++ {
    fmt.Printf("%c ", emoji[i])  // Мусор
}

// ✅ Правильно (2 символа)
for _, r := range emoji {
    fmt.Printf("%c ", r)  // 😀 🎉
}

8. Получение номера символа (не байта)

text := "Привет"

charIndex := 0
for byteIndex, r := range text {
    fmt.Printf("Символ %d (байт %d): %c\n", charIndex, byteIndex, r)
    charIndex++
}

Вывод:

Символ 0 (байт 0): П
Символ 1 (байт 2): р
Символ 2 (байт 4): и
Символ 3 (байт 6): в
Символ 4 (байт 8): е
Символ 5 (байт 10): т

Нужен отдельный счётчик для порядкового номера символа.


9. Практические сценарии

Подсчёт символов

// ❌ Неправильно
count := len(text)  // Байты!

// ✅ Правильно
count := 0
for range text {
    count++
}

// ✅ Или
count = utf8.RuneCountInString(text)

Поиск символа

text := "Привет"
target := 'в'

// ✅ Правильно
for i, r := range text {
    if r == target {
        fmt.Printf("Найден на позиции %d\n", i)  // байтовый offset
        break
    }
}

Вывод с номерами

text := "Го"

fmt.Println("❌ Неправильно:")
for i := 0; i < len(text); i++ {
    fmt.Printf("%d: %c\n", i, text[i])
}
// 0: Ð
// 1: �
// 2: Ð
// 3: �

fmt.Println("\n✅ Правильно:")
for i, r := range text {
    fmt.Printf("%d: %c\n", i, r)
}
// 0: Г
// 2: о (индекс — байтовый offset!)

10. Итоги

for range итерирует по рунам (символам)
for i := 0; i < len(s) итерирует по байтам
✅ Для Unicode строк используйте только for range
⚠️ index в for range — это байтовый offset, не номер символа
✅ Для ASCII обе формы работают (1 символ = 1 байт)
for _, r := range s — если индекс не нужен
✅ Отдельный счётчик для порядкового номера символа

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

// ❌ НЕ делайте так с Unicode
for i := 0; i < len(s); i++ {
    ch := s[i]  // байт, не символ!
}

// ✅ Делайте так
for _, r := range s {
    // r — полноценный Unicode-символ (rune)
}

Индексы в for range:

"Го" → байты [208, 147, 208, 190]

for i, r := range "Го":
  i=0, r='Г'  (символ начинается с байта 0)
  i=2, r='о'  (символ начинается с байта 2)

Практическая рекомендация:

// Безопасная итерация по любым строкам
for _, r := range text {
    fmt.Printf("%c", r)
}