Какие есть способы итерирования по строке в 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 символами.

Ответ 18+ 🔞

Давай разберём эту дичь, чтобы не наступить на грабли размером с лопату. Строки в Go — это не просто набор букв, это, блядь, закодированная последовательность байтов в UTF-8. И если с ними работать как попало, можно получить пиздец вместо текста.

1. Правильный способ: итерация по рунам (символам) через range

Это как раз тот случай, когда нужно работать с символами, а не с сырыми байтами. Оператор range — твой лучший друг, он сам разберёт UTF-8 и выдаст тебе руны.

s := "Привет"

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

Видишь? Индексы скачут через 2 байта, потому что кириллица в UTF-8 занимает по два байта на символ. range всё это за тебя обрабатывает, красота.

2. Способ для самоубийц: итерация по байтам через обычный for

А вот если взять и пройтись по строке как по массиву байтов, то тут начинается цирк. Подходит только для чистого ASCII или бинарных данных. Для русского текста — это пиздец и разбитые символы.

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

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

Смотри, что выведется: 0xd0 0x9f — это два отдельных байта, которые вместе составляют одну букву 'П'. Если их по отдельности выводить как символы, получится какая-то хуйня из крякозябр.

Главные подводные ебли, о которых нужно помнить:

  • len(s) возвращает количество байтов, а не символов. Для строки "Привет" len вернёт 12, а не 6. Овердохуища, да?
  • Чтобы получить реальное количество символов (рун), есть два пути:
    • utf8.RuneCountInString(s) — эффективно, не создавая лишних срезов.
    • len([]rune(s)) — проще, но создаёт новый слайс рун. Для коротких строк — похуй, для длинных — может быть накладно.
  • Никогда, блядь, не обращайся к символу строки по индексу как s[i]. Это даст тебе байт, а не руну. С не-ASCII символами это гарантированно сломается. Хочешь символ по индексу — конвертируй строку в []rune и работай уже с ним.

Короче, запомни: для текста — только range. Для байтовых плясок — обычный цикл, но тогда ты сам в ответе за то, что натворишь.