Каково внутреннее устройство и особенности строк в Go?

Ответ

Строка в Go — это неизменяемая (immutable) последовательность байт. Обычно эти байты представляют собой текст в кодировке UTF-8.

Внутренне строка представляет собой небольшую структуру, содержащую два поля:

// Это лишь концептуальное представление, не реальный код
type stringStruct struct {
    str unsafe.Pointer // указатель на начало последовательности байт в памяти
    len int            // количество байт в строке
}

Ключевые особенности:

  1. Неизменяемость: Любая операция, которая «изменяет» строку (например, конкатенация), на самом деле создает новую строку в памяти.
  2. Длина в байтах: Встроенная функция len(s) возвращает количество байт, а не символов (рун). Для многобайтовых символов (например, кириллицы) это различие существенно.
  3. Итерация по рунам: Цикл for range по строке итерируется по рунам (Unicode code points), а не по байтам. Это правильный способ работать с символами в UTF-8.
  4. Нулевое значение: Нулевое значение для строки — это пустая строка "".

Пример:

s := "Привет"

// len() возвращает количество БАЙТ
fmt.Println(len(s)) // Вывод: 12 (каждая кириллическая буква в UTF-8 занимает 2 байта)

// Конвертация в срез рун для получения количества СИМВОЛОВ
fmt.Println(len([]rune(s))) // Вывод: 6

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

Ответ 18+ 🔞

Слушай, а вот тебе про строки в Go, чтобы ты не думал, что это какая-то простая хуйня. Это не просто текст, это, блядь, неизменяемая последовательность байтов, обычно в UTF-8. Представь себе, как будто ты вырезал буквы из стали — переделать нихуя не получится, только новый кусок отлить.

Внутри это выглядит как маленькая хитрая коробочка с двумя пиздюлинами:

// Это лишь концептуальное представление, не реальный код
type stringStruct struct {
    str unsafe.Pointer // указатель на начало последовательности байт в памяти
    len int            // количество байт в строке
}

А теперь главное, чтобы ты не обосрался с подводными камнями:

  1. Неизменяемость, ёпта! Если ты думаешь, что s += "!" меняет строку — хуй там! Она создаёт новую, старую выкидывает, и память ебёт почём зря. Мартышлюшка.
  2. Длина в байтах, а не в символах! len(s) — это не количество букв, а количество байтов. Для русского слова это, блядь, в два раза больше. Чистая ловушка для распиздяев.
  3. Итерация по рунам — это правильно. Цикл for range идёт по нормальным символам (рунам), а не по байтам. Это твой друг, запомни.
  4. Нулевое значение — это просто пустая строка "". Ничего сложного.

Смотри пример, чтобы не быть мудаком:

s := "Привет"

// len() возвращает количество БАЙТ
fmt.Println(len(s)) // Вывод: 12 (каждая кириллическая буква в UTF-8 занимает 2 байта)

// Конвертация в срез рун для получения количества СИМВОЛОВ
fmt.Println(len([]rune(s))) // Вывод: 6

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

Вот и вся магия. Главное — не путай байты с рунами, а то получишь ебанистический баг, который будешь искать полдня. В рот меня чих-пых!