clang_slide

Программирование на С — структура данных vector

Наконец-то, начинаю цикл статей о том, что меня больше всего привлекает - программирование.

Цепочка статей будет посвящена чистому языку С. Прежде чем начать программировать на языках с объектным подходом, необходимо научится хорошо это делать на процедурных языках. Язык С - это необходимая классика, которую нужно понимать, поскольку идеи данного языка лежат в основе многих других, в том числе объектно-ориентированных языках. Не говоря даже о синтаксисе, основной каркас которого, по сути, стал неким стандартом в мире программирования.

Поэтому, начнем с простого, но нетривиального для данного языка. А именно - структура данных вектор на основе динамических массивов. Вообще понятие структуры данных свойственно больше объектным языкам, где очень удобно воспользоваться классом для этой задачи. Но вот вдруг нам хочется написать игру под Linux с использованием библиотеки для подобия графики в терминалах ncurses и хранить структуры пуль в векторе? Но в таком случае хочется удобной и сладкой жизни с удобным интерфейсом для работы с памятью и массивами. Поэтому сделаем некое подобие вектора (vector в C++, ArrayList в Java и т.д) на основе структуры с несколькими полями, которая будет заменять нам объект вектора, и обычных функций, которые будут работать со "структурой-объектом" и выполнять роль методов класса. Сразу стоит отметить, что программировать будем как минимум на 99 стандартах языка, где уже есть for и т.д.

Углубляться в теоретические подробности - что за такая структура данных вектор, какая у нее трудоемкость поиска элемента и других операций - мы не будем. Это важно и нужно знать, но подобная теория неплохо разжевана и много где ее можно найти и почитать. У нас в данный момент присутствует лишь практический интерес к реализации этого дела на функциональном языке.

И так, начнем - сперва сделаем файлик заголовка, в котором будем все нужное нам объявлять. Я назвал cvector.h. В нем мы то как раз и объявим нужную нам структуру, которая будет выполнять роль объекта вектора. Какие поля нам нужны? Кончено же массив для хранения данных. Но мы делаем вектор, который должен хранить любой тип данных. Поэтому создаем массив указателей на тип void - void** data. Следующее поле - текущий размер вектора - сколько элементов в данный момент находится в векторе. Это просто целочисленная переменная - int size. Добавили элемент - увеличили size, удалили - уменьшили size. Далее мы должны хранить реальный, физический размер массива данных. То есть сколько элементов данных в нем реально может лежать. Поскольку вектор - структура динамическая, то эта величина физического размера массива также будет постоянно меняться. Обойдемся простым int capacity. И последнее поле - это размер элемента данных в байтах. Мы не знаем, какой тип будет лежать в векторе но, сколько выделять памяти под единицу элемента данных мы знать должны.

Пока вот так выглядит у меня заголовочный файл:

Теперь подумаем над интерфейсом вектора: какие функции у нас должны быть. Тут долго думать не будем, вектор должен уметь добавлять, удалять, изменять, предоставлять элементы, предоставлять текущий размер, менять свои размеры, очищаться. И конечно перед использованием вектор нужно инициализировать.

Вот так выглядит заголовочный файл со всеми объявлениями нужных нам функций:

Теперь перейдем к реализации. В файле cvector.c загружаем наш заголовочный файл cvector.h (#include "cvector.h"), и начинаем функцию за функцией реализовывать. Полный код работающих функций приведен ниже, особо комментировать реализацию я не буду, поскольку она проста. Поясню лишь несколько моментов. Каждая функция, которая работает с вектором - получает указатель на нашу структуру "объекта" вектора, через которую функции получают доступ к массиву с данными, и проводят манипуляции с данным массивом. В функциях cvector_push и cvector_set вызывается memcpy(). Эта функция копирует данные из входных аргументов указателей в массив с данными. Зачем? Как мне верно подметили и подсказали коллеги, может случиться такая ситуация: представим, что данные мы не копируем. Где-нибудь в программе, где у нас используется данная реализация вектора без копирования, мы в определенной функции создали локальную переменную и решили ее сохранить в нашем векторе. Поскольку в векторе мы храним указатели, то при попытке доступа к данным по указателю, мы обращаемся в определенную область памяти. Если у нас локальная переменная - то время жизни памяти этой переменной - время работы функции, в которой она была объявлена. Поэтому после завершения функции, локальные переменные уничтожаются, память очищается автоматически, доступ к ней невозможен и подобная попытка завершается ошибкой. А в векторе то у нас указатели! Поэтому при попытке обращения по указателю к локальной переменной мы получаем обвал программы. Именно поэтому нужно копировать данные, чтобы избежать подобных ситуаций.

Также стоит взглянуть на реализацию функции удаления: cvector_delete(). При вызове этой функции, происходит очищение памяти указателя под номером __index. Далее в цикле происходит переприсваивание указателей, которые находятся дальше элемента __index на -1 позицию. То есть, мы просто сдвигаем все элементы, которые дальше _index влево на единицу. Таким образом, у нашего вектора осталась дырка в конце, обращение к которой непозволительно. Поэтому вызываем функцию перестроения размера массива указателей - вызов realloc с входным размером size - 1. То есть мы убираем эту дырку, поскольку realloc очистит нам все, что не входит в размер size - 1.

Это рабочая версия аналога структуры данных вектора. Я тестировал все функции, работало все исправно. В случае, если будут найдены баги, буду признателен за оповещения меня и всех остальных читателей в комментариях.

Вот пример программы использования данного вектора с целочисленными элементами:

Результат работы программы приведен на картинке ниже:

cvector_test

На этом все. Код в удобном виде вы можете найти на моем репозитории на Github: https://github.com/Tetraquark/Cvector. В случае если есть вопросы или замечания, дополнения - пожалуйста, пишите в комментариях, это будет полезно всем читателям, а особенно мне.

 

One thought on “Программирование на С — структура данных vector

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">