1C-Code → Основы 1С → Коллекции: где хранить много значений
Массив, соответствие, структура и таблица значений — четыре инструмента и правило, как выбрать нужный.
Глава 10 из 36 · чтение примерно 30 минут
Переменная хранит одно значение. Но данные почти всегда идут пачками: строки документа, список товаров, цены по номенклатуре.
Для этого есть коллекции. Их в 1С четыре основных, и выбор между ними — не дело вкуса: каждая отвечает на свой вопрос быстро, а на чужой — медленно и криво.
| Вопрос, на который отвечают данные | Что брать |
|---|---|
| «Просто список, по нему пройдут по порядку» | Массив |
| «Найти значение по ключу: цену по товару» | Соответствие |
| «Несколько именованных полей одного объекта» | Структура |
| «Строки и колонки: сгруппировать, сложить, отсортировать» | ТаблицаЗначений |
Упорядоченный список. Элементы идут по порядку, у каждого есть номер — индекс.
Товары = Новый Массив;
Товары.Добавить("Гвозди");
Товары.Добавить("Шурупы");
Товары[0]; // "Гвозди" — индексы с НУЛЯ
Товары.Количество(); // 2
Товары.ВГраница(); // 1 — номер последнего
Товары.Найти("Шурупы"); // 1, а если нет — Неопределено
Индексы с нуля, и за границу выходить нельзя
Первый элемент — Массив[0], а последний — Массив[Массив.Количество() - 1]. Единицу вычесть обязательно.
Обращение за границу — ошибка с прекращением работы, а не пустое значение. Поэтому для обхода почти всегда берут Для Каждого: там выйти за границу невозможно в принципе.
А Найти при неудаче возвращает Неопределено, а не −1 и не 0. Проверять надо через <> Неопределено.
Упражнение 1. Первый массив
Создайте массив с именем Товары и добавьте в него три строки: Гвозди, Шурупы, Молоток. Проверка смотрит на количество элементов.
Решается в тренажёре: код запускается и проверяется сразу.
Пары «ключ → значение». Позволяет мгновенно достать значение по ключу, не перебирая всё подряд.
Цены = Новый Соответствие;
Цены.Вставить("Гвозди", 150);
Цены.Вставить("Шурупы", 80);
Цены["Гвозди"]; // 150
Цены["Молоток"]; // Неопределено — ключа нет
Цены.Количество(); // 2
Соответствие — это ответ на «запрос в цикле»
Помните правило из главы про циклы: к базе обращаются до цикла, а не внутри? Соответствие — то, куда складывают результат.
Один запрос достаёт цены всех нужных товаров сразу. Их раскладывают в соответствие «товар → цена». Дальше цикл берёт цену из памяти за один шаг, вместо запроса к базе на каждой строке.
Именно поэтому соответствие — самая важная коллекция для производительности.
Нет ключа — Неопределено, и оно взорвётся позже
Цены["Молоток"] при отсутствующем ключе вернёт Неопределено. Не ноль, не ошибку.
Проблема в том, что падение случится не здесь, а строкой ниже: Сумма = Количество * Цена; — умножение на Неопределено прекращает работу, и в сообщении будет указана эта строка, а не настоящая причина.
Поэтому значение из соответствия либо проверяют, либо сразу подставляют умолчание:
Цена = Цены[Товар]; Если Цена = Неопределено Тогда Цена = 0; КонецЕсли;
Упражнение 2. Цена с умолчанием
Напишите функцию ЦенаТовара(Цены, Товар): вернуть цену из соответствия, а если такого ключа нет — 0. Проверка спрашивает цену Молотка, которого в списке нет, и ждёт 0.
Решается в тренажёре: код запускается и проверяется сразу.
В цикле Для Каждого по соответствию переменная получает не значение, а пару. У пары есть .Ключ и .Значение.
Для Каждого Пара Из Цены Цикл
Сообщить(Пара.Ключ + ": " + Строка(Пара.Значение));
КонецЦикла;
| Часть | Что означает |
|---|---|
Пара | не значение и не ключ, а пара целиком |
.Ключ | то, по чему ищут — например, товар |
.Значение | то, что нашли — например, цена |
порядок | на порядок обхода соответствия не стоит рассчитывать: нужен порядок — берите массив или таблицу |
Упражнение 3. Общая стоимость склада
Напишите ИтогоПоСкладу(Остатки): сложить все значения соответствия «товар → сумма». Проверка передаёт три позиции на 150, 80 и 200 и ждёт 430.
Решается в тренажёре: код запускается и проверяется сразу.
Набор именованных полей одного объекта. Имена полей известны заранее, когда пишется код.
Позиция = Новый Структура;
Позиция.Вставить("Товар", "Гвозди");
Позиция.Вставить("Количество", 10);
// или короче, одной строкой:
Позиция = Новый Структура("Товар, Количество", "Гвозди", 10);
Позиция.Товар; // "Гвозди" — обращение через точку
Позиция.Количество; // 10
Структура или соответствие — в чём разница
Обе хранят пары «имя → значение». Разница в том, известны ли имена заранее.
Структура — известны. Вы пишете Позиция.Товар прямо в коде, и полей обычно немного. Имя поля обязано быть допустимым именем переменной: без пробелов, не с цифры.
Соответствие — неизвестны. Ключи приходят из данных: товары, склады, даты. Их может быть тысяча, и ключом может быть что угодно — хоть ссылка, хоть дата.
Практическое следствие: «цены по товарам» — всегда соответствие. «Результат проверки: успех и текст ошибки» — всегда структура.
Обращение к несуществующему полю падает
Позиция.Цена, если поля Цена нет, — ошибка «Поле объекта не обнаружено». В отличие от соответствия, которое молча вернёт Неопределено.
Когда структура приходит извне и состав полей не гарантирован, используют Свойство:
Значение = 0;
Если Параметры.Свойство("Цена", Значение) Тогда
// поле есть, и оно уже лежит в переменной Значение
КонецЕсли;Метод возвращает Истину или Ложь, а само значение кладёт во вторую переменную.
Упражнение 4. Вернуть два значения сразу
Функция может вернуть только одно значение — но структура считается одним. Соберите структуру с именем Результат и двумя полями: Успех = Истина и Текст = Готово.
Решается в тренажёре: код запускается и проверяется сразу.
Строки и колонки, как в таблице. Умеет сама сортировать, группировать и считать итоги.
Т = Новый ТаблицаЗначений;
Т.Колонки.Добавить("Товар");
Т.Колонки.Добавить("Сумма");
СтрокаФайла = Т.Добавить(); // добавили пустую строку
СтрокаФайла.Товар = "Гвозди";
СтрокаФайла.Сумма = 1500;
Т.Итог("Сумма"); // сумма по всей колонке
Т.Сортировать("Сумма Убыв");
Т.Свернуть("Товар", "Сумма"); // сгруппировать и сложить
Свернуть — группировка в одну строку
Т.Свернуть("Товар", "Сумма") схлопывает строки с одинаковым товаром в одну, складывая суммы. Первый аргумент — по чему группировать, второй — что суммировать.
Это делает за одну строку то, на что вручную ушёл бы цикл с соответствием.
Обратите внимание: Свернуть меняет саму таблицу, а не возвращает новую. Как и Сортировать. Здесь коллекции ведут себя не так, как строки, — их методы меняют объект на месте.
Упражнение 5. Таблица и итог
Создайте таблицу Т с колонками Товар и Сумма, добавьте две строки на 1500 и 800. Проверка смотрит на итог по колонке Сумма — ожидается 2300.
Решается в тренажёре: код запускается и проверяется сразу.
Это свойство, из-за которого случаются самые загадочные ошибки.
Когда вы передаёте массив в функцию, копии не создаётся. Функция получает тот же самый массив. Добавит она туда элемент — он появится и у вызывающего.
С числом или строкой так не бывает: они копируются.
✓ Число — копия
Процедура Испортить(Ч)
Ч = 999;
КонецПроцедуры
Число = 5;
Испортить(Число);
// Число по-прежнему 5Функция работала со своей копией. Снаружи ничего не изменилось.
✕ Массив — тот же объект
Процедура Испортить(М)
М.Добавить(999);
КонецПроцедуры
Массив = Новый Массив;
Испортить(Массив);
// в Массиве теперь есть 999Функция изменила не копию, а сам массив вызывающего.
Передача по ссылке — рабочий приём
На передаче по ссылке держится половина типового кода: процедура заполняет переданную таблицу, дополняет структуру параметров, добавляет сообщения в общий список.
Помнить надо одно: если вы приняли коллекцию и меняете её, вы меняете чужие данные. Когда это не входило в замысел, делайте копию — Скопировать() у таблицы значений, или собирайте новый массив вместо правки исходного.
Упражнение 6. Только положительные
Напишите ТолькоПоложительные(Массив): собрать новый массив из чисел больше нуля и вернуть его количество. Исходный массив не менять. Проверка передаёт 5, −3, 0, 8 и ждёт 2.
Решается в тренажёре: код запускается и проверяется сразу.
Четыре коллекции выше — это то, что есть в языке. Но в рабочем коде постоянно встречаются ещё две конструкции, и без них чужой код не прочитать: список значений и массив структур.
Список, у каждого элемента которого есть значение, подпись для человека и галочка. Единственная коллекция, которую умеет показывать форма.
Склады = Новый СписокЗначений;
Склады.Добавить("Основной", "Основной склад");
Склады.Добавить("Резерв", "Резерв цеха");
Склады[0].Значение; // "Основной" — что внутри
Склады[0].Представление; // "Основной склад" — что видит человек
Склады[0].Пометка = Истина; // галочка
Склады.НайтиПоЗначению("Резерв"); // элемент или Неопределено
Склады.СортироватьПоПредставлению();
Зачем отдельный тип, если есть массив
У массива есть только значения. А форме нужно три вещи сразу: что выбрали (значение), что написать в строке (представление) и отмечено ли (пометка).
Отсюда и разделение. Значение — для программы: ссылка на склад, код, перечисление. Представление — для человека: текст, который он прочитает. Смешивать их — типичная ошибка: в список кладут красивые строки, а потом не могут по ним найти сам объект.
И ещё одно: Добавить у списка значений возвращает добавленный элемент, а не индекс. Поэтому и пишут в одну строку: Склады.Добавить(Склад, "Основной").Пометка = Истина;
Упражнение 7. Отметить нужное
В списке три склада. Пометьте Резерв и Брак — искать их надо по значению, а не по номеру, — и посчитайте помеченные в переменную Отмечено.
Решается в тренажёре: код запускается и проверяется сразу.
Самая частая коллекция в типовом коде — не таблица значений, а массив структур. Каждая структура — строка, поля структуры — колонки.
Причина не в красоте. Таблицу значений нельзя передать с сервера на клиент в управляемой форме: это несериализуемый тип. А массив структур передаётся спокойно — поэтому именно им возвращают результаты серверных вызовов.
Строки = Новый Массив;
Строки.Добавить(Новый Структура("Товар, Количество, Цена", "Гвозди", 10, 150));
Строки.Добавить(Новый Структура("Товар, Количество, Цена", "Шурупы", 3, 80));
Для Каждого Строка Из Строки Цикл
Сообщить(Строка.Товар + ": " + Строка.Количество * Строка.Цена);
КонецЦикла;
| Часть | Что означает |
|---|---|
Новый Структура("Товар, Количество, Цена", …) | имена полей одной строкой через запятую, значения — следом по порядку |
Строки.Добавить(…) | структура кладётся в массив целиком, копии не делается |
Строка.Товар | обращение по имени поля — как к колонке таблицы |
Строка.Количество * Строка.Цена | считаем прямо в обходе: коллекция в памяти, база не трогается |
Упражнение 8. Сумма по массиву структур
Напишите СуммаСтрок(Строки): каждый элемент — структура с полями Товар, Количество, Цена. Верните сумму «количество × цена» по всем строкам. Проверка передаёт 10×150 и 3×80 и ждёт 1740.
Решается в тренажёре: код запускается и проверяется сразу.
Имя поля структуры — не любая строка
Поле структуры называется по правилам имени переменной: буквы, цифры и подчёркивание, и не с цифры. Пробелов, точек и дефисов быть не может.
Об это спотыкаются, когда имена берут из данных: из колонок загружаемого файла, из названий складов, из кодов. Новый Структура("Склад основной", 5) — ошибка прямо при создании.
Правило простое: имена полей вы придумываете сами, когда пишете код. Если ключи приходят из данных — это соответствие, а не структура. Там ключом может быть что угодно: хоть строка с пробелами, хоть ссылка на справочник.
Задача звучит безобидно: пройти по массиву и выкинуть лишнее. Написанная в лоб, она ломается — и ломается не всегда, а через раз, в зависимости от данных. Такие ошибки живут в коде годами.
Причина в том, что Удалить сдвигает все следующие элементы на позицию назад. Счётчик при этом идёт вперёд как ни в чём не бывало: один элемент оказывается перепрыгнут, а в конце счётчик уходит за границу — той самой ошибкой с прекращением работы.
✓ Так — пропустит и упадёт
Для Сч = 0 По Массив.ВГраница() Цикл
Если Массив[Сч] = 0 Тогда
Массив.Удалить(Сч);
КонецЕсли;
КонецЦикла;Верхняя граница посчитана ОДИН раз, до первого удаления. Массив стал короче, а цикл об этом не знает.
✕ Так — с конца, и всё цело
Сч = Массив.ВГраница();
Пока Сч >= 0 Цикл
Если Массив[Сч] = 0 Тогда
Массив.Удалить(Сч);
КонецЕсли;
Сч = Сч - 1;
КонецЦикла;Идём справа налево: удаление сдвигает только то, что уже пройдено. Ничего не пропущено, за границу не вышли.
Обратного Для в 1С нет
В большинстве языков у цикла есть шаг: «от последнего к первому, шаг минус один». В 1С шага нет. Для Сч = 5 По 0 Цикл — не ошибка: он просто не выполнится ни разу, молча.
Поэтому обход с конца пишут через Пока и уменьшают счётчик руками. Это единственный способ, и выглядит он именно так, как в примере справа.
Второй способ обойтись без этого вовсе: не удалять, а собирать новый массив из того, что нужно оставить. Читается лучше, и ломаться там нечему — как в упражнении «Только положительные» ниже.
Упражнение 9. Выкинуть нули
Напишите УбратьНули(Массив): удалите из самого массива все нули и верните, сколько элементов осталось. Проверка передаёт 5, 0, 3, 0, 0 и ждёт 2. Обход в лоб здесь падает — идите с конца.
Решается в тренажёре: код запускается и проверяется сразу.
Коллекции вкладываются друг в друга, и на этом держится приём, который приходится писать регулярно: разложить плоский список по группам. Строки по складам, документы по контрагентам, ошибки по причинам.
Устройство: соответствие, где ключ — группа, а значение — массив попавшего в неё. Единственная тонкость в том, что массив для новой группы надо создать, а для уже встреченной — взять существующий.
ПоСкладу = Новый Соответствие;
Для Каждого Строка Из Строки Цикл
Список = ПоСкладу[Строка.Склад];
Если Список = Неопределено Тогда
Список = Новый Массив;
ПоСкладу.Вставить(Строка.Склад, Список);
КонецЕсли;
Список.Добавить(Строка.Товар);
КонецЦикла;
| Часть | Что означает |
|---|---|
ПоСкладу[Строка.Склад] | берём массив группы; ключа ещё нет — придёт Неопределено |
Список = Новый Массив | первая строка этого склада: заводим массив |
ПоСкладу.Вставить(…, Список) | кладём массив в соответствие ОДИН раз — дальше он уже там |
Список.Добавить(Строка.Товар) | работает и для нового, и для найденного: массив — один и тот же объект, он лежит в соответствии по ссылке |
Почему не надо класть массив обратно
Строчки ПоСкладу.Вставить(Склад, Список); в конце нет — и это не забывчивость. Коллекции хранятся по ссылке: в соответствии лежит не копия массива, а он сам.
Добавили элемент в Список — он появился и в том, что лежит в соответствии, потому что это одно и то же. Та самая передача по ссылке из конца главы, только здесь она работает на нас.
Упражнение 10. Разложить по складам
Напишите ПоСкладам(Строки): каждый элемент — структура с полями Склад и Товар. Разложите товары по складам и верните, сколько товаров на складе «Основной». Проверка передаёт четыре строки, три из них — на Основном.
Решается в тренажёре: код запускается и проверяется сразу.
Скопировать копирует не всё
Скопировать() у таблицы значений и Новый Массив(Исходный) делают копию верхнего уровня. Сам массив новый, а элементы в нём — те же самые объекты.
Для чисел и строк разницы нет: их значения и так копируются. Но если в массиве лежали структуры, копия массива ссылается на те же структуры — правка поля видна в обоих.
Когда нужна копия целиком, структуры создают заново в обходе. Единственная альтернатива — ЗначениеВСтрокуВнутр и обратно, но так делают редко: медленно и подходит не всем типам.
Фиксированные коллекции: почему Добавить падает
Рано или поздно вы получите ошибку «Метод объекта не обнаружен (Добавить)» на массиве, который выглядит как обычный. Это ФиксированныйМассив — коллекция, которую нельзя менять.
Сами вы такие почти не создаёте, а получаете: параметры сеанса, часть свойств метаданных, дополнительные свойства объектов — всё это приходит фиксированным, чтобы один код не испортил данные другому.
Что делать: сделать изменяемую копию. Новый Массив(Фиксированный) и Новый Структура(Фиксированная) дают обычные коллекции с теми же значениями. Обратно — тем же способом: Новый ФиксированныйМассив(Массив).
Отдельная причина, по которой массив встречается чаще остальных коллекций: его понимает язык запросов. Это штатный способ спросить у базы «вот про эти двадцать товаров, все сразу».
Запрос.Текст =
"ВЫБРАТЬ Ссылка, Цена ИЗ Справочник.Номенклатура
|ГДЕ Ссылка В (&СписокТоваров)";
Запрос.УстановитьПараметр("СписокТоваров", Товары);
| Часть | Что означает |
|---|---|
В (&СписокТоваров) | условие «входит в список» — в параметр кладут массив |
Товары | обычный массив ссылок; список значений и таблица значений тоже подойдут |
одно обращение | вместо запроса в цикле на каждый товар — то самое правило из главы про циклы |
Пустой массив в параметре
Ссылка В (&Список) с пустым массивом не находит ничего — и это правильно, но неожиданно: код, который «работал», однажды тихо возвращает пустой результат, потому что список наверху оказался пуст.
Поэтому перед запросом почти всегда стоит Если Товары.Количество() = 0 Тогда Возврат; КонецЕсли;. Это не перестраховка: это разница между «ничего не нашли» и «нечего было искать».
| Действие | Массив | Соответствие | ТаблицаЗначений |
|---|---|---|---|
| создать | Новый Массив | Новый Соответствие | Новый ТаблицаЗначений |
| добавить | М.Добавить(З) | С.Вставить(К, З) | СтрокаФайла = Т.Добавить() |
| получить | М[0] | С[Ключ] | Т[0].Колонка |
| сколько | М.Количество() | С.Количество() | Т.Количество() |
| убрать | М.Удалить(Индекс) | С.Удалить(Ключ) | Т.Удалить(Стр) |
| если нет | выход за границу — ошибка | Неопределено | НайтиСтроки даёт пустой массив |
| обойти | Для Каждого Э Из М | Для Каждого Пара Из С | Для Каждого СтрокаФайла Из Т |
Коллекции — последняя тема «чистого языка». Дальше начинается сама 1С: справочники и документы, чтение и запись данных, программная работа с формами.
Читать про код и писать код — разные умения. В тренажёре к этой главе идут упражнения: вы пишете решение, оно запускается на настоящем интерпретаторе 1С и проверяется сразу, без установки платформы.
← Циклы: повторить для каждого
→ Справочники и документы: чтение и запись