Итерация по строке: правильный и неправильный способ
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)
}