← Назад к вопросам

Что получается при итерации по строке?

2.3 Middle🔥 141 комментариев
#Основы Go

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

При итерации по строке в Go вы получаете не отдельные символы (руны), а байты — значения типа byte (который является псевдонимом для uint8). Это прямое следствие того, что строка в Go по своей сути является неизменяемым срезом байтов ([]byte), представляющим последовательность байтов в кодировке UTF-8.

Рассмотрим ключевые аспекты подробно.

1. Базовое поведение: итерация по байтам

При использовании цикла for...range по умолчанию вы перебираете байты строки, что может привести к неожиданным результатам для строк, содержащих не-ASCII символы (например, русские буквы, эмодзи и т.д.).

package main

import "fmt"

func main() {
    s := "Привет"
    
    // Итерация по строке через for i := 0; i < len(s); i++
    // Получаем доступ к байтам по индексу
    fmt.Println("Байты строки:")
    for i := 0; i < len(s); i++ {
        fmt.Printf("Индекс %d: байт = %v, символ = %q\n", i, s[i], s[i])
    }
    
    // Итерация через for range БЕЗ индексов - также даст руны
    fmt.Println("\nИтерация через for range (получаем руны):")
    for _, r := range s {
        fmt.Printf("Руна: %q (кодовая точка: %U)\n", r, r)
    }
}

Вывод (сокращённо) покажет, что строка "Привет" занимает 12 байтов (по 2 байта на каждую русскую букву в UTF-8), а не 6, как может ожидаться.

2. Правильная работа с Unicode: итерация по рунам

Для корректной обработки символов Юникода (включая многобайтовые) необходимо использовать цикл for range, который специально адаптирован для строк. В этом случае на каждой итерации возвращается кодовая точка Unicode (руна) типа rune (псевдоним для int32) и её стартовая позиция в байтах.

package main

import "fmt"

func main() {
    s := "Hello, 世界! 🌍"
    
    fmt.Println("Итерация по рунам через for range:")
    for index, r := range s {
        fmt.Printf("Позиция в байтах: %d, руна: %q (Unicode: U+%04X)\n", index, r, r)
    }
    
    // Демонстрация разницы в длине
    fmt.Printf("\nДлина в байтах: %d\n", len(s))       // 19 байтов
    fmt.Printf("Количество рун: %d\n", len([]rune(s))) // 11 символов
}

3. Ключевые различия между подходами

Аспектfor i := 0; i < len(s); i++for index, r := range s
Возвращаемое значениеБайт (byte/uint8)Руна (rune/int32)
Обработка UTF-8Не учитывает; перебирает все байты подрядКорректно декодирует многобайтовые символы
ИндексПозиция байтаПозиция первого байта руны в строке
ПроизводительностьO(n) по байтамO(n) по рунам (декодирование на лету)
ПрименениеРабота с бинарными данными, ASCIIТекстовая обработка, интернационализация

4. Важные практические следствия

Строка — это не массив символов

s := "Go"
// s[0] возвращает байт 71 ('G' в ASCII), НЕ символ "G" как объект

Невозможность изменения строки через индекс

s := "cat"
// s[0] = 'b' // Ошибка компиляции: строки неизменяемы

Конвертация для разных целей

s := "Golang"
// Для работы с байтами:
bytes := []byte(s) // Конвертация в срез байтов
// Для работы с рунами:
runes := []rune(s) // Конвертация в срез рун
// Обратно в строку:
sFromBytes := string(bytes)
sFromRunes := string(runes)

5. Пример обработки сложных случаев

package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    // Строка с символами разной байтовой длины
    s := "é́́" // Составной символ
    
    fmt.Println("Анализ строки:", s)
    fmt.Println("Длина в байтах:", len(s)) // 7 байтов
    
    // Способ 1: range - корректно обрабатывает
    fmt.Println("\nЧерез for range:")
    for i, r := range s {
        fmt.Printf("Позиция %d: %q\n", i, r)
    }
    
    // Способ 2: Ручное декодирование
    fmt.Println("\nРучное декодирование:")
    for i := 0; i < len(s); {
        r, size := utf8.DecodeRuneInString(s[i:])
        fmt.Printf("Позиция %d: %q (размер %d байт)\n", i, r, size)
        i += size
    }
}

6. Рекомендации по использованию

  1. Для текстовой обработки всегда используйте for range, если вам нужны символы.
  2. Для бинарных данных или ASCII можно использовать прямой перебор по индексам.
  3. Помните о неизменяемости строк — для модификаций конвертируйте в []byte или []rune.
  4. Используйте пакет unicode/utf8 для низкоуровневой работы с UTF-8.
  5. Учитывайте, что len() возвращает количество байтов, а не символов.

Таким образом, итерация по строке в Go — это операция, требующая понимания внутреннего представления строк как последовательностей UTF-8 байтов. Выбор метода итерации напрямую зависит от того, работаете ли вы с текстом как с последовательностью символов или как с потоком байтов.

Что получается при итерации по строке? | PrepBro