Перейти к содержимому
Теория · Строки · урок 1 из 8

Строки и байты

Чему вы научитесь

Чему вы научитесь:

  • объяснять, почему len("привет") возвращает 12, а не 6;
  • отличать байт от руны и перебирать строку по символам;
  • собирать новую строку из частей, раз старую изменить нельзя;
  • искать, разбивать и склеивать строки функциями пакета strings.
Две фишки с буквами G и o, как в настольной игре со словами, и на каждой её код, 71 и 111

Представьте ряд фишек с буквами, как в настольной игре со словами. Каждая фишка — один байт: маленькое число от 0 до 255, а буква на ней — лишь способ это число показать. Строка в Go устроена как такой ряд: это байты, в которых записан текст. Латинская буква, цифра или пробел занимают одну фишку, а буква кириллицы — две.

Из этой картинки следуют два сюрприза. Строку можно индексировать, как срез: s[0] — её первый элемент. Но результат выглядит неожиданно:

  • первый элемент строки "Go" печатается числом;
  • длина строки "Привет" вдвое больше числа букв в ней.
GO
package main

import "fmt"

func main() {
    s := "Go"
    fmt.Println(len(s), s[0])  // 2 71
    fmt.Println(len("Привет")) // 12
}

Давайте разберём вывод:

  1. len(s) возвращает 2: в "Go" две латинские буквы, по фишке на каждую.
  2. s[0] возвращает 71 — число с первой фишки.
  3. len("Привет") возвращает 12: в слове шесть букв по две фишки.

Строка из байтов

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

Функция len возвращает число байтов, а не букв: она считает фишки. Поэтому в строке "Привет" шесть букв и двенадцать байтов.

Этот вопрос часто задают на собеседовании: «Что вернёт len("привет")?» Правильный ответ — 12, потому что len у строки всегда считает байты.

Индекс возвращает байт

Индекс s[i] возвращает один байт — значение типа byte:

  • тип byte — другое имя типа uint8, целое число от 0 до 255;
  • поэтому fmt.Println печатает не букву G, а её код 71;
  • с буквой байт сравнивают через одинарные кавычки: s[0] == 'G' даёт true.

Нажимайте кнопку и читайте строку "Go Привет" по байту, как s[i]: следите, сколько байтов занимают латинская буква, пробел и буква кириллицы.

ВживуюСтрока: байты UTF-8len(s) = 15 · символов: 9

Строка хранит байты UTF-8. Нажмите кнопку, чтобы читать её по одному байту, как это делает s[i].

Ячейки — байты в шестнадцатеричной записи

Границы индекса такие же, как у среза:

  • индексы идут от 0 до len(s) - 1;
  • индекс за пределами строки вызывает панику;
  • у пустой строки "" длина 0, и паникует любой индекс.

Строку нельзя изменить

Строки в Go неизменяемы: байты созданной строки больше не меняются. Здесь картинка с фишками перестаёт работать: настоящую фишку можно снять и заменить, а байт строки — нельзя.

Что можно и чего нельзя:

  • присвоить новое значение элементу s[i] не получится;
  • переменной можно присвоить другую строку целиком;
  • новую строку собирают оператором + из частей старой.

Новая строка из частей

GO
s := "Go"
t := "g" + s[1:]
fmt.Println(t, s) // go Go

Давайте разберём пример по строкам:

  1. s := "Go" создаёт строку из двух байтов.
  2. t := "g" + s[1:] собирает новую строку. Выражение s[1:] берёт часть строки по тем же правилам, что и срез среза, и возвращает строку. Байты с индекса 1 до конца попадают в t вместе с буквой "g".
  3. fmt.Println(t, s) печатает go Go. Сама s не изменилась: оператор + создаёт новую строку из байтов обеих частей.

Границы в s[1:] тоже считаются в байтах, а не в буквах. Для "Go" это одно и то же. А "Привет"[1:] начинается с середины буквы П: такой срез режет букву пополам, и программа напечатает на её месте непонятный знак.

Копирование и сравнение

Две операции работают со строкой целиком:

  • присваивание t := s не копирует байты: обе переменные видят один и тот же текст, и ни одна не может его испортить;
  • оператор == сравнивает две строки: он сравнивает их байты по порядку.

Частая ошибка

Первую букву строки заменяют присваиванием по индексу:

GO
s := "hello"
s[0] = 'H' // ✗
TEXT
cannot assign to s[0] (neither addressable nor a map index expression)

Компилятор отклоняет присваивание, потому что строка неизменяема, и программа не собирается. Новую строку собирают из частей: s = "H" + s[1:].

Главное

  • Строка — ряд байтов в кодировке UTF-8; len считает байты, поэтому len("привет") равно 12.
  • s[i] возвращает один байт типа byte: число от 0 до 255, а не букву.
  • Срез строки s[1:] тоже режет по байтам и может разрезать букву пополам.
  • Строку изменить нельзя: s[0] = 'H' не компилируется, а новую строку собирают из частей через +.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом