The website "dmilvdv.narod.ru." is not registered with uCoz.
If you are absolutely sure your website must be here,
please contact our Support Team.
If you were searching for something on the Internet and ended up here, try again:

About uCoz web-service

Community

Legal information

7.7 Пример — List, Queue и Stack

7.7 Пример — List, Queue и Stack

Предыдущая  Содержание  Следующая V*D*V

Давайте посмотрим на несколько новых классов, реализованных с нуля с помощью ooc, чтобы можно было оценить сэкономленные силы. Начнём с List, реализованного в виде двухстороннего кругового буфера, который будет динамически увеличиваться по мере необходимости.

 

OOC_Example_-_List_Queue_and_Stack

 

begin и end ограничивают используемую часть списка, dim это максимальный размер буфера, а count представляет количество элементов в данный момент находящихся в буфере. count позволяет легко различать полный и пустой буфер. Вот описание класса List.d:

 

% ListClass: Class List: Object {

    const void ** buf; // const void * buf [dim]

    unsigned dim;      // текущий размер массива

    unsigned count;    // количество элементов в буфере

    unsigned begin;    // индекс слота takeFirst, 0..dim

    unsigned end;      // индекс слота addLast, 0..dim

%

    Object @ addFirst (_self, const Object @ element);

    Object @ addLast (_self, const Object @ element);

    unsigned count (const _self);

    Object @ lookAt (const _self, unsigned n);

    Object @ takeFirst (_self);

    Object @ takeLast (_self);

%—                     // абстракция, для Queue/Stack

    Object @ add (_self, const Object @ element);

    Object @ take (_self);

%}

 

Реализация в List.dc не очень сложна. Конструктор создаёт буфер начального размера:

 

% List ctor {

    struct List * self = super_ctor(List, _self, app);

 

    if (! (self -> dim = va_arg(* app, unsigned)))

        self -> dim = MIN;

    self -> buf = malloc(self -> dim * sizeof * self -> buf);

    assert(self -> buf);

    return self;

}

 

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

 

% List dtor {

%casts

    free(self —> buf), self —> buf = 0;

    return super_dtor(List, self);

}

 

addFirst() и addLast() добавляют один элемент в начало, по адресу begin, или конец, по адресу end:

 

% List addFirst {

%casts

    if (! self -> count)

        return add1(self, element);

    extend(self);

    if (self -> begin == 0)

        self -> begin = self -> dim;

    self -> buf[-- self -> begin] = element;

    return (void *) element;

}

% List addLast {

%casts

    if (! self -> count)

        return add1(self, element);

    extend(self);

    if (self -> end >= self -> dim)

        self -> end = 0;

    self -> buf[self -> end ++] = element;

    return (void *) element;

}

 

Обе эти функции используют один и тот же код для добавления одного элемента:

 

static void * add1 (struct List * self, const void * element)

{

    self -> end = self -> count = 1;

    return (void *) (self -> buf[self -> begin = 0] = element);

}

 

Поведение, однако, отличается: если count не равен нулю, то есть если в буфере есть какие-либо элементы, begin указывает к элемент, в то время как end указывает на свободный слот для заполнения. Любое значение может быть только за пределами текущего диапазона буфера. Буфер круговой; таким образом, мы сначала устанавливаем соответствие переменных с кругом, прежде чем получаем доступ к буферу. extend() выполняет трудную работу: если места больше нет, мы используем realloc(), чтобы удвоить размер буфера:

 

static void extend (struct List * self) // добавление элемента

{

    if (self -> count >= self -> dim) {

        self -> buf =

                realloc(self -> buf, 2 * self -> dim

                        * sizeof * self -> buf);

        assert(self -> buf);

        if (self -> begin && self -> begin != self -> dim) {

            memcpy(self -> buf + self -> dim + self -> begin,

                self -> buf + self -> begin,

                (self -> dim - self -> begin)

                        * sizeof * self -> buf);

            self -> begin += self -> dim;

        }

        else

            self -> begin = 0;

    }

    ++ self -> count;

}

 

realloc() копирует указатели, хранящиеся в buf[], но если наш круг не начинается в начале буфера, мы должны использовать memcpy(), чтобы сдвинуть начало круга в конец нового буфера.

Остальные функции гораздо проще. count() это просто функция доступа к компоненту count. lookAt() использует арифметический трюк для возврата необходимого элемента из кругового буфера:

 

% List lookAt {

%casts

    return (void *) (n >= self -> count ? 0 :

        self -> buf[(self -> begin + n) % self -> dim]);

}

 

takeFirst() и takeLast() просто обратны вариантам соответствующих функций add. Вот один из примеров:

 

% List takeFirst {

%casts

    if (! self -> count)

        return 0;

    -- self -> count;

    if (self -> begin >= self -> dim)

        self -> begin = 0;

    return (void *) self -> buf[self -> begin ++];

}

 

takeLast() остаётся в качестве упражнения — как и все селекторы и инициализация.

List показывает, что ooc возвращает нас к занятию решения вопросов реализации класса как типу данных, а не особенностей в стиле объектно-ориентированного кодирования. Учитывая разумный базовый класс, мы можем легко унаследовать более проблемно-ориентированные классы. List ввёл динамически компонуемые методы add() и take(), поэтому подкласс может наложить дисциплину доступа. Stack производит операции с одной стороны:

 

Stack.d

 

% ListClass Stack: List {

%}

 

Stack.dc

 

% Stack add {

    return addLast(_self, element);

}

% Stack take {

    return takeLast(_self);

}

%init

 

Queue может быть наследником Stack и перезаписать take() или может быть подклассом List и определить оба метода. List сам по себе не определяет динамически скомпонованные методы и предпочтения; поэтому может быть назван абстрактным базовым классом. Наши селекторы достаточно надёжны, поэтому мы конечно же узнаем, если кто-то попытается использовать add() или take() для List, а не подкласса. Вот тестовая программа, демонстрирующая, что мы можем добавлять в Stack или Queue простые строки C, а не объекты:

 

#include "Queue.h"

 

int main (int argc, char ** argv) {

    void * q;

    unsigned n;

 

    initQueue();

    q = new(Queue, 1);

 

    while (* ++ argv)

        switch (** argv) {

        case ’+’:

            add(q, *argv + 1);

            break;

        case ’-’:

            puts((char *) take(q));

            break;

        default:

            n = count(q);

            while (n -- > 0) {

                const void * p = take(q);

 

                puts(p), add(q, p);

            }

        }

    return 0;

}

 

Если аргумент командной строки начинается с +, она добавляется в очередь; если с - , один элемент удаляется. Любой другой аргумент выводит на экран содержимое очереди:

 

$ queue +axel - +is +here . - . - .

axel

is

here

is

here

here

 

Заменив Queue на Stack мы можем увидеть разницу в порядке удаления:

 

$ stack +axel - +is +here . - . - .

axel

is

here

here

is

is

 

Поскольку Stack является подклассом List, есть разные способы неразрушающего вывода на экран содержимого стека, например:

 

n = count(q);

while (n -- > 0) {

    const void * p = takeFirst(q);

 

    puts(p), addLast(q, p);

}

 

Предыдущая  Содержание  Следующая