Слайсы, мапы, строки
Слайсы, мапы и строки — это три коллекции, без которых не обходится почти ни одна программа на Go. Они выглядят как простые встроенные типы, но за их удобным синтаксисом скрывается устройство, которое и спрашивают на junior-секции: слайс — это не массив, а лёгкое окно над общим backing array; map — хеш-таблица с особой семантикой чтения и записи; а string — неизменяемая последовательность byte, а не «строка символов».
За этим удобством прячутся отличия, на которых ловят новичков. append к слайсу иногда переиспользует тот же массив, а иногда выделяет новый — и от этого зависит, увидят ли соседние срезы изменение. Выражение среза s[low:high] не копирует данные, а делит память с исходником (алиасинг). Чтение отсутствующего ключа map возвращает нулевое значение, а не панику; запись в nil map, наоборот, паникует. Обход map через range нарочно идёт в случайном порядке. А индексация строки отдаёт byte, а не символ. Тема разбирает каждую коллекцию по слоям — от устройства слайса до неизменяемости строк.
Карта темы
- Основы слайсов — слайс как окно над backing array (
ptr,len,cap); создание литералом илиmake([]T, len, cap); индексация,len,cap,append(может перевыделить память),copy;nil-слайс тоже можноappend. - Выражения среза —
s[low:high]и трёхиндексноеs[low:high:max]; срез делит backing array с исходником (алиасинг!); как считаютсяlenиcapрезультата; повторное взятие среза. - Основы мап —
make(map[K]V), записьm[k]=v, чтениеm[k], форма comma-okv, ok := m[k],delete(m, k); чтение отсутствующего ключа возвращает нулевое значение, запись вnilmap паникует. - Порядок обхода мапы —
rangeпоmapобходит ключи в случайном порядке (специально, свой на каждый запуск); никогда не полагайтесь на порядок; чтобы получить отсортированный вывод, соберите ключи и отсортируйте черезsort. - Основы строк — конкатенация через
+; индексацияs[i]даётbyte;len(s)считает байты, а не символы; конвертации[]byte(s)/string(b)/[]rune(s). - Неизменяемость строк —
stringдоступен только на чтение; присвоить байт по индексу нельзя (ошибка компиляции); любое «изменение» — это новая строка через копию[]byte.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
| Считать слайс копией данных | Слайс — это окно над общим массивом; правка через один срез видна через другой, если они делят backing array |
Думать, что append всегда возвращает тот же слайс | При нехватке cap append выделяет новый массив; старый слайс продолжает видеть прежние данные |
Игнорировать результат append (append(s, x) без присваивания) | Новый len/ptr теряется; почти всегда нужно s = append(s, x) |
Резать s[1:4] в расчёте на копию | Срез делит память с исходником; правка результата меняет и оригинал |
Считать m[k] для отсутствующего ключа ошибкой | Возвращается нулевое значение типа; отличить «нет ключа» от «ноль» можно только через v, ok := m[k] |
Писать в nil map (var m map[K]V; m[k]=v) | Паника во время выполнения; nil map можно только читать — для записи нужен make |
Полагаться на порядок ключей при range по map | Порядок рандомизирован и свой на каждый запуск; для стабильного вывода нужно сортировать ключи |
Считать s[i] символом, а len(s) — числом символов | s[i] это byte, а len(s) — число байтов; для не-ASCII символ занимает несколько байт |
Значение для собеседований
Коллекции — это junior-фильтр на «понимает, как Go устроен внутри» против «знает только синтаксис». Слайсы, мапы и строки спрашивают почти на каждом собеседовании, и обычно именно через их неочевидную семантику.
Что обычно проверяют:
- Чем слайс отличается от массива и из чего состоит его заголовок (
ptr,len,cap). - Когда
appendперевыделяет память и почему результат всегда нужно присваивать. - Что выражение среза делит память с исходником и как трёхиндексная форма ограничивает
cap. - Что возвращает чтение отсутствующего ключа
mapи почему нужна форма comma-ok. - Почему обход
mapидёт в случайном порядке и как получить отсортированный вывод. - Почему
stringнеизменяема и чтоs[i]отдаётbyte, а не символ.