Ответ
В 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. Для байтовых плясок — обычный цикл, но тогда ты сам в ответе за то, что натворишь.