Какие есть способы итерирования по строке в Go и в чем их разница?

Ответ

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

1. Итерация по рунам (символам) с помощью range

Этот способ является предпочтительным при работе с текстом. Оператор range автоматически декодирует UTF-8 последовательности и на каждой итерации возвращает индекс начала руны в байтах и саму руну (тип rune, который является псевдонимом для int32).

s := "Привет"

// i - индекс начала руны в байтах
// r - сама руна (символ)
for i, r := range s {
    // Привет -> П(2 байта) р(2) и(2) в(2) е(2) т(2)
    fmt.Printf("Индекс байта: %d, Руна: %cn", i, r)
}
// Вывод:
// Индекс байта: 0, Руна: П
// Индекс байта: 2, Руна: р
// Индекс байта: 4, Руна: и
// ...

2. Итерация по байтам с помощью стандартного цикла for

Этот способ итерируется по каждому байту строки. Он подходит для работы с ASCII-строками или бинарными данными, представленными в виде строки, но некорректен для многобайтовых символов (кириллица, эмодзи и т.д.).

s := "Hi Привет"

for i := 0; i < len(s); i++ {
    // s[i] возвращает i-й байт строки
    fmt.Printf("%#x ", s[i])
}
// Вывод будет содержать байты каждого символа:
// 0x48 0x69 0x20 0xd0 0x9f 0xd1 0x80 ...

Что важно помнить:

  • len(s) возвращает количество байтов, а не символов (рун).
  • Для получения количества символов (рун) используйте utf8.RuneCountInString(s) или, если нужна дальнейшая работа с рунами, len([]rune(s)).
  • Прямое обращение к символу по индексу s[i] дает байт, а не руну, что может привести к ошибкам с не-ASCII символами.