» Кольцевые двусвязные списки на С – проще не бывает : Берлога инженера - бесплатные программы - стереофото - справочные материалы - обои для рабочего стола


Кольцевые двусвязные списки на С – проще не бывает

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

Сразу оговорюсь, это не моё изобретение, мне подсказал идею мой друг, а он, в свою очередь, добыл эти знания где-то в недрах сетевого ядра FreeBSD.

Итак, имеем проект, написанный на С. Задача – хранить некоторое количество структур данных таким образом, чтобы имелась возможность вставки и удаления произвольной структуры при сохранении адресов всех прочих.

Одним их подходящих решений для данной задачи является кольцевой двусвязный список (я надеюсь, мне не нужно объяснять вам, что это такое). Но в С нет такого встроенного типа, нельзя решить задачу и через шаблоны, как это сделано, например, в STL. На помощь нам приходит близость языка к «железу» и такое мощное (в умелых руках) средство, как приведение типов.

Почему я упомянул здесь про умелые руки? Бездумное приведение типов есть отличный источник трудноуловимых ошибок. Хрестоматийным случаем является приведение беззнакового целого к знаковому и наоборот. Не зря в С++ это действие сопровождается ритуальными плясками с static_cast и прочими cast-ами. Поэтому пользоваться им необходимо осмотрительно, точно зная, что делаешь.

Сначала нам необходимо провести подготовительные работы. Знакомьтесь - базовая структура для кольцевого двусвязного списка:


struct dl_node {
  struct dl_node  *next; // Указатель на следующий
                         // элемент
  struct dl_node  *prev; // Указатель на предыдущий
                         // элемент
};

У пустого списка, представленного своим головным элементом, оба указателя содержат адрес самой структуры «головы».

Пустой двусвязный список

При добавлении элемента его элементы next и prev будут указывать на «голову» списка, а те же элементы «головы» - на добавленный элемент.

Двусвязный список с одним элементом

Далее можно добавлять элементы в конец и начало списка, то есть до и после головного элемента. Получаем такую структуру:

Двусвязный список

Для того, чтобы удалить элемент из списка, достаточно правильно перелинковать его предыдущего и последующего соседей.

Все эти операции осуществляют функции, находящиеся в файлах list.h и list.c. Архив с ними лежит здесь.

Теперь пришла ваша очередь задать вопрос: «Конечно, всё вышеизложенное весьма интересно, но где же сами данные, которые мы будем хранить при помощи двунаправленного списка?».

Замечание справедливо, поэтому переходим к практическому применению полученного кода.

Пример: необходим список целых знаковых значений.

Для реализации сначала опишем следующую структуру:


struct int_list {
  struct dl_node  node;  // Служебная структура для
                         // реализации кольцевого
                         // двусвязного списка
  int             value; // Собственно, данные
};

Далее определяем переменные.


struct dl_node	our_list; // «голова» списка. Её тип
                          // одинаков для любых типов
                          // хранимых значений - это
                          // очень удобно
struct int_list	*element; // Это указатель на текущий
                          // элемент списка

Инициализируем список


list_init (&our_list);

Аллокируем и добавляем первый элемент


element = (struct int_list *)malloc (sizeof (int_list));
element->value = 45;
list_append (&our_list, (struct dl_node*)element);

Здесь как раз используется приведение типов. Почему такое возможно? Потому, что структура dl_node есть первый элемент структуры int_list, а значит, их адреса совпадают. Поэтому мы можем свободно приводить их адреса друг к другу, не опасаясь за последствия.

Мы можем добавлять сколько угодно членов любых типов в структуру int_list, и она будет оставаться двусвязным списком, при условии первенства элемента node.

А теперь пробежимся по списку и распечатаем его значения. Сделать это можно многими способами. Мне больше всего нравится такой:


for (element = (struct int_list *)our_list.node.next;
    element != (struct int_list *)&dev->protocols;
    element = (struct int_list *) element ->node.next) {
  printf ("Element is %d\n", element->value);
}

Пришло время собирать камни. Функции list_remove, служащей для удаления элемента из списка, требуется только один параметр – адрес этого элемента. А бремя освобождения памяти, занятой элементами списка ложится целиком на ваши плечи.

Но вы ведь не какие-нибудь там неженки, ожидающие, что весь мусор за вами соберет система? Eсли вы дочитали до этого места, вы – программисты от сохи, привыкшие всё делать сами, обладающие навыками кодинга без страховки и набором собственных приемов написания правильного кода.

Надеюсь, этот набор сегодня пополнился.

P.S. Спасибо Ивану Сагалаеву за его highlight.js

 Рекомендуйте на news2.ru     Занесите в del.icio.us

Читайте также:
No related posts





8 комментария to “Кольцевые двусвязные списки на С – проще не бывает”

  1. Sergey :

    Ох уж эти высокоуровневые программисты, страдающие от отсутствия двусвязанного списка в STL :) А если серьезно, то C хорош как раз тем, что там можно сделать все что угодно и как угодно, причем алгоритм работы будет весьма прозрачен, в отличии хотя бы от С++. Возможно поэтому он и является самым распространенным языком для микроконтроллеров.

  2. Vladimir :

    2 Sergey: Соглашаюсь с Вами. Хотя, как видно, посетители Берлоги больше склоняются всё же к C++:
    http://beta.delta-z.com/cgi-bin/yabb2/YaBB.pl?num=1159537441
    Кстати, там в комментариях я помещал ссылки на другие рейтинги (и коммерческие и статистические).

    Позволю отвлечься от темы, да простит меня автор статьи.

    Да, статья подтверждает красоту, простоту и изящество применения C. Реализация приведённой в статье конструкции на каком-либо другом языке повлечёт за собой увеличение либо объёмов исходных текстов, либо кода (если этот язык не имеет уже встроенного аппарата для работы со списками. ;) ).

    Сделаю одно замечание. C конечно же хорош, но в среде контроллеров и DSP к нему относятся неоднозначно. Многое зависит от компилятора. Часто ассемблерные команды контроллера или DSP настолько ёмки, что трудно найти в C эквивалент (а с упаковкой команд - вообще проблема - только ASM).
    Да, наверное большая часть текстов для MCU&DSP пишется на C. Но этот инструмент обеспечивает лишь простоту и функциональность. Настоящую же скорость и мощь даст лишь специализированный инструмент (ASM или, опять же, библиотека, написанная на том же ASM’е).

  3. Роман :

    Я конечно дико извеняюсь, но о связном списке знает любой хоть как то грамотный программист и то что его открыл некий друг в недрах ФриБСд … хм… ну не буду конечно критикавать, но всё же забавно +) А так статья хорошая.

    Присоединясь к оффтопу:
    Си хорошь и я пишу например исключительно на нём ибо пока ещё не было задачи в которой надо было использывать что то другое ( я имею ввиду более высокоуровневое ) С++ тоже хорошо даже отлично очень хорошая структура программы получаеться легко читаемая если невдаваться в реализацию классов…. Но многие почему то начинают изучать язык с Си++ только потому что это “типа крута” причем в 90% случаев используют 2-3% языка =)

  4. Алексей :

    2Vladimir:
    > посетители Берлоги больше склоняются всё же к C++:

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

    2Роман:
    Не могу не согласиться с Вашим комментарием, но хотелось бы заметить, что данный ресурс читают не только опытные программисты, да и не все, кто занимается программированием, получили хорошее образование по этому профилю - жизнь заставляет :)

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

    Если есть желание написать статью о чем-то более “продвинутом” - милости просим в авторы.

  5. Maverick :

    Я конечно не супер программист и в программирование не так уж и давно. Но мне кажется что это не просто Двунаправленный спиок, а кольцевой список. Так как последний элемент указывает на следующий как на головной ( последний на первый). Тут конечно половина было не понятно потому как написано на чистом Си. А разве нельзя на С++ сделать примерно тоже самое через new(). Не пользуясь тем же STL.

    
    struct lsit {
       int a;
       char name[10];
       list *next;
       list *prev;
    }
    list head=NULL;
    
      new(list);
      head=list;
      list.next=NULL;
      list.prev=NULL;
    
    

    Или я что то не правильно делаю? Если разъясните большое спасибо, а то я только учусь :-))

  6. Алексей :

    2Maverick:
    Насчёт того, что список кольцевой, абсолютно согласен. Сейчас поправлю.

    Теперь о С и С++. Суть данного решения на С состоит в том, чтобы всего лишь добавив в начало любой структуры данных элемент типа struct dl_node получить возможность организовывать эти структуры в кольцевой двусвязный список. Это достигается при помощи приведения типов.

    Тот же самый результат можно получить в С++ при помощи шаблонов или наследования.

    Результат получаем один и тот же - при помощи один раз написанного кода осуществлять манипуляцию с различными двусвязными списками.

  7. GQ :

    Кто сказал, что stl-ный list однонаправленный? Я хочу плюнуть ему в лицо :)

  8. Vit :

    Не являюсь профессиональным программистом. Статья симпатичная. Но думаю, что о списках лучше не подсматривать во FreeBSD, а почитать старую добрую книгу Никлауса Вирта “Алгориты и структуры данных”, а уж потом смотреть перевод на С:)

Оставить комментарий

 
микрофон экзамен, невидимый наушник, миниатюрные наушники; Индиана Джонс на DVD; Властелин колец на DVD; aqwella мебель для ванной комнаты; S37РGТ - диссертации, кандидатская, вак