23 вопросов
Строка - неизменяемая последовательность байт. Внутри это структура с указателем на массив байт и длиной (length). Строка не обязана быть валидным UTF-8; для текста в UTF-8 итерация по рунам делается через for _, r := range s.
Неизменяемые. После создания нельзя изменить байты строки. Операции вроде конкатенации или среза создают новую строку. Поэтому частые конкатенации в цикле лучше делать через strings.Builder.
Когда нужно много раз добавлять части к строке (например, в цикле). Builder накапливает байты в буфере и выдаёт строку через String() без лишних копирований. Эффективнее, чем конкатенация через + или += в цикле.
Каждый s + x создаёт новую строку и копирует данные. В цикле это O(n^2) по времени и лишние аллокации. Для однократной конкатенации двух строк + нормален; для накопления результата лучше strings.Builder.
s += part каждый раз создаёт новую строку: старую копируют, добавляют part, старую отбрасывают. Итоговая сложность квадратичная, много аллокаций. Используйте strings.Builder и WriteString для накопления.
Количество рун (кодовых точек): utf8.RuneCountInString(s). len(s) даёт число байт. Для ASCII строки оба совпадают; для UTF-8 с многобайтовыми символами len больше.
len(s) возвращает длину в байтах, не количество символов. Для UTF-8 количество символов: utf8.RuneCountInString(s) или len([]rune(s)) (последний аллоцирует срез рун).
for _, r := range s даёт руны (кодовые точки) и корректно обрабатывает многобайтовые UTF-8 символы. Индекс в for i, r := range s - смещение в байтах начала руны. По индексу байта s[i] даёт байт (uint8).
Строки неизменяемы, поэтому "на месте" нельзя. Обычно: преобразовать в []rune, изменить элемент, обратно string(runes). Либо собрать новую строку через strings.Builder или срезы: s[:i] + newChar + s[i+width:] (для байтов нужен размер руны).
[]byte(s) и string(b) - преобразования между строкой и срезом байт. Под капотом при копировании данных может использоваться копирование; не изменяйте полученный []byte, если строка должна остаться неизменной (не гарантируется копия при оптимизациях в будущем). Для явной копии: b := append([]byte(nil), s...).
Когда нужен произвольный доступ по индексу символа (руны) или изменение "символов". []rune(s) разбирает UTF-8 и создаёт срез кодовых точек; аллокация и копирование. Для итерации достаточно for _, r := range s без среза рун.
strconv.Atoi(s) - строка в int; strconv.ParseInt(s, 10, 64) - с основанием и размером; strconv.Itoa(i) - int в строку; ParseFloat, FormatBool и др. Все возвращают ошибку при неверном формате.
Go хранит строки в UTF-8. Кириллица - многобайтовые руны. Используйте for _, r := range s для обхода по символам; len(s) даст байты. Для среза "по символам" переведите в []rune, срежьте, обратно string(). Функции strings, utf8 работают с UTF-8.
Пустая строка "" - это структура строки (указатель + длина). Размер заголовка строки фиксирован (обычно 16 байт на 64-битной платформе). Данных нет, указатель может указывать на пустой или общий буфер; не аллоцируется отдельная память под "пустые" данные.
Функции для поиска и замены: Contains, HasPrefix, Split, Join, Replace, Trim, Fields; сравнение Compare; Builder для накопления; Reader для чтения строки как io.Reader. Работа идёт с байтами; для UTF-8 с символами - utf8, runes.
Да. Исходный код в UTF-8; строки по умолчанию - последовательности байт в UTF-8. Литералы рун и строк в UTF-8. range по строке даёт руны (кодовые точки). Пакеты unicode, utf8 для проверки и разбора UTF-8.
По индексу байта: s[i] - один байт (тип byte). Для i-й руны (символа) нужно итерацию или срез: []rune(s)[i] - но это аллоцирует срез; либо перебор for i, r := range s и счётчик. Для одной руны по индексу байта: r, _ := utf8.DecodeRuneInString(s[offset:]).
strings.Builder предназначен для построения строки (метод String() возвращает строку, без копирования при повторном вызове в Go 1.15+). bytes.Buffer - для накопления байт; подходит и для строк, и для бинарных данных, реализует больше интерфейсов (io.Reader, io.Writer и т.д.). Для только строк предпочтителен Builder.
strings.Reader реализует io.Reader, io.ReaderAt, io.Seeker и др., читая из строки. Удобно, когда API ожидает io.Reader, а данные уже в строке. Создание: strings.NewReader(s).
Когда нужно отформатировать строку по шаблону: подстановка чисел, отступы, несколько значений. Пример: fmt.Sprintf("id=%d", id). Для простой конкатенации часто достаточно + или strconv. Для накопления в цикле - strings.Builder и fmt.Fprintf(builder, ...).
Спецификация не гарантирует интернирование. Компилятор и рантайм могут объединять одинаковые строковые литералы. Не стоит полагаться на то, что s1 == s2 по указателю; для сравнения содержимого всегда используйте == строк или strings.Compare.
Операторы == и != сравнивают строки побайтово. Строки сравнимы (comparable). Для лексикографического порядка: strings.Compare(a, b) возвращает -1, 0 или 1. Регистронезависимое сравнение: strings.EqualFold(a, b).
Строка неизменяема и удобна как ключ map, аргумент функций. []byte изменяем и подходит для парсинга, протоколов, повторной перезаписи. Преобразование string(b) и []byte(s) может копировать данные. Для чтения без изменения часто используют string; для обработки на месте - []byte.