Работа с массивами в 1С:Предприятие кажется простой только на первый взгляд. Начинающие разработчики часто сталкиваются с ситуацией, когда при изменении значения в одной «ячейке» двумерного массива данные магическим образом меняются во всех строках сразу. Это происходит из-за непонимания механизмов работы системы с объектами и ссылками.
В этой статье мы подробно разберем, почему возникает такая «чудесная» система, как правильно инициализировать многомерные структуры и какие альтернативы существуют для решения прикладных задач.
Рассмотрим ситуацию, описанную автором темы. Программист пытается создать структуру «массив массивов», используя следующий код:
Для i=1 По ВсегоСтрок Цикл
ДанныеИзЭкселевскогоФайлаЯчейка.Добавить(i);
КонецЦикла;
Для i=1 По ВсегоКолонок Цикл
ДанныеИзЭкселевскогоФайла.Добавить(ДанныеИзЭкселевскогоФайлаЯчейка);
КонецЦикла;
Проанализируем, что здесь происходит на уровне памяти. В 1С объект Массив является ссылочным типом данных. Когда вы создаете переменную ДанныеИзЭкселевскогоФайлаЯчейка, система выделяет область памяти для одного объекта. В первом цикле вы заполняете этот единственный объект данными.
Во втором цикле вы вызываете метод Добавить() для основного массива. Однако вместо того, чтобы создать копию данных, вы добавляете ссылку на один и тот же объект ДанныеИзЭкселевскогоФайлаЯчейка несколько раз. В итоге ваш основной массив превращается в список указателей, которые все ведут к одной и той же области памяти. Именно поэтому при изменении ДанныеИзЭкселевскогоФайла[0][0] вы видите изменения во всех остальных «строках» — физически это одна и та же строка.
Если вам заранее известна размерность массива, в 1С предусмотрен штатный механизм создания истинно многомерных массивов через конструктор. Это самый простой и эффективный способ избежать ошибок с дублированием ссылок.
Рассмотрим пример создания двумерного массива размером 10 на 5 (10 строк, 5 колонок):
// Создание прямоугольного массива
ДвумерныйМассив = Новый Массив(10, 5);
// Обращение к элементам (индексация начинается с 0)
ДвумерныйМассив[0][0] = "Значение 1";
ДвумерныйМассив[9][4] = "Значение 50";
В этом случае система самостоятельно инициализирует структуру, где каждый элемент доступен по двум индексам, и они независимы друг от друга. Вы можете указать до 8 измерений в конструкторе: Новый Массив(Размер1, Размер2, ..., Размер8).
Часто на практике количество «колонок» в каждой строке может быть разным или структура должна строиться динамически. В этом случае мы используем вложенные массивы (так называемые зубчатые массивы — jagged arrays). Чтобы избежать проблемы, описанной в начале статьи, необходимо на каждой итерации цикла создавать новый экземпляр вложенного массива.
Посмотрим на правильный алгоритм реализации:
// Основной контейнер
ДанныеИзФайла = Новый Массив;
Для НомерСтроки = 1 По ВсегоСтрок Цикл
// КРИТИЧЕСКИ ВАЖНО: Создаем НОВЫЙ объект массива для каждой строки
НоваяСтрока = Новый Массив;
Для НомерКолонки = 1 По ВсегоКолонок Цикл
НоваяСтрока.Добавить("Данные " + Строка(НомерСтроки) + ":" + Строка(НомерКолонки));
КонецЦикла;
// Добавляем уникальный объект в основной массив
ДанныеИзФайла.Добавить(НоваяСтрока);
КонецЦикла;
Теперь ДанныеИзФайла[0] и ДанныеИзФайла[1] указывают на разные объекты в памяти. Изменение одного не затронет другой.
В процессе обсуждения на форуме часто звучит вопрос: «Зачем использовать двумерный массив, если есть ТаблицаЗначений?». Выясним причины выбора того или иного инструмента:
ТаблицаЗначений — это объект, доступный только на сервере (или в толстом клиенте). Если вам нужно передать двумерную матрицу с сервера на клиент в тонком клиенте, массив — ваш единственный выбор.ТаблицаЗначений нужно определять колонки, типы данных. В массиве вы можете хранить любые типы вперемешку без предварительного описания схемы.Однако помните: у многомерных массивов, созданных через Новый Массив(X, Y), нет встроенных методов Найти() или Сортировать(). Эти методы работают только для одномерных структур. Для поиска в двумерном массиве вам придется писать вложенные циклы самостоятельно.
Если ваша задача связана с интеграцией (например, передача данных в Excel или запуск скриптов на других языках через COM), обычный массив 1С может не подойти. В таких случаях используется COMSafeArray.
Проанализируем пример создания такого массива:
// Создаем описание типа для элементов
ОписаниеТиповСтрока = Новый ОписаниеТипов("Строка");
// Создаем безопасный массив для передачи во внешнюю систему
// Параметры: Тип элементов, Размер1, Размер2
КомМассив = Новый COMSafeArray(ОписаниеТиповСтрока, 10, 10);
КомМассив.Установить(0, 0, "Данные для Excel");
Главное отличие COMSafeArray в том, что он строго типизирован и имеет фиксированный размер, который нельзя изменить после создания методом Добавить(). Это «честный» массив в понимании классического программирования (как в C++ или VB).
Рассмотрим несколько важных нюансов, которые помогут избежать ошибок в будущем:
Структура) или ДеревоЗначений.Массив потребляет память не только под данные, но и под служебную информацию. В таких случаях «массив массивов» может быть менее выгоден, чем один плоский массив, где индекс рассчитывается формулой Индекс = (НомерСтроки * ВсегоКолонок) + НомерКолонки.Выясним причину, по которой листинг 2.4 из старых учебников казался неверным. В учебниках часто приводится пример добавления одного и того же объекта для краткости кода, подразумевая, что читатель понимает разницу между ссылкой и значением. На практике же всегда следуйте правилу: один вызов Новый Массив — один независимый объект.
Таким образом, для создания двумерного динамического массива мы рекомендуем либо использовать прямой конструктор с двумя параметрами (если матрица прямоугольная), либо создавать новый массив внутри цикла (если структура сложная или динамическая).