9 структур данных, которые вам понадобятся
Еще в девяностые профессор Корейского университета передовых технологий Сонгчун Мун предложил Биллу Гейтсу назвать свой стартап Microdata, а не Microsoft. Мун указал на то, что данные и их структура — будущее программирования.
Структуры данных — способы хранения и извлечения информации. Правильный выбор структуры поможет эффективнее выполнить задачу. СД важны в разработке ПО, от них зависит, как будут работать алгоритмы.
Рассказываем о структурах данных, которые используются чаще всего.
#1. Массив (Array)
Массив — простая базовая структура. Стеки, очереди и списки — производные от массивов. Единице данных в массиве присваивается число или индекс, который указывает на ее расположение. Чтобы найти ячейку с информацией в массиве, нужно добавить к базовому элементу ее индекс. Базовый элемент, как правило, обозначается именем самого массива.
Представьте себе записную книжку со страницами, пронумерованными от 1 до 10. Каждая из них может содержать информацию или быть пустой. Блокнот — массив страниц, страницы — элементы массива «блокнот». Программно вы извлекаете информацию со страницы, обращаясь к ее индексу, то есть «блокнот+4» будет ссылаться на содержимое четвертой страницы.
Массив — это фиксированная структура, хранящая элементы одного типа в непрерывных ячейках памяти. Есть исключение — гетерогенные массивы, которые могут хранить данные разных типов. Массивы бывают одномерными и многомерными (массивы в массивах). Их размеры фиксированы, поэтому в уже созданный массив нельзя просто вставить новый элемент. Нужно скопировать старый массив и создать новый, увеличив размер.
#2. Матрица (Matrix)
Матрица — двумерный массив, выглядящий как список столбцов и строк, на пересечении которых находятся элементы данных. Это прямоугольный массив, в котором количество строк и столбцов задает его размер. В математике их используют для компактной записи линейных алгебраических или дифференциальных уравнений.
Матрицы используют для описания вероятностей. Например, для ранжирования страниц в поиске Google при помощи алгоритма PageRank. В компьютерной графике — для работы с 3D-моделями и проецирования их на двумерный экран.
#3. Связный список (Linked list)
Списки схожи с массивами, но отличаются более гибкой структурой. Они выглядят как цепочки нод или узлов, где каждая нода содержит ссылку на следующую. Доступ к элементам в связном списке осуществляется последовательно, в отличие от массивов с произвольным доступом. Списки бывают односвязными и двусвязными.
Начальный элемент этой структуры называется головой, а все последующие узлы цепочки — хвостом. Хвост состоит из элементов двух типов: с информацией (info) и с указанием на следующий узел (next). Конец цепочки обозначается как null.
#4. Стек (Stack)
Это вертикальный столбец с блоками, доступ к которым можно получить только с одного конца: сверху или снизу. Как в стопке книг — чтобы добраться до нижней, нужно сначала убрать все книги сверху.
Новые элементы стека заменяют старые. Принцип работы такой структуры — LIFO (last in — first out, «последним пришел — первым ушел»). Поэтому стек еще называют магазином — по аналогии с огнестрельным оружием: выстрелит патрон, который был заряжен последним.
Эта структура данных реализована в функции «отменить» (undo). Программа сохраняет статус работы так, что последнее действие становится первым в очереди на отмену. В стеке возможны всего три операции: добавление элемента (push), удаление (pop), чтение (peek).
Стек может быть реализован в виде связного списка или одномерного массива. В первом случае, каждый элемент содержит ссылку на следующий, во втором — упорядочен индексом.
Существует похожая СД — дек (deque — double ended queue, «двусторонняя очередь»). Это стек с двусторонним доступом.
#5. Очередь (Queue)
Этот тип СД напоминает стеки, но принцип работы реализован как FIFO (first in — first out, «первым пришел — первым ушел»). Как в супермаркете: первым покупки унесет домой тот, кто раньше всех займет очередь.
Очереди используются, когда ресурс нужно распределить между несколькими потребителями (работа ЦП, пропускная способность роутера). Или когда данные передаются асинхронно, то есть скорости приема и отдачи — разные.
В этой СД можно выполнить две операции: добавление элемента в конец очереди (enqueue) и удаление первого элемента (dequeue). Очереди бывают в виде связных списков или массивов, по аналогии со стеками.
#6. Дерево (Tree)
Деревья — структура, в которой данные связаны между собой узлами, и при этом расположены иерархически. Различают двоичное дерево поиска, расширенное, черно-красное и еще десяток видов.
Как и у настоящего дерева, тут есть корни, ветви и листья. Самый верхний узел в этой СД, не имеющий предков, называется корневым. Остальные узлы — потомками или дочерними элементами. Дочерние узлы с одним и тем же родителем — это узлы-братья. А листья — это узлы, не имеющие потомков.
Деревья используют, например, в разработке видеоигр. Они позволяют разделить пространство и быстро находить объекты. Так, дерево с четырьмя дочерними узлами (quadtree) — квадрант — используется для создания карты и ориентации по четырем сторонам света в игре.
Но деревья сложно хранить и у них невысокая скорость работы.
курсы по теме:
Data Science with Python
Структура данных
Структура данных в программировании — это объект-контейнер, в котором хранится информация. Данные внутри контейнера структурированы по особой системе — она различается в зависимости от вида структуры. Так разработчики оптимизируют доступ к информации.
Освойте профессию «Data Scientist»
Структуру данных можно представить как переменную, в которой хранится не одно значение, а несколько. Через эту переменную можно по определенному алгоритму получить доступ к каждому из значений — или почти к каждому. Какие типы могут быть у этих переменных, как именно они будут структурированы — зависит от конкретной сущности.
Основными структурами данных пользуются практически все разработчики. Такие структуры есть почти во всех современных языках программирования, просто могут называться по-разному.
Профессия / 24 месяца
Data Scientist
Дата-сайентисты решают поистине амбициозные задачи. Научитесь создавать искусственный интеллект, обучать нейронные сети, менять мир и при этом хорошо зарабатывать. Программа рассчитана на новичков и плавно введет вас в Data Science.
Для чего нужны структуры данных
Современные программы могут работать с большим количеством информации, причем иногда довольно разнообразной. Было бы сложно выделять для каждой единицы таких данных отдельную переменную: это лишняя трата памяти, к тому же в обилии переменных тогда было бы слишком легко запутаться.
Поэтому разработчики используют структуры данных — единые объекты, которые хранят в себе набор информации. Правила доступа к этой информации могут быть разными — для этого есть специальные операции, порой специфичные для конкретного структурного типа. Каждая структура подходит для своих сценариев использования: есть задачи, в которых ее будет удобно использовать, а есть те, где это излишне.
Пользоваться базовыми структурами данных должен уметь каждый разработчик. Они упрощают написание кода и делают возможным решение задач, которые очень сложно выполнить без структурированных данных.
Характеристики структуры данных
Описание структуры данных можно условно поделить на интерфейс и реализацию. Интерфейс — это набор операций, который описывает поведение конкретной структуры. То есть это то, что можно сделать с конкретной структурой данных. А реализация — то, как данные структурированы внутри.
Кроме того, типичная структура данных отвечает нескольким критериям:
- корректность, то есть грамотная реализация своего технического описания;
- пространственная сложность — реализация должна занимать минимум места;
- временная сложность — операции должны выполняться за минимум времени.
«Минимум места» и «минимум времени» могут различаться для разных структур. Это нормально: у каждой из них свое назначение и оптимальный способ использования.
Какими бывают структуры данных
Структуры данных в общем можно разделить на линейные и нелинейные. Разница в том, как они хранят свои элементы — данные, которые в них расположены.
- В линейных структурах элементы расположены как бы «по цепочке», друг за другом. Они выстраиваются в последовательность. Например, слово можно представить как линейную структуру данных, где данные — буквы.
- В нелинейных элементы могут ветвиться, образовывать таблицы или схемы. Пример нелинейной структуры данных из жизни — схема дорог.
Ниже мы коротко расскажем о наиболее популярных структурах данных: их особенностях, операциях с ними и ситуациях, когда их следует применять.
Массивы
Это наиболее простой пример структуры данных. Массив — это линейная последовательность значений, у каждого из которых есть свой номер. Номера называются индексами — по ним можно получить доступ к любому элементу массива.
Индексы идут последовательно. Это целые числа, последовательность которых начинается с 0.
Можно выделить одномерные и многомерные массивы. Одномерные — это просто ряд пронумерованных значений. Многомерные — матрица: каждое из значений массива само является массивом. Соответственно, доступ к элементу возможен по двум индексам — строки и столбца.
Классические массивы — статические, то есть у них конечная и неизменная длина. Если такой массив создать с длиной 5 — там всегда будет место ровно для 5 элементов, не больше и не меньше. Еще бывают динамические массивы: их длина изменяется в зависимости от того, сколько значений в них «положили».
Как правило, в массиве могут лежать данные только одного типа. Например, массив чисел или массив строк.
Операции. К массиву можно добавить элемент или несколько — в конец или в определенный участок ряда. Также можно вернуть элемент по индексу, в некоторых случаях — удалить. По прямому обращению к элементу по индексу его можно изменить. Существуют также операции, позволяющие узнать длину массива, и операции, выдающие весь массив целиком.
Применение. Массивы применяют в большинстве ситуаций, где требуется организованное хранение данных. Не используют их только в сложных структурах: там логичнее будет применить другой формат. Но и тут массивы нужны как строительные «кирпичики» для реализации более комплексных структур.
Станьте дата-сайентистом и решайте амбициозные задачи с помощью нейросетей
Очереди
Очередь похожа на массив. Это тоже линейная структура, состоящая из последовательности элементов. Разница в том, что доступ к этим элементам возможен только по принципу FIFO: First In, First Out. Это значит, что из очереди можно быстро и легко извлечь элемент, который расположен в самом ее начале и находится в ней дольше всего. А вот операций для доступа с конца или из середины может вообще не быть. Добавляются же элементы, наоборот, только в конец — как с реальной очередью в магазине.
Операции. Основные операции, характерные для очереди — добавление элемента в конец, получение элемента из начала без удаления или с удалением, а также проверка ее на пустоту.
Применение. Очереди применяют для организации многопоточных процессов, когда несколько действий выполняются одновременно. Эта структура данных позволяет задать действиям последовательность и очередность выполнения. Так балансируется нагрузка на программу и предотвращается зависание. По такому же принципу очереди используют при выполнении запросов — если их много, и они поступают очень быстро.
Стеки
Стек — структура, обратная очереди. Это последовательность, в которой доступ работает по принципу LIFO: Last In, First Out. Элементы добавляются в конец, а быстро получить и извлечь их можно опять же с конца. То есть чем позже элемент добавили в стек, тем легче до него добраться.
Английское слово stack означает «стопка», например, стопка бумаг. Так можно представить и стек: как стопку бумаг, где самый верхний элемент берется первым.
Операции. Среди операций специально для стеков — так называемый push, то есть добавление элемента в конец, и pop — извлечение элемента из конца. Извлечение означает, что элемент удаляется из стека, но возвращается как значение.
Также есть проверка пустоты стека и операция, возвращающая последний элемент без его удаления.
Применение. Стек используется при выделении памяти программам и процессам. Функции, работающие внутри программы, как бы «укладываются» в системный стек. Популярное выражение stack overflow — это распространенная ошибка, возникающая, когда стек переполняется из-за неправильного использования памяти. Чаще всего такое бывает при рекурсии.
Еще один вариант применения стека — история просмотров: доступ к данным, которые человек просмотрел последними, проще.
Деки
Деками называют двусторонние очереди: они объединяют возможности и очереди, и стека. Такие структуры данных могут работать и по принципу FIFO, и по принципу LIFO. Доступ к элементам возможен с любого конца.
Операции. Можно сказать, что деки объединяют в себе операции, характерные для очередей и стеков. В каком-то смысле эти структуры данных напоминают массивы и приближены к ним по функциональности.
Применение. Деки используют, когда важно обеспечить доступ и к первым, и к последним элементам. Например, при оптимизации выполнения процессов.
Связанные списки
Еще один распространенный пример линейной структуры данных. Это последовательность элементов, каждый из которых хранит данные и ссылку на следующий или предыдущий элемент. В результате от одного элемента можно быстро получить доступ к его «соседу».
Сами элементы, в отличие от массива, хранятся отдельно друг от друга. У них нет номеров. Последовательность достигается исключительно за счет указателей «следующий» и «предыдущий». Последний элемент будет указывать на «пустое» значение.
У односвязных списков есть ссылки только на следующий элемент, у двусвязных — и на следующий, и на предыдущий. Их еще называют однонаправленными или двунаправленными. Есть и круговые списки — они закольцовываются, и последний их элемент указывает на первый.
Первый элемент называется головой списка, последний — хвостом.
Операции. В список можно добавить элемент — в голову, хвост или заданный участок. Также можно обновить значение элемента или удалить его, провести поиск по структуре данных. Также есть операция-проверка, пуст ли список. Доступ к следующему элементу возможен с помощью указателя next, к предыдущему — prev.
Применение. Связанные списки используют для построения более сложных структур, например, графов и хэш-таблиц, которые мы рассмотрим позже. Кроме того, они бывают важны при выделении памяти программам в динамических процессах и операционных системах.
Множества
Множество — еще одна линейная структура данных. Ее особенность в том, что элементы не имеют четкого порядка, зато уникальны по значению. Ее можно представить не как последовательность или список, а как «облако тегов»: неупорядоченный набор уникальных значений. Это определение близко к математическому понятию множества. И работают с ним так же: не сортируют и не структурируют, но хранят, получают и анализируют данные.
Множество еще называют сетом. По-английски название звучит как Set.
Операции. Характерные для этой структуры данных операции рассчитаны на работу с двумя множествами. Например, это операции объединения множества, сравнения, поиска пересечений между двумя сетами. Похоже на круги Эйлера, но программно реализованные.
Также есть операция, которая возвращает только элементы множества, не пересекающиеся с другим, и операция поиска подмножества. Она показывает, содержится ли одно множество в другом.
Применение. Множества используют для хранения наборов данных, которые должны быть уникальными, но не нуждаются в упорядочивании. Обычно это наборы, с которыми нужно проводить определенные операции: искать в них подмножества, объединять, сравнивать с другими и выделять пересечения.
Карты
По-английски эта структура данных называется Map. Ее еще называют ассоциативным массивом или словарем. Она действительно похожа на массив, но вместо упорядоченных числовых индексов в ней используются «ключи» — заданные пользователем числа или строки. Весь массив — это набор пар из ключей и значений.
Можно представить эту структуру данных как телефонный справочник. Ключ — это имя, по которому человек ищет нужный номер. А само значение — собственно, номер телефона.
Разработчик сам задает ключи или генерирует их автоматически. Часто ключи строковые: это облегчает понимание и поиск информации.
Обычно в ассоциативных массивах можно хранить данные разных типов. Ключи тоже могут различаться.
Операции. С картами можно работать как с массивами, но доступ к ним осуществляется по названию ключа, а не индексу. Можно добавить пару ключ-значение, удалить ее или изменить значение. Также можно обратиться к элементу по его ключу.
У ассоциативных массивов иначе реализован проход по всей структуре, чем у обычных: тут не получится просто увеличивать индекс на 1 на каждом шаге, ведь ключ может быть каким угодно. Поэтому в языках программирования обычно предусмотрены специальные операции для прохода по словарю.
Применение. Ассоциативные массивы используют для хранения данных, которые нужно быстро найти по какому-то неизменному ключу. Еще они применяются для передачи данных — ключ выступает как название параметра, а в самом элементе хранится его значение. Например, когда человек заполняет форму на сайте и отправляет ее, на сервер приходят данные в виде словаря вроде такого:
Ключи name, phone и isAgree описывают, что именно передают: имя, телефон и наличие согласия на получение сообщений. А Ivan, true и номер — уже значения, которые приняли эти параметры.
Хэш-таблицы
Эта структура данных похожа на ассоциативный массив и иногда реализуется через него. Разница в том, что ключ для каждого значения задается автоматически с помощью специальной формулы — хэш-формулы. Эта формула применяется к самому значению — в результате генерируется ключ, основанный на нем.
С помощью хэш-таблицы можно генерировать ключи автоматически, например, в ситуациях, когда их название не должно нести полезной нагрузки. Это удобнее и быстрее, чем словарь, если речь идет о больших объемах данных. Кроме того, использование хэшей позволяет шифровать информацию — правда, одной таблицы для этого недостаточно.
При работе с хэш-таблицами важно избегать коллизий — ситуаций, когда применение формулы к разным значениям дает одинаковые ключи. Чтобы такого не было, нужно грамотно подбирать формулу для каждого случая. Также существуют специальные стратегии для предотвращения коллизий.
Операции. В таблицу можно добавить элемент, удалить или найти по тому или иному признаку. При ее создании также можно и нужно задать формулу, по которой будут генерироваться хэши.
Применение. Хэш-таблицы используются для хранения больших объемов информации, в базах данных, а также для создания кэшей или при построении более сложных структур. Чаще всего таблицы используют, когда нужен быстрый доступ к информации.
Графы
Граф — нелинейная структура организации данных, которая состоит из «вершин» и «ребер» между ними. Каждая вершина — это значение, а ребра — пути, которые соединяют между собой вершины. Получается своеобразная «сетка», похожая на карту дорог или созвездие.
Графы бывают неориентированными, когда ребра не имеют конкретного направления, и ориентированными. Во втором случае по ребрам можно пройти только в одну сторону — как по дороге с односторонним движением. Есть смешанные графы, в которых есть и ориентированные, и неориентированные ребра.
Также существуют взвешенные графы, у ребер которых есть «вес» — то или иное значение. Например, в карте дорог весом ребра-дороги можно назвать его длину.
Графы обычно реализуют с помощью связанных списков или матриц — двумерных массивов.
Операции. С этими структурами есть базовые операции: добавление вершины или ребра, отображение вершины или графа целиком, оценка «стоимости» обхода взвешенного графа и так далее.
Существует несколько алгоритмов обхода графов для поиска информации или для нахождения кратчайшего пути от одной вершины до другой. Например, DFS, BFS, алгоритм Дейкстры и другие. Для них не всегда существуют отдельные команды, так что реализовать эти алгоритмы может понадобиться самостоятельно.
Применение. Графы активно используют для хранения моделей машинного обучения, а также при работе с картами, например, для построения маршрутов через онлайн-сервисы. Программное эмулирование электрических цепей — это тоже графы. Также теория графов применяется в поисковых алгоритмах и социальных сетях — например, так рассчитывается «охват» друзей одного человека. Графы можно использовать для распределения ресурсов внутри системы или при организации сложных вычислений.
Деревья
Деревья можно назвать частным случаем графов. Это тоже структуры из вершин и ребер, но имеющие древовидный формат. Вершины деревьев называются узлами. От одного узла может отходить несколько вершин-потомков, но предок у каждого узла может быть только один. Так и получается древовидная иерархическая структура.
В программировании применяют несколько видов деревьев. Большинство из них реализуют с помощью графов.
- Бинарные деревья поиска — самый распространенный формат деревьев. У каждого узла может быть не более двух потомков. Если значение узла-потомка меньше предка, он располагается слева. Если равно или больше — справа. С помощью таких деревьев удобно сортировать данные и искать в них нужное значение с помощью бинарного поиска.
- Префиксные деревья, они же нагруженные деревья или боры — это деревья, которые хранят данные «по цепочке». Узлы-предки — префиксы, которые нужны, чтобы перейти к потомкам. При прохождении дерева «собираются» полные данные. Например, в каждом узле — буква. При прохождении от начала до конца получаются слова. Обычно такие деревья используют для хранения повторяющихся данных или, например, для реализации автодополнения при вводе.
- Двоичные кучи — двоичные деревья, заполненные целиком. У каждого узла два потомка. Потомки в зависимости от типа дерева должны быть строго больше предков или меньше либо равны им.
Также существуют N-арные деревья, красно-черные деревья, AVL-деревья, сбалансированные, 2-3-4-деревья и так далее.
Операции. По деревьям можно проходить снизу вверх или сверху вниз, в ширину или в глубину. Для этого существуют специальные алгоритмы — как и для графов. Так как обычно в деревьях расположение узла зависит от его значения, произвольно добавить узел куда угодно не получится. Поэтому при добавлении, удалении или изменении узла происходит перебалансировка — изменение структуры с учетом добавленного значения.
Применение. Деревья используют для хранения информации, для которой важна иерархичность. Также их могут применять для эффективной сортировки, поиска нужных значений или хранения повторяющихся данных, как в случае с префиксными деревьями. Часто деревья встречаются в алгоритмах машинного обучения.
Структуры данных есть в любом языке программирования. Впервые вы встретитесь с ними еще в начале обучения, когда начнете проходить массивы. Постепенно программа будет усложняться, и вы будете сталкиваться и с другими структурами — а возможно, писать их собственные реализации.
Узнать больше о программировании и хранении данных вы можете на наших курсах. Записывайтесь — сделайте первый шаг к новой популярной профессии.
Data Scientist
Дата-сайентисты решают поистине амбициозные задачи. Научитесь создавать искусственный интеллект, обучать нейронные сети, менять мир и при этом хорошо зарабатывать. Программа рассчитана на новичков и плавно введет вас в Data Science.
Что такое структура в чем ее отличие от массива
Как известно, тип описывает множество значений переменной и операций, которые могут выполняться над этой переменной. За исключением типа «символьная строка», все типы, с которыми мы имели дело до сих пор, представляли только единичные значения. В программах, выполняющих более содержательную работу, может понадобиться переменная, которая содержит множество значений. Переменная scores (баллы), например, должна содержать информацию о рейтинге 100 студентов. Или переменная salaries (оклады), которая содержит оклады всех служащих некоторой компании. Массив — это такая совокупность данных, которая содержит множество значений одного и того же типа. Например, можно создать один массив, который содержит 100 значений типа inl, и другой массив, который содержит 25 значений типа float. Тип каждого значения, присваиваемого массиву, должен быть таким же, как и тип самого массива. В этом разделе мы научимся создавать массивы в программе и работать с ними. После приобретения некоторого опыта работы с массивами легко убедиться в эффективности их использования. Для тех, кто уже имеет опыт работы со строками, освоение массивов не составит особого труда — символьная строка есть просто массив символов.
454. Объявление массива
Массив — это переменная, в которой можно хранить множество значений одного и того же типа. Для объявления массива необходимо указать требуемый тип (такой как int, float или double ) и размер массива. Для указания размера массива количество его элементов (размерность массива) помещается в квадратных скобках после имени массива. Например, в следующем объявлении создается массив scores, который может содержать баллы, обозначающие рейтинг 100 студентов:
int scores[100];
Аналогичным образом следующим оператором объявляется массив, содержащий зарплаты 50 служащих:
float salaries [50];
При объявлении массива Си выделяет достаточно памяти для размещения всех его элементов. Номер первого элемента начинается с нуля. Например, для массивов scores и salaries первым элементам можно присвоить значения 80 и 35000 следующим образом:
scores[0] = 80; salaries[0] = 35000.0;
Поскольку номер первого элемента равен нулю, номер последнего элемента на 1 меньше, чем размер массива. Для массивов scores и salaries последним элементам можно присвоить значения следующим образом:
scores[99] = 75; salaries[49] = 24000.0;
455. Размещение массива в памяти
Итак, массив — переменная, в которой хранится множество значений одного и того же типа. Для того чтобы лучше понять, как хранится информация в массиве, рассмотрим следующие объявления массивов:
char string[64]; float salaries[50]; int scores[100]; long planets[13];
После присваивания значений каждому из этих массивов они располагаются в памяти, как показано на рис. 455.
Рис. 455. Размещение значений в массиве
Как можно видеть, первое значение массива расположено по смещению 0. В разделе «Язык Си: начало» переменная определялась как имя, связываемое с одной или более ячеек памяти. В случае массива число таких ячеек памяти может быть значительно больше.
456. Объем памяти для массива
Как мы уже знаем, массив есть именованное множество значений одного и того же типа. При объявлении массива Си выделяет достаточно памяти для размещения указанного количества элементов, фактически требуемый объем памяти зависит от типа массива. Например, для массива из 100 элементов типа int требуется 100 * 2, т.е. 200 байтов памяти. С другой . стороны, для массива из 100 элементов типа float требуется 100* 4, или 400 байтов. В следующей программе ARRAYSIZ.C для вывода на экран количества памяти, требуемой для массивов различных типов, используется оператор sizeof:
#include void main(void)
После компиляции и выполнения этой программы на экран выводится:
C:\> ARRAYSIZ Массиву int scores[100] выделяется 200 байтов Массиву float salaries[100] выделяется 400 байтов Массиву char string[100] выделяется 100 байтов
457. Инициализация массива
Многие примеры, приведенные в этой книге, содержат проинициализированные символьные строки,например:
char title[] = "1001 совет по C/C++"; char section[64] = "Массивы";
В первом случае Си-компилятор выделяет 20 байтов для хранения строки. Во втором случае компилятор выделяет массив 64 байта, инициализируя первые 8 символов словом «Массивы» и двоичным нулем. Большинство компиляторов инициализирует двоичным нулем и остальные байты. Массивы других типов инициализируются аналогично. Например, следующий оператор инициализирует массив scores целых чисел значениями 80,70,90,85 и 80:
int scores[5] = ;
При присваивании массиву начальных значений эти значения заключаются в фигурные скобки < >. Е нашем случае размер массива соответствует количеству начальных значений. В следующем операторе присваивается 4 действительных значения массиву, который в действительности может содержать 64 значения:
float salaries[64] = ;
В зависимости от компилятора, остальным 60 элементам могут быть присвоены нули. Как правило, не следует делать предположения об инициализации оставшихся элементов по умолчанию. Если размерность массива не указана, компилятор выделит ровно столько памяти, сколько нужно для хранения заданных начальных значений. Следующее объявление создает массив достаточно большой для того, чтобы содержать 3 значения типа long:
long planets[] = ;
458. Доступ к элементам массива
Значения, которые хранятся в массиве, называются элементами массива. В следующей программе ELEMENTS.C, содержащей инициализируемый массив scores, для вывода элементов массива используется функция printf:
#include void main(void)
После компиляции и выполнения этой программы на экран выводится:
C:\> ELEMENTS Значения элементов массива scores[0] = 80 scores[1] = 70 scores[2] = 90 scores[3] = 85 scores[4J = 80
Как можно видеть, для обращения к элементам массива необходимо указать сначала имя массива, а затем номер требуемого элемента в квадратных скобках.
459. Использование элементов массива в цикле
В С458 были выведены на экран значения элементов массива с номерами от 0 до 4. Обращение к элементам массива с указанием номера каждого элемента массива — способ достаточно громоздкий. В качестве альтернативы можно использовать переменную для указания номеров элементов. Например, предположим, что переменная;’ содержит значение 2, тогда следующий оператор присвоит элементу array [2] значение 80:
i = 2; array [i] =80;
В следующей программе SHOWARRA.C для вывода на экран значений массива scores используется переменная i и цикл for:
#include void main(void)
460. Использование констант при объявлении массивов
При работе с массивами необходимо указать размер массива. Например, в следующей программе 5_VALUES.C определяется массив из 5 элементов и используется цикл/олдля вывода значений этого массива:
#include void main(void)
Предположим, мы хотим изменить программу, увеличив размер массива до 10 значений; в этом случае нам придется изменить не только объявление массива, но и границу цикла for. Альтернативой этому является объявление массива с использованием константы. В следующей программе 5_CONST.C объявляется массив, размер которого равен значению ARRAY_SIZE. Как видим, программа использует константу не только для объявления массива, но и в цикле for:
#include #define ARRAY_SIZE 5 void main(void) < int values[AKKAY_SIZE] = (80, 70, 90, 85, 80>; int i; for (i = 0; i
Теперь, если понадобится изменить размер массива, можно просто изменить значение константы ARRA Y_SIZE; в этом случае автоматически изменится как верхняя граница цикла обработки массива, так и размер самого массива.
461. Передача массивов как параметров функций
Массив — это переменная, которая может содержать множество значений одного и того же типа. Как и все другие переменные, массивы могут использоваться как параметры функций. В следующей программе ARRFUNCT.C определяется функция show_array для вывода значений массива. Как видим, программа передает функции массив и количество элементов, содержащихся в массиве:
#include void show_array(int values[], int number_of_elements) < int i; for (i = 0; i < number of elements; i++) printf("%d\n", values[i]); >void main(void) < int scores[5] = (70, 80, 90, 100, 90>; show_array(scores, 5); >
При объявлении функции с массивом в качестве формального параметра необязательно указывать размер массива. В случае с функцией show_values квадратные скобки, которые следуют за именем переменной value, информируют компилятор лишь о том, что параметр является массивом; размер массива для компилятора не имеет значения.
462. Массивы как функции
Как известно (С461), при объявлении массива в качестве формального параметра функции не требуется указывать размер массива. Вместо этого необходимо указать только квадратные скобки [ ]. В следующей программе ARRPARAM.C функция show_values вызывается три раза с тремя различными массивами в качестве фактических параметров массива values:
#include void show array(int values[], int number of elements) < int i; printf("Вывод на экран %d значений\n", number of elements); for (i = 0; i < number of elements; i++) printf("%d\n", values[i]); >void main(void)
После компиляции и выполнения программы на экран выводятся значения всех трех массивов. Обратим внимание на то, что для функции не имеет значения размер массива. Заметим, однако, что все массивы, передаваемые в функцию, имеют тип int. При попытке передать в функцию массив тпа float компилятор выдаст сообщение об ошибке.
463. Отличительная особенность символьных строк как массивов
Примеры, в которых строки выступают в роли фактических параметров вызываемых функций, встречаются в нашей книге нередко. Как правило, в таких случаях длина строки не указывается как параметр функции. Например, в следующем фрагменте используется функция struprw» преобразования строчных букв в прописные [строка фрагмента не переведена на русский язык, поскольку функция strupr не делает преобразование русских букв. — Прим. перев.>:
char title[64] = "1001 совет по C/C++"; strupr(title);
Как нам уже известно, в языке Си конец символьной строки отмечается NULL-СИМВОЛОМ. Таким образом, в функциях для определения конца строки (рассматриваемого также и как массив) делается поиск элемента, равного нулю. В массивах других типов, например, int, float или long, нет эквивалента для NULL-символа. Поэтому, когда в качестве параметра функции указывается массив элементов, приходится указывать также и другой параметр — длину (количество элементов) массива.
464. Передача массивов в стек при вызове функций
В нескольких ранее приведенных примерах рассматривалось использование массива в качестве параметра функции. При передаче массива как фактического параметра функции Си помещает в стек только адрес первого элемента массива. На рис. 464 показаны, например, массив scores и функция show_array, использующая этот массив. Как можно видеть, в стек помещается только начальный адрес массива.
Рис. 464. При передаче массива как фактического параметра Си передает в стек только адрес первого элемента массива
Как видно из рис. 464, компилятор не передает в функцию никакой информации о длине массива.
465. Максимальный объем памяти, занимаемой массивом
Не существует одной общей границы для максимального объема памяти, занимаемой массивом. При работе в среде DOS максимальный объем памяти, который может быть отведен под массив, зависит от текущей модели памяти. Вообще говоря, массив не может использовать больше 64К памяти. Компиляция следующей программы ТОО_ВIG.С может быть неуспешной из-за того, что массивы требуют достаточно много памяти:
void main(void) < char string[66000L]; // 66,000 байт int values[33000L]; // 33,000 * 2 = 66,000 байт float numbers[17000]; // 17,000 * 4 = 68,000 байт >
466. Использование сверхбольшой модели памяти для больших массивов
Если объем памяти, требуемый для массива, превышает 64К, то можно настроить компилятор на использование сверхбольшой модели памяти, рассматривая массив как указатель и добавив ключевое слово huge в объявлении:
float huge values[17000];
В следующей программе HUGE_FLT.C создается массив с указателем, объявленным как huge:
#include # include void main (void) < int i; float huge *values; if ((values = (float huge *) halloc (17000, sizeof(float))) == NULL) printf ("Ошибка при выделении массива huge\n"); else < printf("Заполнение массива\n"); for (i = 0; i < 17000; i++) values[i] = i * 1.0; for (i = 0; i < 17000; i++) printf ("%8.1f ", values[i]); hfree(values); >>
467. Массивы или динамическая память
Программисты, достаточно хорошо знающие язык Си и приемы работы с указателями, редко используют массивы, предпочитая динамическое выделение памяти по мере необходимости. При выборе между массивом и динамической памятью следует учитывать следующее. Во-первых, многие пользователи считают массивы более простыми для понимания и использования. В результате программа, использующая массивы, нагляднее и легче для поддержки другими пользователями. Во-вторых, поскольку компилятор сам выделяет место для массива, то в программе не требуется иметь дело с управляющей информацией, связанной с динамической памятью. В результате программа, работающая с массивами, может выполняться несколько быстрее.
Однако, как известно, при объявлении массива необходимо указать его размер. Если точный размер массива не известен заранее, то, возможно, будет выделено больше памяти, чем потребуется на самом деле. В результате память может расходоваться впустую. Если же задавать предельно маленький размер массива, то может потребоваться часто редактировать программу, изменяя размеры массивов, и компилировать программу заново.
Итак, объявляя в программе массив, следует иметь в виду, что идентичную обработку можно обеспечить с помощью динамического выделения памяти. Как мы увидим в разделе «Массивы, указатели и структуры», при доступе к динамически выделенной памяти можно с тем же успехом использовать индексы массива, что даст возможность исключить путаницу с указателями, часто смущающую начинающих программистов. Поскольку большинство операционных систем располагает достаточно быстрыми средствами выделения памяти, рекомендуется использовать в программах систему динамического управления памятью для более эффективной и гибкой работы в обмен на незначительную потерю времени, связанную с использованием управляющей информации.
468. Многомерные массивы
Как мы уже знаем, массив — это переменная, которая может содержать множество значений одного и того жетипа. Во всех предшествующих примерах массивы представляли собой линейную последовательность данных. Однако язык Си поддерживает двумерные, трехмерные и многомерные массивы, как показано на рис. 468.
Рис. 468. Многомерные массивы
Лучший способ наглядного представления двумерного массива — это таблица, в которой выделены строки и столбцы. Трехмерный массив наглядно описывается массивом из нескольких страниц, каждая из которых содержит двумерную таблицу. На рис. 468 представлены массивы, которые объявляются следующим образом:
char strings[64]; int table[10][5]; float pages[10][5][3];
469. Столбцы и строки двумерного массива
Итак, язык Си обеспечивает возможность работы с многомерными массивами. Двумерный массив хорошо представляется как таблица со строками и столбцами. Данные в строках рассматриваются слева направо, в то время как в столбцах — сверху вниз, как показано на рис. 469.
Рис. 469. Строки и столбцы массива
При объявлении двумерного массива первое измерение задается количеством строк, а второе — количеством столбцов.
int table [2] [3];
470. Доступ к элементам двумерного массива
В качестве модели представления двумерного массива была выбрана таблица строк и столбцов. Для обращения к требуемому элементу двумерного массива необходимо указать номер строки и номер столбца. На рис. 470 показаны операторы, в которых осуществляется доступ к требуемым элементам массива table.
Рис. 470. Для обращения к требуемому элементу двумерного массива необходимо указать номер строки и номер столбца.
471. Инициализация элементов двумерного массива
При инициализации элементов двумерного массива используется техника, аналогичная той, которая использовалась для одномерных массивов (см. С457)-перечисление значений, заключенных внутри фигурных скобок. Однако, в данном случае значения двумерного массива в каждой строке заключаются в фигурные скобки < >отдельно:
int table[2][3] = , >;
Компилятор Си инициализирует элементы массива, как показано на рис. 471.
Аналогично, в следующем операторе показана инициализация элементов массива, в котором количество строк и столбцов больше:
int sales[4][5] , , , >;
472. Определение объема памяти для многомерного массива
В С456 показано, как определить объем памяти, требуемый для одномерного массива — количество элементов массива умножается на количество байтов, требуемое для представления типа массива (например, 2 — для int, 1 -для char и т.д.). Для определения объема памяти в случае многомерного массива необходимо выполнить те же операции. Количество элементов многомерного массива просто равно произведению всех его измерений. Например, количество элементов двумерного массива равно произведению количества строк на количество столбцов. Следующие объявления массивов сопровождаются подсчетом соответствующего объема памяти:
int а[5][10]; // 2 * 5 * 10 == 100 байт float b[5][8]; //4 * 5 * 8==160 байт int c[3][4][5]; //2 * 3 * 4 * 5 = 120 байт
В следующей программе MDJ3IZE.C при определении количества байтов, требуемых для различных массивов, используется операция sizeof:
#include void main(void)
После компиляции и выполнения программы на экране появляется следующее:
C:\> MDJSIZE Для массива int bох[3][3] требуется 18 байт Для массива float year_sales[52][5] требуется 1040 байт Для массива char pages[40][60][20] требуется 48000 байт
473. Использование элементов двумерного массива в цикле
В С458 было продемонстрировано использование переменной для индексирования элементов массива. При работе с двумерными массивами для обращения к элементам массива обычно используются две индексные переменные. В следующей программе SHOW_2D.C для вывода значений массива table используются переменные row и column:
#include void main(void) < int row, column; float table[3][5] = , , >; for (row = 0; row
Используя вложенные циклы for, программа выводит на экран элементы сначала из первой строки (от 1.0 до 5.0), затем из второй и, наконец, из третьей.
474. Обработка трехмерного массива
В С473 было показано, как пройти по всем элементам двумерного массива, используя две переменные row и column. В следующей программе SHOW_3D.C используются три переменные: row, column и table для обработки трехмерного массива:
#include void main(void) < int row, column, table; float values[2][3][5] = < , , >, , , > >; for (row = 0; row
475. Инициализация многомерного массива
В С474 было показано, как вывести на экран значения элементов трехмерного массива, используя три переменные: row, column и table. Там же была применена инициализация трехмерного массива:
float values[2][3][5] = < , , >, , , > >;
На первый взгляд, инициализация многомерного массива при его объявлении может породить путаницу. Для того, чтобы показать, как инициализируются такие массивы, ниже представлены различные примеры.
Для того, чтобы правильно задавать данные инициализации, необходимо представлять себе, каким образом формируются соответствующие операторы присваивания этих данных элементам массива. Прежде всего, глубина вложенности фигурных скобок соответствует количеству измерений массива. Самая внешняя пара фигурных скобок соответствует объявлению всего массива. Число пар скобок первого уровня соответствует числу строк (элементов первого измерения); число пар скобок второго уровня, заключенных в скобки первого уровня, соответствует числу столбцов (элементов второго измерения); число элементов, заключенных внутри каждой пары скобок второго уровня, соответствует количеству таблиц (элементов третьего измерения) и т.д. — Прим. перев>
int а[1][2][3] = < < , (4, 5, 6> > >; // Эти скобки используются для инициализации // всего массива int b[2][3][4] = < < , , >, < , , > >; // Эти скобки используются для инициализации // всего массива int с[3][2][4] = < < , >, < , >, < , > >; // Эти скобки используются для инициализации // всего массива int d[l][2][3][4] = < <<, , >, <, , >> >; // Эти скобки используются для инициализации // всего массива
476. Двумерные массивы как фактические параметры
При работе с многомерными массивами может возникнуть необходимость определять функции, имеющие массивы в качестве параметров. В С461 мы видели, что при передаче функции в качестве фактического параметра одномерного массива нет необходимости указывать количество его элементов. При передаче функции двумерных массивов необязательным является только количество строк, но количество столбцов должно указываться. В следующей программе FUNCT_2D.C определяется функция show_2d_array для вывода значений элементов различных двумерных массивов:
#include void show_2d_array(int array[][10], int rows) < int i, j; for (i = 0; i < rows; i++) for (j = 0; j < 10; j++) printf ("array [%d] [%d] =%d\n", i, j, array[i][j]); >void main(void) < int a[1][10] = >; intb[2][10] = , >; int c[3][10] = , , >; show_2d_array(a, 1); show_2d_array(b, 2); show_2d_array(с, 3); >
477. Одномерная интерпретация многомерных массивов
Из С476 мы знаем, что при передаче функции двумерного массива как фактического параметра необходимо указать количество столбцов:
void show 2d array(int array[][10], int rows)
Однако, если работа с многомерным массивом не требует доступа к элементам посредством конкретного указания строк, столбцов и т.д., то функция может воспринимать многомерный массив как одномерный, хотя в качестве фактического параметра ей передается многомерный массив. В следующей программе SUM_2D.C определяется функция, возвращающая сумму значений двумерного массива.
#include long sum_array(int array[], int elements) < long sum = 0; int i; for (i = 0; i < elements; i++) sum += array[i]; return(sum); >void main(void) < int a[10] = ; int b[2][10] = <, >; int c[3][10] = <, , >; printf("Сумма элементов первого массива %d\n", sum array(a, 10)); printf("Сумма элементов второго массива %d\n", sum array(b, 20)); printf("Сумма элементов третьего массива %d\n", sum array(с, 30));
Как видим, функция sum_arrays справляется с одномерными массивами так же, как и с двумерными и многомерными массивами. Секрет работы функции с массивами заключен в способе, принятом в Си для хранения в памяти многомерных массивов. Подробнее об этом в С478.
478. Хранение многомерных массивов в Си
В С454 мы видели, что при объявлении массива, например inl scores[100], Си выделяет память, достаточную для размещения всех элементов массива. Для многомерного массива делается то же самое. Хотя логически многомерный массив содержит строки, столбцы и т.д., для компилятора он представляет собой один длинный массив байтов. Например, пусть в программе объявлен следующий массив:
#include table[3][5];
На рис. 478 показано логическое представление и фактическое использование памяти для этого массива.
Рис. 478. Размещение в памяти элементов двумерного массива
В С477 была показана функция, которая суммировала значения двумерного массива, считая его одномерным. Поскольку фактически Си располагает двумерный массив в одномерной памяти, считать двумерный массив одномерным опустимо.
479. Расположение элементов массива по строкам или по столбцам
В С478 мы видели, что компилятор Си располагает многомерный массив в одномерной памяти. Для этого есть две возможности. Как показано на рис. 479, компилятор может начать расположение элементов многомерного массива как с первого столбца, так и с первой строки и далее:
Рис. 479. Два способа расположения в памяти элементов двумерного массива
Если компилятор размещает в памяти сначала первый столбец, затем второй и т.д., то такое размещение называется по столбцам. Если компилятор размещает в памяти сначала первую строку, затем вторую и т.д., то такое размещение называется по строкам. Компиляторы Си размещают многомерные массивы по строкам.
480. Массивы структур и структуры в массивах
Массивы и структуры объединяют информацию в группы. Известно, что Си позволяет создавать массивы структур и использовать массивы как члены структур. Вообще говоря, в Си не предусмотрено ограничение на глубину вложенности при размещении данных в структурах. Например, в нижеследующем фрагменте объявляется массив, содержащий данные о 100 служащих, организованные в виде структуры. Внутри этой структуры объявляется массив emp_dates структур типа Date, который содержит даты приема, первой и последней аттестации служащего:
struct Employee < char name[64]; int age; char ssan[ll]; // Социальный номер служащего int pay_grade; float salary; unsigned employee_number; struct Date < int month; int day; int year; >emp_dates[3]; > staff[100];
Обращение к элементам структуры и массивам выполняется по вложенности, «слева направо», начиная с внешнего имени. Например, дата приема служащего устанавливается следующим образом:
staff[10].emp_dates[0].month = 7; staff[10].emp_dates[0].day = 7; staff[10].emp_dates[0].year = 7;
Хотя использование вложенных структур и массивов может быть очень удобно, имейте в виду, что дальнейшее усложнение структуры может привести к ухудшению понимания вашей программы другими программистами.
481. Объединения
Как известно, использование структур в программе дает возможность хранить связанную информацию. В зависимости от назначения программы, информация, которую хранят в структурах, может принимать либо одно, либо другое значение. Например, программа может использовать для каждого служащего два специальных значения даты. Для работающих в настоящее время служащих используется количество отработанных дней. Для служащих, которые больше не работают в компании, программа использует дату последнего выхода на работу. Один из способов поддержки этой информации — использование структуры:
struct EmpDates < int days worked; struct LastDate < int month; int day; int year; >last_day; >;
Поскольку в программе на самом-то деле используется либо days_worked (количество отработанных дней), либо last_day (дата последнего выхода на работу), то это приводит к ненужной трате памяти, поскольку в любом случае имеется неиспользуемое значение. В качестве альтернативы Си поддерживает union (объединение), что позволяет выделять память, достаточную для хранения элемента объединения наибольшей длины:
union EmpDates < int days worked; struct LastDate < int month; int day; int year; >last_day; >;
Для обращения к члену объединения используется оператор «точка» (.), как при обращении к членам структуры. В отличие от структуры, объединение хранит значение только одного члена. На рис. 481 показано, как Си выделяет память для структуры и объединения.
Рис. 481. Распределение памяти для похожих структуры и объединения
Как станет известно, использование объединений не только экономит память, но дает также возможность различной интерпретации значений в памяти.
482. Экономия памяти при использовании объединений
В С481 мы познакомились с еще одним способом хранения информации — объединение (union). При использовании объединений Си выделяет память, требуемую элементу объединения наибольшей длины. В следующей программе UNIONSIZ.C для вывода на экран объема памяти, требуемой для хранения объединений, используется операция sizeof:
#include void main(void) < union EmployeeDates < int days_worked; struct Date < int month; int day; int year; >last_day; > emp_info; union Numbers < int a; float b; long c; double d; // Элемент наибольшей длины требует 8 байт >value; printf("Длина объединения EmployeeDates равна %d байт\n", sizeof(emp info)); printf("Длина объединения Numbers равна %d байт\n", sizeof(value)); >
После компиляции и выполнения программы на экране появляется следующее:
C:\> UNIONSIZ Длина объединения EmployeeDates равна 6 байт Длина объединения Number равна 8 байт
483. Объединение REGS — классический пример объединения
Итак, использование объединений в программе экономит память и позволяет интерпретировать одну и ту же память по-разному. В разделе «Обслуживающие средства DOS и BIOS» мы увидим, что для того, чтобы получить доступ к средствам DOS и BIOS в программе, выполняется присваивание необходимых значений определенным регистрам (на уровне языка ассемблера). Для обеспечения доступа к системным средствам DOS и BIOS в Си-программах большинство Си-компиляторов предоставляет соответствующие библиотечные подпрограммы, которые используют объединение типа REGS:
struct WORDREGS < unsigned int ax, bx, ex, dx, si, di, cflag, flags; >; struct BYTEREGS < unsigned char al, ah, bl, ah, cl, ch, dl, dh; >; union REGS < struct WORDBEGS x; struct BYTEREGS h; >;
При обращении к регистрам общего назначения (АХ, ВХ, СХ, DX) их содержимое может быть доступно как в целом, в 16-разрядном формате, так и по отдельным байтам, старшим и младшим (AL, АН, BL, ВН, CL, CH, DL, DH). Поскольку оба метода относятся к одним и тем же регистрам, имеем два способа просмотра одних и тех же ячеек памяти. В таких случаях удобно использование объединения. На рис. 483 показано, как хранится информация в памяти при использовании объединения REGS.
Рис. 483. Хранение значений переменных типа «объединение REGS»
484. Использование объединения REGS
В С483 было выделено особо одно из наиболее часто используемых объединений -объединение REGS. В следующей программе GET_VERX.C объединение REGS используется для вывода на экран текущей версии DOS. Регистры общего назначения используются в 16-разрядном формате (слово). #include #include void main(void) < union REGS inregs, outregs; inregs.x.ax = 0х3000; intdos(&inregs, &outregs); printf("Текущая версия DOS: %d.%d\n", outregs.x.ax & OxFF, outregs.x.ax >> 8); >
В следующей программе GET_VERH.C для вывода на экран текущей версии DOS используются регистры в 8-разрядном формате (байт):
#include #include void main(void)
485. Битовые поля
Во многих функциях, приведенных в этой книге, показано, как уменьшить количество переменных (а значит, и количество занимаемой памяти) в том случае, когда параметры функции или возвращаемое значение имеют битовую структуру. Если отдельные биты или их комбинации имеют определенный смысл, то можно использовать в программе битовые операции для получения значений группы битов. Предположим для примера, что программа работает с датами количеством 100 000. Один из способов — создать структуру типа Date:
struct Date < int month; // от 1 до 12 int day; // от 1 до 31 int year; // последние две цифры >;
В качестве альтернативы для хранения даты можно использовать’битовое представление одного значения unsigned int, как показано на рис. 485.
Рис. 485. Битовое представление даты
Таким образом, каждый раз при присваивании даты в программе необходимо использовать корректные битовые операции, как показано в следующем фрагменте:
unsigned date; date = month; date = date | (day > 4) & OxIF, (date >> 9));
Однако, чтобы программа была проще для понимания, Си дает возможность создавать структуру, составленную из битовых полей. При объявлении такой структуры в каждом поле после двоеточия указывается количество битов на этом поле:
struct Date < unsigned month:4; unsigned day:5; unsigned year:7; >date;
Обращение к битовым полям делается следующим образом:
date.month = 12; date.day = 31; date.year = 94; printf("Месяц %d День %d Год %d", date.month, date.day, date.year);
Примечание: При объявлении битовых полей тип элементов структуры должен быть целым без знака (unsigned int).
486. Представление битовых полей в структуре
В С485 обсуждалась возможность поразрядного представления значения с использованием структуры битовых полей. При объявлении такой структуры Си выделяет достаточно памяти для хранения всех битов структуры. Если в последнем байте некоторые биты не использованы, те большинство Си-компиляторов инициализируют эти биты нулями. Для пояснения того, как Си хрант информацию в структуре битовых полей, приводится рис. 486, иллюстрирующий представление битовых полей структуры Date.
struct Date < unsigned month:4; unsigned day:5; unsigned year:7; >date;
Рис. 486. Представление битовых полей структуры Date
487. Диапазоны значений битовых полей структуры
В С486 было показано, как Си представляет биты в структуре битовых полей. При создании такой структуры необходимо выделить достаточное количество битов для каждого поля, чтобы в них могло содержаться требуемое значение. Для того чтобы помочь сориентироваться с количеством требуемых битов, в табл. 487 указан диапазон значений, которые можно хранить в указанном количестве битов.
Таблица 487. Диапазоны значений, предсгпавимых в заданном количестве битов
Размер поля в битах | Допустимый диапазон значений |
1 | 0-1 |
2 | 0-3 |
3 | 0-7 |
4 | 0-15 |
5 | 0-31 |
6 | 0-63 |
7 | 0-127 |
8 | 0-255 |
9 | 0-511 |
10 | 0-1023 |
11 | 0-2047 |
12 | 0-4095 |
13 | 0-8191 |
14 | 0-16383 |
15 | 0-32767 |
16 | 0-65535 |
488. Поиск заданного значения в массиве
Как известно, для хранения связанных значений одного и того же типа используются массивы. Наиболее общая операция при работе с массивами — поиск указанного значения в массиве. Существует два способа поиска в массиве: последовательный поиск и двоичный поиск. При последовательном поиске программа начинает с первого элемента и просматривает элементы один за другим до тех пор, пока не будет найден нужный или пока не будут исчерпаны все элементы массива.
Например, в следующем цикле while показан поиск в массиве значения 1001:
found = 0; i = 0; while ((i < ARRAY_ELEMENTS) && (! found)) if (array[i] ==1001) found = true; else i++; if (i < ARRAY_ELEMENTS) printf("Значение найдено на элементе номер %d\n", i); else printf("Значение не найдено");
Если элементы массива расположены в порядке возрастания (массив отсортирован по возрастанию), то программа может выполнить двоичный поиск, который подробно описан в С489.
489. Двоичный поиск
Итак, один из способов поиска значения в массиве - простой просмотр каждого его элемента. Такой способ поиска приемлем для массивов небольших размеров. Последовательный поиск в большом массиве может потребовать много времени. Если элементы массива отсортированы, то для поиска значения можно использовать двоичный поиск. Такой поиск называется двоичным потому, что при каждой операции количество просматриваемых значений уменьшается в два раза. Наилучший способ понять механизм двоичного поиска- это представить, как в словаре организуется поиск нужного слова. Допустим, нужно найти слово "Dalmatian." Для этого открываем словарь где-то посередине и просматриваем открывшиеся слова. Допустим, что все слова на этой странице на букву М, в этом случае ясно, что нужное слово - в первой половине словаря. Таким образом, исключается вторая половина словаря. Далее открываем словарь на половине оставшейся части, и видим там слова, начинающиеся на букву F. Теперь исключена половина оставшейся части. Разделим надвое оставшуюся часть. На этой странице увидим слова на букву С. Следовательно, слово "Dalmatian" находится на страницах между буквами С и F. После того как мы откроем словарь на середине оставшейся части, мы с большой вероятностью попадем на слова, начинающиеся с буквы D. Повторяя процесс деления пополам, мы можем быстро найти страницу со словом "Dalmatian".
Примечание: При двоичном поиске значения в массиве должны быть отсортированы в порядке возрастания.
490. Программа двоичного поиска
Как можно убедиться в С489, двоичный поиск дает способ быстрого нахождения указанного значения в отсортированном массиве. В следующей программе BINARY.C выполняется двоичный поиск в массиве count, который содержит значения от 1 до 100. Для лучшего понимания процедуры двоичного поиска в функции binary_search выводятся сообщения о каждом шаге выполнения поиска:
#include int binary_search(int array[], int value, int size) < int found = 0; int high = size, low = 0, mid; mid = (high + low) / 2; printf("\nПоиск значения %d\n", value); while ((! found) && (high >= low)) < printf("Индексы: Нижний %d Текущий %d Верхний %d\n",low,mid, high); if (value == array[mid]) found = 1; else if (value < array[aid]) high = mid - 1; else low = mid + 1; mid = (high + low) / 2; >return((found) ? mid: -1); > void main(void)
Скомпилируйте и выполните программу. Обратите внимание на количество операций, требуемых для поиска значения. В программе используются переменные high, mid и low для отслеживания границ индексов, в пределах которых производится поиск.
491. Сортировка массива
Использование массивов дает возможность хранить связанные значения одного и того жетипа. При работе с массивами может возникнуть необходимость сортировки значений массива как по возрастанию (от меньшего к большему), так и по убыванию (от большего к меньшему). Для сортировки массива есть несколько различных алгоритмов: метод пузырька, метод выбора, метод Шелла и быстрая сортировка. Далее описан каждый из этих методов
492. Сортировка массива методом пузырька
Алгоритм метод пузырька - наиболее простой метод сортировки массива, известный большинству программистов. Из-за своей простоты этот метод не очень эффективен и требует процессорного времени больше, чем остальные методы. Однако для сортировки небольших массивов, с 30 или меньшим количеством элементов, использование метода пузырька приемлемо. Предположим, что массив сортируется в порядке возрастания, тогда метод пузырька использует цикл по значениям массива, перемещая постепенно наибольшие значения в конец массива (подобно тому, как в воде всплывает на поверхность пузырек). На рис. 492 показаны две итерации метода пузырька. При второй итерации второе наибольшее значение перемещается на вторую позицию. При третьей итерации третье наибольшее значение перемещается на третью позицию и т.д.
Рис. 492. Сортировка методом пузырька
493. Программа сортировки методом пузырька
В С492 был проиллюстрирован алгоритм метода пузырька. В следующей программе BUBBLE.C метод пузырька используется для сортировки массива из 30 случайных значений
#include #include void bubble_sort(int array[], int size) < int temp, i, j; for (i = 0; i < size; i++) for (j = 0; j < size; j++) if (array[i] < array[j]) < temp = array[i]; array[i] = array[j]; array[j] = temp; >> void main(void)
Примечание: Функция bubble_sort сортирует значения по возрастанию от меньшего к большему. Для сортировки массива в обратном порядке нужно просто изменить условие сравнения на if (array [i] > array [j]).
494. Сортировка массива методом выбора
Как и сортировка методом пузырька, представленная в С493, сортировка методом выбора очень проста. Этот метод также рекомендуется использовать только для сортировки небольших массивов (порядка 30 элементов). Сортировка методом выбора начинается с выбора одного из элементов массива (например первого элемента). После этого делается просмотр всего массива и находится минимальное значение. Это значение помещается в начало; затем находится второй по возрастанию элемент и помещается на второе место и т.д. На рис. 494 показаны две итерации алгоритма сортировки методом выбора.
Рис. 494. Сортировка методом выбора
495. Программа сортировки методом выбора
В С494 был проиллюстрирована сортировка методом выбора. В следующей программе SELECT.C этот метод используется для сортировки массива из 30 случайных значений:
#include #include void selection_sort (int array [], int size) < int temp, current, j; for (current =0; current < size; current++) for (j = current +1; j < size; j++) if (array[current] >array[j]) < temp = array[current]; array[current] = array[j]; array[j] = temp; >> void main(void)
Примечание: Функция selection_sort сортирует значения по возрастанию от меньшего к большему. Для сортировки массива в обратном порядке нужно просто изменить условие сравнения на if (array [current]
497. Программа сортировки методом Шелла
В С497 была проиллюстрирована сортировка по методу Шелла. В следующей программе SHELL.C этот метод используется -для сортировки массива из 30 случайных значений:
#include #include void shell_sort(int array[], int size) < int temp, gap, i, exchange_occurred; gap = size / 2; do < do < exchange_occurred = 0; for (i = 0; i < size - gap; i++) if (array[i] >array[i + gap]) < temp = array[i]; array[i] = array[i + gap]; array[i + gap] = temp; exchange_occurred = 1; >> while (exchange_occurred); > while (gap = gap / 2); > void main(void)
Примечание: Функция shell Jsort сортирует значения по возрастанию ощ меньшего к большему. Для сортировки массива в обратном порядке нужно просто изменить условие сравнения на if (array [i]
499. Программа по методу быстрой сортировки
В С498 была проиллюстрирована сортировка по методу быстрой сортировки. В следующей программе QUICK.C этот метод используется для массива из 100 случайных значений:
#include #include void quick_sort(int array [], int first, int last) < int temp, low, high, list_separator; low = first; high = last; list_separator = array[(first + last) / 2]; do < while (array[low] < list_separator) low++; while (array[high] >list_separator) high--; if (low > while (low void main(void)
Примечание: Функция quick _sort сортирует значения по возрастанию от меньшего к большему. Для сортировки массива в обратном порядке нужно просто изменить условия сравнения в двух операторах while на:
while (array[low] > list_separator) low++; while (array[high] < list_separator) high++;
500. Проблемы, связанные с сортировкой
В предыдущих примерах показаны различные методы, которые могут использоваться программой для сортировки массива. К сожалению, все приведенные методы работают с массивами типа int. Если в програм ме нужно сортировать массив другого типа, то необходимо написать новые функции. Например, для массивов типа float в программе нужно внести изменения в определение функции quick_sort, как показано в следующем фрагменте:
void quick_sort(float array[], int first, int last) < float temp, list separator; int low, high;
Далее, если нужно сортировать массив типа long, то необходимо написать еще одну функцию. Как станет известно, используя библиотечную функцию qsort, программа может сортировать массивы различных типов. Это оказывается возможным благодаря косвенной адресации.
501. Сортировка символьных строк
Как известно, Си дает возможность создавать массив символьных строк:
char *days[] = < "Понедельник", "Вторник", "Среда" );
Подобно тому как возникает необходимость сортировки массивов с численными значениями, может потребоваться сортировка и массива символьных строк. В следующей программе STRJ30RT.C используется метод пузырька для сортировки массива символьных строк:
#include #include #include void bubble_sort(char *array[], int size) < char *temp; int i, j; for (i = 0; i < size; i++) for (j=0; j < size; j++) if (strcmp(array[i], array[j]) < 0) < temp = array[i]; array[i] = array[j]; array[j] = temp; >> void main(void) < char *values[] = ("AAA", "CCC", "BBB", "EEE", "DDD">; int i; bubble_sort(values, 5); for (i = 0; i
При сортировке массива функция не изменяет содержимое строк, а вместо этого обменивает значения указателей на символьные строки так, чтобы элементы массива располагались по порядку.
502. Поиск в массиве с использованием функции lfind
Как мы уже знаем, при последовательном поиске выполняется просмотр элементов массива по порядку до тех пор, пока не будет найдено требуемое значение. Для поиска в массиве любого типа используется библиотечная функция Си Ifind:
#include void *lfind(const void *element, void *base, size_t *number_of_entries, size_t element_width, int (*compare)(const void *, const void *));
Как видим, список параметров функции изобилует указателями. Параметр element - указатель на значение, которое требуется найти. Параметр base-указатель наначало массива. Параметр number_of_entries - указатель на количество элементов массива. В параметре element_width указывается количество байтов на каждый элемент массива. Наконец, параметр compare - указатель на другую функцию, которая сравнивает два элемента массива. В отличие от функций, рассмотренных ранее и возвращающих индекса массиве найденного элемента, функция Ifind возвращает указатель на требуемый элемент массива или NULL, если значение не найдено. В следующей nporpaммe LFIND.C функция lfind используется для поиска значения типа int и значения типа float:
#include #include int compare_int(int *a, int *b) < return(*a - *b); >int compare_float(float *a, float *b) < return((*a == *b) ? 0: 1); >void main(void)
Как уже обсуждалось ранее, несоответствия типов были препятствием для поиска и сортировки. Теперь при использовании указателей этого можно избежать.
503. Поиск в массиве с использованием функции /search
В С502 для поиска в массиве элемента с требуемым значением использовалась функция Iflnd. Если требуемый элемент найден, функция возвращает указатель на него, иначе возвращает 0. В программе может понадобиться добавить значение в массив, если оно не найдено. В этом случае удобно использовать функцию lsearch:
#include void *lsearch(const void *element, void *base, size_t *number_of_entries, size_t element_width, int (*compare)(const void *, const void *));
В следующей программе LSEARCH.С используется функция Isearch для поиска значения 1001 в массиве. Если это значение не найдено, то функция Isearch добавляет его в массив:
#include #include int compare_int(int *a, int *b) < return(*a - *b); >int compare_float(float *a, float *b) < return((*a == *b) ? 0: 1); >void main(void) < int int_values[10] = ; int *int_ptr, int_value = 1001, elements =5, i; printf("Содержимое массива до поиска\n"); for (i = 0; i
Как можно видеть, при добавлении значения в массив функция Isearch изменяет и значение параметра, в котором указывается количество элементов массива.
Примечание: При использовании функции Isearch необходимо предусмотреть дополнительное место в массиве для добавляемых значений.
504. Поиск в отсортированном массиве с использованием функции bsearch
В С489 мы видели, что при двоичном поиске в отсортированном массиве местоположение требуемого значения определяется путем деления пополам количества элементов исследуемого массива с каждой итерацией. Для операции двоичного поиска используется функция bsearch:
#include void *lfind(const void *element, void *base, size_t *number_of_entries, size_t element_width, int (*compare)(const void *, const void *));
Как можно видеть, в функции очень часто используются указатели. Параметр element - указатель на значение, которое требуется найти. Параметр base - указатель на начало массива. Параметр number_of_entries - указатель на количество элементов массива. В параметре element_width указывается количество байтов, занимаемое каждым элементом массива. Наконец, параметр compare - указатель на другую функцию, которая сравнивает два элемента массива. В отличие функций, рассмотренных ранее и возвращающих индекс найденного элемента в массиве, функция bsearch возвращает указатель на элемент массива, содержащий требуемое значение или NULL, если значение не найдено. В следующей программе BSEARCH.C функция bsearch используется для поиска значения типа int и значения типа float:
#include #include int compare_int(int *a, int *b) < return(*a - *b); >int compare_float(float *a, float *b) < return((*a == *b) ? 0: 1); >void main(void)
Примечание: Для использования функции bsearch необходимо, чтобы значения элементов массива были отсортированы в порядке возрастания.
505. Сортировка массивов с использованием функции qsort
Из С498 нам известно, что при сортировке по методу быстрой сортировки массив рассматривается как список элементов. Метод быстрой сортировки путем разделения элементов массива на списки меньшего размера оказывается очень эффективным. Для того чтобы сортировать массивы всех типов с использованием алгоритма быстрой сортировки, библиотека Си предоставляет функцию qsort:
#include void *qsort(void *base, size_t number_of_entries, size_t element_width, int (*compare)(const void *, const void *));
Как можно видеть, в прототипе функции очень часто используются указатели. Параметр base -указатель на начало массива. Параметр number_of_entries - указатель на количество элементов массива. В параметре element_width указывается количество байтов, занимаемое каждым элементом массива. Наконец, параметр compare - указатель на другую функцию, которая сравнивает два элемента массива следующим образом:
*а < *b // Возврат значения < О *а == *а // Возврат О *а >*b // Возврат значения > О
В следующей программе QSORT.C функция qsort используется для сортировки значений типа int и значений типа float:
#include #include int compare_int(int *a, int *b) < return(*a - *b); >int compare_float( float *a, float *b) < if (*a < *b) return(-1); else if (*a == *b) return (0); else return(1); >void main(void)
506. Определение количества элементов массива
Для многих из рассмотренных ранее функций, работающих с массивами, в качестве параметра требуется количество элементов массива. При изменении количества элементов массива можно упростить необходимые изменения в программе, если использовать константу:
#define NUM_ELEMENTS 5
Кроме того, в программе можно использовать операцию sizeofдля определения количества элементов массива:
elements = sizeof(array) / sizeof(array[0]);
В следующей программе NUM_ELS.C этот метод используется для вывода на экран количества элементов массива:
#include void main(void) < int int_values[] = ; float float_values[1 = (21.1, 13.3, 22.2, 34.4, 15.5); printf("Количество элементов массива int_values %d\n", sizeof(int_values) / sizeof(int_values[0])); printf("Количество элементов массива float values %d\n", sizeof(float_values) / sizeof(float_values[0])); >
507. Указатель как адрес
В разделе "Язык Си: начало" этой книги понятие переменная определялось как имя ячейки памяти, в которой хранится значение указанного типа. Каждая ячейка памяти имеет свой уникальный адрес. Указатель - это переменная, значениями которой являются адреса. В языке программирования Си указатели используются очень часто. При передаче функции массива или строки в качестве параметра Си-компилятор в действительности передает указатель. Подобным образом, если функция должна изменить значение параметра, то программа должна передавать функции указатель на память, выделенную переменной. В дальнейшем это описывается более подробно.
508. Определение адреса переменной
Указатель - это адрес участка памяти. При работе с массивами (или строками) программа работает с указателем на первый элемент массива. Для определения адреса переменной программа может использовать операцию нахождения адреса (&). Например, в следующей программе ADDRESS.С операция нахождения адреса используется для вывода на экран адресов различных переменных:
#include void main(void)
В этой программе используется формат вывода %х, ввиду чего она может запускаться только в малой (Small) модели памяти. Это же относится ко всем последующим программам этого раздела. - Прим. перев.> После компиляции и выполнения программы на экран выводится:
C:\> ADDRESS Адрес переменной count is fff4 Адрес переменной salary is fff0 Адрес переменной distance is ffec
509. Массив как указатель
Как указывалось ранее, компилятор Си рассматривает массивы как указатели. Например, при передаче массива в функцию в качестве фактического параметра компилятор передает начальный адрес массива. В следующей программе ARRAYADD.C выводятся начальные адреса различных массивов:
#include void main(void) < int count[10]; float salaries[5]; long distances[10]; printf("Начальниц адрес массива count - %x\n", count); printf("Начальный адрес массива salaries - %x\n", salaries); printf("Начальный адрес массива distances - %x\n", distances);
После компиляции и выполнения программы на экран выводится:
C:\> ARRAYADD Начальный адрес массива count - ffe2 Начальный адрес массива salaries - ffce Начальный адрес массива distances - ffa6
510. Операция нахождения адреса (&) для массива
Как мы уже знаем Си-компилятор рассматривает массив как указатель на первый элемент этого массива. В С508 было показано использование операции нахождения адреса (&) для получения адреса переменной. Если эта операция применяется к массиву, то Си возвращает начальный адрес массива. Таким образом, для массива операция нахождения адреса является избыточной. рта операция все же может использоваться при объявлении указателя на массив. - Прим. перев.] В следующей программе ARRAYTWO.C выводится начальный адрес массива и указатель на массив, возвращаемый операцией &:
#include void main(void)
После компиляции и выполнения программы на экран выводится:
C:\> AKRAYTWO Начальный адрес массива count - ffe2 &count есть ffe2 Начальный адрес массива salaries - ffce &salaries есть ffce Начальный адрес массива distances - ffa6 &distances есть ffa6
511. Объявление указателей
По мере усложнения программы вы обнаруживаете, что в основном работаете с указателями. Для хранения указателей в программе необходимо объявить соответствующие переменные (переменная-указатель). Для объявления указателя необходимо указать тип значения, на которое указывает указатель (например, int-, float, char и т. д.) и звездочку (*) перед именем переменной.
Например, в следующем операторе объявляется указатель типа int:
int *iptr;
Перед использованием указателя (как и любой переменной) ему необходимо присвоить значение. При присваивании значения указателю ему в действительности присваивается некоторый ^дрес. Предположим, что ранее в программе объявлена переменная int count; в следующем операторе указателю iptr присваивается значение адреса переменной count:
iptr = &count; // Присваивание переменной iptr адреса count
В следующей программе IPTR.C объявляется переменная-указатель iptr, затем этому указателю присваивается адрес переменной count. После этого на экран выводится как значение переменной iptr, так и адрес count:
#include void main(void) < int *iptr; // Описание переменной-укаэателя int count = 1; iptr = &count; printf("Значение iptr равно %х Значение count равно %d" " Address of count равен %х\n", iptr, count, &count); >
После компиляции и выполнения программы на экран выводится:
C:\> IPTR Значение iptr равно fff2 Значение count равно 1 Адрес count равен fff2
512. Доступ к значениям, адресуемым указателями
Как мы уже знаем, указатель содержит адрес, который указывает на значение заданного типа. Используя адрес, содержащийся в указателе, можно определить значение, содержащееся по этому адресу. Для этого используется операция "звездочка" С"). Например, следующим оператором выводится значение, содержащееся по указателю iptr:
printf("Значение, содержащееся по указателю iptr, ", *iptr);
Аналогично, в следующем операторе переменной count присваивается значение, содержащееся по адресу указателя iptr:
count = *iptr;
Наконец, в следующем операторе в память по указателю iptr заносится значение 7:
*iptr = 7;
Примечаиие: Для того чтобы использовать значение, лежащее в ячейке памяти, на которую ссылается указатель, применяется операция "звездочка" (*).
513. Использование значений по указателю
В С512 мы видели, что переменной-указателю можно присвоить адрес, используя операцию адресации (&). Там же была представлена операция "звездочка" (*) для доступа к значению по адресу, на который ссылается указатель. В следующей программе PTR_DEMO.C указателю iptr назначения типа int присваивается адрес переменной counter. После этого программа выводит на экран значение указателя и значение, которое хранится по указателю (значение counter). Далее программа изменяет значение по указателю:
#include void main(void) < int counter = 10; int *iptr; // Объявление указателя iptr = &counter; // Присвоение указателю адреса printf("Адрес в iptr %х Значение в *iptr %d\n", iptr, *iptr) *iptr = 25; // Изменение значения в памяти printf("Значение counter %d\n", counter);
514. Указатели как параметры функций
Процесс передачи параметров в функциях рассматривался подробно в разделе "Функции". Как станет известно в дальнейшем, если значение параметра должно изменяться, то в функцию передается указатель на параметр. В следующей программе SWAPVALS.C используются указатели на два параметра типа int для операции обмена значениями пере менных:
#include void swap_values(int *a, int *b) < int temp; temp = *a; // Временная переменная для хранения значения а *а = *b; // Значение Ь присваивается а *b = temp; // Значение а присваивается Ь >> void main(void)
Как видно, внутри функции для нахождения значения по указателю используется операция 'звездочка" (*). Программа передает адреса переменных в функцию, используя операцию адресации (&).
515. Арифметические операции над указателями
Указатель - это адрес, по которому хранится значение определенного типа. Другими словами, указатель - значение, указывающее на определенную ячейку памяти. Если к указателю прибавить число 1, то он будет ссылаться на следующую ячейку памяти. Если к указателю прибавить 5, то он будет ссылаться на ячейку, отстоящую от текущего адреса на пять ячеек выше. Арифметика указателей не так проста, как кажется на первый взгляд. Предположим для примера, что значение указателя равно 1000. Если к указателю прибавить 1, то ожидаемый результат - адрес 1001. В действительности результат зависит от типа указателя. Например, если указатель имеет тип char, то результатом сложения действительно будет 1001. Однако если прибавить 1 к указателю типа int (тип, требующий 2 байта памяти), то результирующий адрес будет 1002. Аналогично, если прибавить 1 к указателю типа float (тип, требующий 4 байта памяти), то результирующий адрес будет 1004. При выполнении арифметики над указателями необходимо иметь в виду тип указателя. Кроме рассмотренной операции сложения указателя и числа, арифметика над указателями допускает следующие операции: нахождение разности указателя и числа, нахождение суммы и разности двух указателей. В дальнейшем приводятся примеры с использованием различных арифметических операций над указателями.
516. Увеличение и уменьшение указателя на 1
При работе с указателями одна из самых часто используемых операций - увеличение и уменьшение значения на 1 для обращения к следующему или предыдущему участку памяти. В следующей программе PTRARRAY.C указателю iptr присваивается начальный адрес массива. Затем значение указателя в цикле увеличивается на 1 и, таким образом, выводятся на экран значений 5 элементов массива:
#include void main(void) < int values[5] = ; int counter; int *iptr; iptr = values; for (counter = 0; counter < 5; counter++) < printf("%d\n", *iptr); iptr++; >>
После компиляции и выполнения программы на экран выводятся числа: от 1 до 5. Сначала указателю присваивается начальный адрес массива. Затем программа увеличивает значение указателя на 1 для того, чтобы он ссылался на следующий элемент массива и т.д.
517. Комбинирование операций доступа по указателю и приращения
В С516 для вывода значений элементов массива был использован указатель iptr в цикле for.
for (counter =0; counter
Как видно, в цикле for происходит обращение к элементу массива и увеличение на 1 указателя для доступа к следующему элементу массива. Используя постфиксную операцию увеличения, эти две операции можно записать одной строкой. В следующем фрагменте в цикле for сначала значение по указателю выводится на экран, и лишь затем значение самого указателя увеличивается на 1.
for (counter = 0; counter < 5; counter++) printf("%d\n", *iptr++);
518. Использование в цикле указателя на строку
В разделе "Строки" указатели используются достаточно часто. Как нам известно, строка - массив символов, оканчивающийся NULL-СИМВОЛОМ. В следующей программе STR_PTR.C определяется функция show_string для вывода символов строки с использованием указателя на строку:
#include void show string(char *string) < while (*string) putchar(*string++); >void main(void) < show_string("1001 совет по C/C++"); >
Как видим, переменная string объявлена в функции как указатель. Указатель увеличивается в простом цикле, до тех пор пока не встретится конец строки. Перед тем как вывести очередной символ, этот символ определяется в функции посредством операции (*). Затем указатель увеличивается, тем самым ссылаясь на следующий символ.
519. Функции, возвращающие указатели
Известно, что функции могут возвращать в программу указатели. Возвращаемые значения функции должны быть всегда одного и того же типа: inl, float или char. Кроме возвращения указателей на значения базовых типов, функция может возвращать указатель на структуру. Например, функция fopen, которую большинство программ Си используют для открытия файлового потока, возвращает указатель на структуру типа FILE:
FILE *fopen(const char *pathname, const char *mode);
Подобным образом, многие функции, представленные в разделе "Строки", возвращают указатель на символьные строки. При просмотре объявлений функций, представленных в этой книге, обратим внимание на функции, которые возвращают "указатель на значение", а не значение базового типа.
520. Создание функции, возвращающей указатель
Из С519 ясно, что многие библиотечные функции Си возвращают указатели. По мере усложнения программы приходится создавать функции, которые возвращают указатели на определенные типы. Например, в следующей программе PTR_UPR.C создается функция string_uppercase, которая преобразует все строчные латинские буквы в прописные и возвращает указатель на строку [строки в данной программе не переведены, поскольку функция toupper не преобразует русские буквы. - Прим. перев.>:
#include #include char *string_uppercase(char *string) < char *starting_address; starting_address = string; while (*string) toupper(*string++); return(starting_addresa); >void main(void) < char *title = "1001 Совет по C/C++"; char *string; string = string_uppercase(title); printf("%s\n", string); printf("%s\n", string_uppercase("Arrays and Pointers")); >
Как можно видеть, для создания функции, возвращающей указатель, перед именем функции необходимо поместить символ "звездочка" (*).
char *string uppercase(char * string);
521. Массивы указателей
О массивах в этом разделе уже достаточно подробно рассказывалось. До сих пор все представленные массивы имели один из базовых типов Си (например, int, float или char). Возможные типы массивов в Си этим не ограничиваются. Так же, как функции, возвращающие указатели, можно создавать и массивы указателей. Наиболее частое использование массивов указателей-массивы строк. Например, в следующем объявлении создается массив weekdays(wn недели), который содержит указатели на символьные строки:
char *weekdays[7] = < "Воскресение","Понедельник", "Вторник", "Среда","Четверг","Пятница", "Суббота" >;
При просмотре массива справа налево находим, что он содержит 7 элементов. Символ "звездочка" (*) перед именем означает указатель. При комбинации имени типа char, "звездочки" и объявления массива получается массив указателей из 7 символьных строк. Массив символьных строк argv используется особенно часто. Его объявление дано в разделе "Переназначение ввода-вывода и обработка командной строки". Этот массив содержит командную строку запуска программы.
Примечание: При объявлении массива символьных строк компилятор Си не добавляет NULL в конце массива, как это делается для самих символьных строк.
522. Размещение в памяти массива символьных строк
Как мы уже знаем, Си рассматривает массивы как указатели на начало самих этих массивов в памяти. В С521 был создан массив символьных строк weekdays, который содержал дни недели. При создании массива символьных строк компилятор Си хранит указатели на строки массива. На рис. 522 показано, как компилятор Си хранит массив letters, объявление которого имеет вид.
char *lettere[4] = ("ААА", "ВВВ", "ССС", "DDD">;
Рис. 522. Си хранит массив строк как массив указателей
Примечание: При объявлении массива символьных строк компилятор Си не добавляет NULL в конце массива, как это делается для самих символьных строк.
523. Использование в цикле массива символьных строк
Итак, при создании массива символьных строк Си хранит в элементах массива указатели на каждую строку. В следующей программе WEEKDAYS.C массив weekdays, содержащий указатели на строки (названия дней недели), просматривается в цикле:
#include void main(void) < char *weekdays[7] = < "Воскресение","Понедельник", "Вторник", "Среда","Четверг","Пятница", "Суббота" >; int i; for (i = 0; i
Как вы видите, в программе используется цикл по элементам массива; для вывода на экран строк используется спецификатор формата %s.
524. Символьная строка как указатель
Массивы в Си рассматриваются как указатели на начальные элементы этих массивов. В разделе "Строки" встречается объявление символьной строки следующего вида:
char *string;
Мы уже знаем, что Си дает возможность создавать массивы строк. В следующем объявлении, например, создается массив workdays, предназначенный для хранения 5 символьных строк:
char *workdays[5];
Поскольку в объявлении создается массив, Си дает возможность обращения к массиву с использованием указателя. Для этого необходимо объявить переменную-указатель, которая ссылается на массив символьных строк. Описание будет иметь вид:
char **work_day_ptr;
Сдвоенный символ "звездочка" (*) обозначает, что work_day_ptr есть указатель на указатель символьных строк. Такие указатели используются в разделе "Переназначение ввода-вывода и обработка командной строки".
525. Указатель на указатель символьных строк
В С524 были представлены указатели на указатели символьных строк, которые объявляются следующим образом:
char **work_day_ptr;
В следующей программе WORKDAYS.C указатель на указатель символьных строк используется для вывода на экран строк массива workdays:
#include void main(void) < char *workdays[] =; char **work_day; work_day = workdays; while (*work_day) printf("%s\n", *work_day++); >
Сначала в программе указателю work_day присваивается начальный адрес массива workdays (адрес строки "Понедельник"). Затем цикл выполняется до тех пор, пока не встретится указатель на пустую строку (используемую в качестве конца массива).
Примечание: При объявлении массива символьных строк компилятор Си не добавляет пустую строку в конце массива, как это делается для.самих символьных строк. Поэтому в объявлении массива workdays пустая строка добавлена явно, что, таким образом, дает возможность выхода из цикла в программе.
526. Описание строковой константы через указатель
Иногда в этой книге строковые переменные инициализируются в самом объявлении, например:
char titled = "1001 совет по C/C++";
При объявлении массива таким способом компилятор Си выделяет достаточно памяти для хранения всех символов, включая NULL-СИМВОЛ в конце строки, присваивая переменной title указатель на первый символ. Поскольку компилятор Си автоматически выделяет требуемую память и работает с указателем на память, программа может использовать указатель на символьную строку вместо массива:
char *title = "1001 совет no C/C++";
527. Указатель void
Как известно, при объявлении переменной-указателя необходимо указывать его тип (например, int, float и char). Указание типа позволяет компилятору корректно выполнить операции сложения и вычитания с указателем, умножая добавляемую или вычитаемую величину на длину типа для получения смещения. В некоторых случаях, однако, этого не требуется. Может оказаться, что в программе достаточно просто получить указатель памяти, с которым она будет работать далее по назначению.
В таком случае программа может создавать указатель типа void:
void *meinory_pointer;
При просмотре библиотечных функций, представленных в разделе "Работа с памятью", можно заметить, что некоторые функции возвращают указатель на тип void. Для таких функций существенно то, что они возвращают указатель на область памяти, для которой (при компиляции) нет информации о том, какого типа данные должны содержаться в памяти.
528. Указатели на функции
Как известно, Си дает возможность создавать указатель на все типы данных (такие как int, char, float и даже символьные строки). Кроме этого, Си дает возможность создавать и использовать указатели на функции. Наиболее общее использование указателей на функции - возможность передать адрес функции в качестве фактического параметра другой функции. Указатели на функции объявляются следующим образом:
int (*min)(); int (*max)(); float (*average)();
Обратим внимание наскобки, охватывающие имена переменных. Без них приведенные объявления есть прототипы функций, которые возвращают указатель на указанный тип:
int *min(); int *max(); float * average;
При чтении объявления переменной необходимо начинать с самого вложенного объявления переменной, заключенного в скобки, и затем - слева направо:
int (*min) ();
529. Использование указателя на функцию
В С528 была рассмотрена возможность языка Си по созданию указателей на функции. Наиболее общее использование указателей на функции - возможность передать адрес функции в качестве фактического параметра другой функции. Ранее в этом разделе мы рассматривали библиотечные функции Си сортировки и поиска. При сортировке значений по возрастанию библиотечной функции передается в качестве параметра некоторая своя функция сравнения. При сортировке значений в обратном порядке передается другая функция. В следующей nporpaммe PASSFUNC.C функции get_result передаются в качестве параметров функции (т.е. их адреса) min и max. Возвращаемое значение функции get_result зависит от того, какая функция передается:
#include int get_result(int a, int b, int (*compare)()) < return(compare(a, b)); // Вызов передаваемой функции >int max(int a, int b) < printf("Вызов функции max\n"); return((a >b) ? a: b); > int min(int a, int b) < printf("Вызов функции min\n"); return((a < b) ? a: b); >void main(void)
530. Указатель на указатель на указатель .
Си дает возможность создавать переменные-указатели, которые сами являются указателями. Вообще говоря, глубина ссылки (указатель на указатель) в программе не ограничивается. Однако для большинства программистов использование глубины ссылок, большей, чем указатель на указатель, приводит к путанице и делает программу очень трудной для понимания. Например, в следующей программе PTRTOPTR.C используется 3 уровня указателей на int. Даже в небольшой программе приходится тратить немало времени на понимание уровней ссылок, чтобы уяснить выполняемую обработку данных:
#include int what_is_the_value(int ***ptr) < return(***ptr); >void main(void)
531. Структуры
Мы уже знаем, что массив - это переменная, которая дает возможность хранить в программе множество значений одного и того же типа. Другими словами, массив дает возможность сгруппировать связанную информацию в одном имени, например, рейтинг 100 человек или зарплата 50 служащих. По мере усложнения программы возникает необходимость группировать связанную информацию различных типов данных. Например, предположим, что программа работает с информацией о служащих. Для каждого служащего необходимо поддерживать следующие данные:
char name[64]; // Фамилия int age; // Возраст char ssan[ll]; // Социальный номер служащего int pay_grade; // Разряд по оплате float salary; // Зарплата unsigned employee_number; // Номер служащего
Предположим, что в программе есть несколько различных функций, которые работают с этой информацией. Каждый раз при вызове любой из этих функций необходимо следить за тем, что ей переданы все параметры, причем в указанном порядке. Как обсуждалось в разделе "Функции", чем больше у функции параметров, тем труднее программа для восприятия и больше вероятность ошибки. Для упрощения программа может создать структуру, в которой связанная информация группируется под одним именем. Например, поля сведений о служащих можно сгруппировать в структуру Employee, объявление которой имеет вид:
struct Employee < char name[64]; // Фамилия int age; // Возраст char ssan[ll]; // Социальный номер служащего int pay_grade; // Разряд по оплате float salary; // Зарплата unsigned employee_number; // Номер служащего >;
Описание такого рода создает структуру типа Employee.
532. Структура как шаблон для объявления переменных
В С531 обсуждалась возможность языка Си для группирования связанной информации в структуру. Само по себе объявление структуры не создает никаких переменных. Структура есть шаблон, который программа может использовать в дальнейшем для объявления переменных. Таким образом, объявление структуры само по себе не приводит к выделению памяти. Это объявление используется компилятором только в том случае, если в программе будет объявлена переменная типа структура.
533. Тег или имя структуры
В С531 обсуждалась возможность языка Си для группирования связанных данных в структуру. Используя ключевое слово struct, программа может объявить структуру следующим образом:
struct Employee < char name[64]; // Фамилия int age; // Возраст char ssan[ll]; // Социальный номер служащего int pay_grade; // Разряд по оплате float salary; // Зарплата unsigned employee_number; // Номер служащего >; >;
В данном случае именем структуры является идентификатор Employee. Программисты-пользователи языка Си называют имя структуры тегом структуры. Как мы увидим в дальнейшем, тег структуры используется для объявления переменной указанного типа. Следующее объявление создает структуру с именем Shape:
struct Shape < int type; // 0 - окружность, 1 - квадрат, 2 - треугольник int color; float radius; float area; float perimeter; >;
534. Различные способы объявления переменных типа структура
Итак, язык Си дает возможность группировать связанные данные в структуру (С531). Само по себе объявление структуры не создает переменной, а служит шаблоном для ее объявления. Описать переменную можно двумя способами. Первый способ, пусть в программе объявлена структура типа Employee:
struct Employee < char name[64]; //Фамилия int age; //Возраст char asan[ll]; //Социальный номер служащего int pay_grade; //Разряд по оплате float salary; //Зарплата unsigned employee number; //Номер служащего
Следуя представленному определению структуры, можно объявить переменную типа Employee:
struct Employee employee_info; struct Employee new_employee, terminated_employee;
Второй способ, объявление переменной может непосредственно следовать за объявлением структуры:
struct Employee < char name[64]; // Фамилия int age; // Возраст char ssan[ll]; // Социальный номер служащего int pay_grade; // Разряд по оплате float salary; // Зарплата unsigned employee_number; // Номер служащего >employee_info, new_employee,terminated_employee;
535. Члены структуры
Как уже известно, Си дает возможность группировать связанные данные в структуру. Например, в следующем операторе создается переменная triangle с использованием структуры типа Shape:
struct Shape < int type; // 0 - окружность, 1 - квадрат, 2 - треугольник int color; float radius; float area; float perimeter; >triangle;
Каждый выделенный элемент информации структуры представляет собой отдельный член этой структуры. Структура Shape состоит из 5 членов: type, color, radius, area и perimeter. Для обращения к члену структуры используется операция "точка"(.). Например, в следующем фрагменте членам структуры присваиваются конкретные значения:
triangle.type = 2; triangle.perimeter = 30.0; triangle.area = 45.0;
536. Распределение памяти под структуры
При объявлении переменной типа заданной структуры выделяется память для всех членов структуры. Например, при объявлении переменной типа Employee с объявлением структуры из С535 выделяет память, как показано на рис. 536.
Рис. 536. Распределение памяти под структуру на примере Employee
537. Адрес структуры как фактический параметр функции
Как известно, язык Си позволяет группировать информацию в виде структуры. В разделе "Дата и время" используется функция geldate для определения текущей системной даты. Функция присваивает текущую дату членам структуры типа date:
struct date < int da_year // Год char da_day // День char da_mon // Месяц >;
В следующей программе DOSDATE.C функция getdate используется для присваивания переменной cwr_date текущей системной даты:
#include #include void main (void)
Поскольку функция должна изменить значение параметра, программа передает переменную типа "структура" по ссылке (адресу).
538. Передача структуры по значению как фактического параметра функции
В языке Си переменные типа заданной структуры могут передаваться в качестве параметров функций, как и любые другие переменные. В следующей программе STRUFUNC.C функция show_structure принимает в качестве параметра переменную типа структуры Shape. Функция show_struclure выводит на экран значения всех членов структуры:
#include struct Shape < int type; int color; float radius; float area; float perimeter; >; void show structure(struct Shape shape) < printf("shape.type %d\n", shape.type); rintf("shape.color %d\n", shape.color); rintf("shape.radius %f shape.area %f shape.perimeter %f\n", hape.radius, shape.area, shape.perimeter); >void main(void) < struct Shape circle; circle.type = 0; circle.color = 1; circle.radius = 5.0; circle.area == 22.0 / 7.0 * circle.radius * circle.radius; circle.perimeter = 2.0* 22.0 / 7.0 * circle.radius; show structure(circle); >
539. Изменение структуры внутри функции
В С538 была продемонстрирована возможность передачи переменной типа структура как параметра функции так же, как и для переменных любого типа. Если оказывается необходимым изменять значения членов структуры, то переменная должна передаваться не по значению, а по адресу (так же, как и переменная любого другого типа, значение которой требуется изменить). В следующей программе CHGSTRUC.C определяется функция change„structure, которая изменяет значения членов структуры типа Shape:
#include Struct Shape < int type; int color; float radius; float area; float perimeter; ); void change structure(struct Shape * shape) < (*shape)-type = 0; (*shape).color = 1; (*shape).radius = 5.0; (*shape).area = 22.0 / 7.0 * (*shape).radius * (*shape).radius; (*shape).perimeter = 2.0 * 22.0 / 7.0 * (*shape).radius; >void main(void)
Для передачи структуры как параметра функции используется указатель на переменную типа структура. Внутри функции доступ к членам структуры осуществляется посредством операции "звездочка" (*) -извлечения значения по указателю:
(*pointer).member = value;
Эта операция рассматривается в С540.
540. Операция (*указатель).член
Если в функции необходимо изменять значения переменной типа структура, то программа должна передавать указатель на эту переменную. Внутри функции содержимое по этому указателю определяется с помощью операции "звездочка" (*):
(*pointer).member = value;
В этом выражении сначала выполняется операция в скобках - "звездочка" (*), которая определяет адрес в памяти, отведенной для переменной типа структура. По имени члена компилятор Си добавляет к адресу смещение. Если скобки не указаны:
*pointer.member = value;
Си предполагает, что член структуры сам является указателем и операция "звездочка" (*) применяется к члену структуры. Такой синтаксис может быть корректен в том случае, если член структуры объявлен как указатель, например:
struct Planet < char name[48]; int *some_pointer; >planet;
Как видно, второй член структуры является указателем на значение типа int. Предполагая, что указатель был присвоен некоторому адресу памяти, следующий оператор заносит по этому адресу значение 5:
*planet.some pointer = 5;
541. Формат указатель->член
В С540 мы видели, что если в функции необходимо изменять значения переменной типа структура, то программа должна передавать указатель на эту переменную. Для нахождения содержимого адреса внутри функции Си поддерживает два формата. Первый формат был использован в С540 - обращение к члену структуры следующим образом:
(*pointer).member = value; some_value = (*pointer).member;
Второй формат имеет вид:
pointer->member = valued; some_value = pointer->member;
В следующей программе CHMEMBER.C функция change Jstructure использует второй формат для обращения к членам структуры, передаваемой функции по адресу.
#include struct Shape < int type; int colors; float radius; float area; float perimeter; >; void change_structure(struct Shape *shape) < shape->type = 0; shape->color = 1; shape->radius = 5.0; shape->area = 22.0 / 7.0 * shape->radius * shape->radius; shape->perimeter =2.0 * 22.0 / 7.0 * shape->radius; > void main(void)
542. Структура без тега
Тег структуры - это имя структуры. Тег структуры используется при объявлении переменной типа этой структуры. Однако при объявлении переменной типа структура тег указывать необязательно. Например, в следующем фрагменте объявлены две переменные типа структуры:
struct < int type; // 0 - окружность, 1 - квадрат, 2 - треугольник int colors; float radius; float area; float perimeter; >triangle, circle;
Если в программе не требуется обращения к структуре по имени (например, в прототипе функции или формальном параметре), то тег структуры можно не указывать. Однако наличие тега дает возможность другим программистам, читающим вашу программу, понять назначение структуры. Включение содержательных имен структур делает программу более удобочитаемой.
543. Область видимости объявлений структуры
В разделе "Функции" понятие область видимости раскрывалось как область программы, в которой объявление идентификатора (будь то переменная или функция) является доступным. При определении структуры необходимо иметь в виду область ее видимости. Можно было обратить внимание на то, что в программах, которые работают со структурами и функциями, объявления структур предшествуют объявлениям функций. В результате объявления структуры имеют глобальную область видимости, что дает возможность любой функции.обращаться к нему. Если структура объявлена внутри main, то единственной функцией, в которой доступно это объявление, является main. Если необходимо использовать объявление структуры в нескольких функциях, структура должна быть объявлена вне функций.
544. Инициализация структуры
Подобно массивам, Си дает возможность инициализировать структуры непосредственно при их объявлении. В следующей программе INITSTRU.C объявляется и инициализируется переменная типа структуры Shape:
#include void main(void) < struct Shape < int type; int color; float radius; float area; float perimeter; >circle =
Поскольку структура используется только в функции main, она объявлена внутри main.
545. Ввод-вывод переменных типа структура
В приведенных примерах для вывода значений одного или нескольких членов структуры использовалась функция printf. При выполнении операций вывода на экран или ввода с клавиатуры, изменяющих члены структуры, для каждого члена необходимо выполнить отдельную операцию ввода-вывода. Однако при чтении структуры из файла или записи в файл можно работать со всей структурой. Для чтения и записи структур через файловый поток можно использовать функции fread и fwrite. В разделе "Файлы, директории и дисковые устройства" демонстрируется использование этих функций для ввода-вывода структур. При выполнении операций ввод-вывода структур с использованием идентификатора файла применяются функции read и write. Для компилятора Си структура, с точки зрения ее хранения в памяти, - это просто массив байтов. Все упомянутые функции ввода-вывода читают или записывают непрерывную последовательность байтов. При использовании этих функций со структурами необходимо просто передавать функции адреса структур.
546. Вложенные структуры
struct Employee < char name[64]; // Фамилия int age; // Возраст char ssan[ll]; // Социальный номер служащего struct Date < int day; int month; int year; >hire_date; int pay_grade; // Разряд по оплате float salary; // Зарплата unsigned employee_number; // Номер служащего > new_employee;
Для обращения к члену вложенной структуры используется операция "точка" (.), сначала указывается член внешней структуры, затем член вложенной структуры:
new_employee.hire_date.month = 12;
547. Структура, содержащая массив
Типы членов структуры могут быть любыми, в том числе структурами или массивами. Если член структуры - массив, то обращаться к нему можно по имени, которому должна предшествовать операция "точка" (.). В следующей программе STRUARRA.C инициализируются поля структуры, включая массив. В программе выполняется цикл для вывода на экран всех элементов массива:
#include void main(void) < struct Date < char month_name[64]; int month; int day; int year; >current_date = ( "Июль", 7, 4, 1994 ); int i; for (i = 0; current_date.month_name[i]; i++) putchar(current_date.month_name[i]); >
548. Создание массива структур
Как известно, использование массивов дает возможность хранить множество значений одного и того же типа. Большинство массивов этого раздела имеет тип int, float или char. Однако Си дает возможность объявить массив типа структуры. Например, в следующем объявлении создается массив, в котором хранятся сведения о 100 служащих:
struct Employee < char name[64]; // Фамилия int age; // Возраст char ssan[ll]; // Социальный номер служащего int pay grade; // Раэряд по оплата float salary; // Зарплата unsigned employee_number; // Номер служащего >staff[100];
Если предположить, что значения для каждого служащего уже проинициализированы, то в следующем фрагменте внутри цикла for выводится имя и номер каждого служащего:
for (emp = 0; emp < 100; emp++) printf("Номер служащего %s - %d\n", staff[emp].name, staff[emp].employee_number);
При использовании массива структур операция "точка" (.) применяется ко всем элементам массива.
Массив структур в программах 1С
В данной статье будет описано что такое массив, структура и как создавать различные массивы структур в 1С, на примерах.
Массив – это некоторый набор значений. В одном массиве могут находиться значения нескольких типов. Массивы практически используются в 1С чаще всего для проведения отборов и сортировок, а также чтобы сохранять какой-либо перечень со значениями, для дальнейшей эксплуатации.
Структура – это некоторая совокупность из пар. «Значение» - это любой тип данных, «Ключ» - это строковой тип данных. Данная опция необходима для сохранения и отправки параметров.
Так как структура по сути является набором элементов с разными типами и именем, то можно создать массив структур.
Соединение двух инструментов: массив и структура, - сделает проще отправку больших массивов схожих данных с сервера на клиент и наоборот. В таком случае элементы массива – это и есть структура информационного массива.
Опишем поэтапно, как будет появляться и из чего состоит массив структур в программах 1С.
Рассмотрим некоторый тип массива структур, который является простым, в нём, нумерация будет начинаться с нуля:
Простой тип массива структур А массив с неизвестным количеством элементов будет иметь следующий вид, как представлено на части кода ниже:
Массив структур в 1с с неизвестным количеством элементов Таким образом, приведём описание количества элементов в массиве:
Описание количества элементов массива структур в 1С Следующим этапом будет создание многомерного массива данных, что представлено на скриншоте с примером кода ниже:
Создание многомерного массива данных Поняв принципы работы и классификацию массивов, можно создать массив структур, он будет следующим:
Создание массива структур в 1С Перебор значений в таком массиве будет осуществляться таким же, одним из стандартных, способов: · Первый способ:
Способ перебора массива структур в 1С · Второй способ:
Перебор значений массива структур в 1С
2. Практическое использование массива структур в 1С
Рассмотрим пример сложнее: пусть необходимо получить реквизиты по каким-то объектам, которые находятся на сервере. В этом случае необходимо отправить на сервер некоторый массив с ссылками на нужные объекты, а с серверной стороны – необходимо получить массив с структурами. Решение приведено ниже, на скриншоте с кодом программы с сортировкой массива структур:
Сортировка массива структур Далее рассмотрим задачу, в которой необходимо произвести преобразование таблицы, с некоторыми значениями, в массив структур, при этом перебирать коллекцию из столбцов нет возможности, для каждой строки из таблицы. Решение будет следующим, как можно увидеть, массив структур очень упрощает преобразование элементов в массиве:
Преобразование элементов в массив В данной статье было описано, как работают массив и структура в 1С и где они используются, а также показано, как создавать массив структур поэтапно, и описано то, в каких задачах такой инструмент может сэкономить память системы и время выполнения задачи. Также было показано применение массива структур, относительно преобразования данных в массив структур.
Специалист компании «Кодерлайн» Айдар Фархутдинов