LINUX.ORG.RU

Abstract data type и Си

 ,


1

2

Задача написать учебный пример по Abstract data type на примере Си.

Что получилось:

stack.h:

// ...
struct Stack_s;
typedef struct Stack_s Stack;
// Old: typedef int Item;
typedef int8_t Item;

// Old: Stack*  stack_create    ();
// Old: void    stack_destroy   (Stack **stack);
// Old: Stack*  stack_clone     (Stack *stack);
Stack*  stack_create    (void);
void    stack_destroy   (Stack *stack);
Stack*  stack_clone     (const Stack *stack);

size_t  stack_size      (const Stack *stack);
int     stack_is_empty  (const Stack *stack);

void    stack_push      (Stack *stack, Item item);
Item    stack_pop       (Stack *stack);
Item    stack_top       (const Stack *stack);
void    stack_clear     (Stack *stack);
// ...

stack.c:

#include "stack.h"

struct Stack_s {
  // Полное описание структуры стека скрыто от пользователя
  // ...
};

// Old: Stack* stack_create() {
Stack* stack_create(void) {
  // Создать стек можно только используя stack_create
  // ...
}

// ...

main.c:

#include <stdio.h>
#include <stack.h>

int main(void) {
  Stack *stack = stack_create();
  
  stack_push(stack, 1);
  stack_push(stack, 2);
  stack_push(stack, 3);
  
  while(!stack_is_empty(stack)) {
    printf("%d\n", stack_pop(stack));
  }
  
// Old: stack_destroy(&stack);
  stack_destroy(stack);
  return 0;
}

Хочется конструктивной (и не очень) критики.

Не смог найти хороших ссылок по именованию и минимальному набору функций (в том числе как удалять «объект»).

З.Ы.: Про GObject, Cello, OOC toolkit, Axel T. Schreiner, Object-Oriented Programming with ANSI-C слыхал

★★★★★

Последнее исправление: AlexVR (всего исправлений: 4)
Ответ на: комментарий от Sorcerer

С одной стороны, на него не ругается даже -Wall -Wpedantic, и на слайдах это будет лишней сущностью.

С другой - вроде как и стоит его добавить.

AlexVR ★★★★★
() автор топика
Ответ на: комментарий от former_anonymous

И причём тут double? Есть «класс» кишки которого наружу не видны, а интерфейс в достаточной мере позволяет создавать и обрабатывать его экземпляры.

Хотя твой вопрос говорит о том, что пример стоит выбрать другим.

AlexVR ★★★★★
() автор топика
Ответ на: комментарий от UVV

Проверил на слайдах, c minted смотрится хорошо. Да и сам написал main(void), пора закреплять привычку.

AlexVR ★★★★★
() автор топика
Ответ на: комментарий от UVV

А чем это будет отличаться от стека в который можно запихать char, double, struct Foo одновременно?

AlexVR ★★★★★
() автор топика
Ответ на: комментарий от UVV

fix:

typedef int8_t Item;

Но какое отношение это имеет к Abstract data type?

Михалкович С.С., Основы программирования:

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

ВикипедиЯ:

Абстрактный тип данных --- это множество объектов, определяемое списком компонентов (операций, применимых к этим объектам, и их свойств). Вся внутренняя структура такого типа спрятана от разработчика программного обеспечения — в этом и заключается суть абстракции. Абстрактный тип данных определяет набор функций, независимых от конкретной реализации типа, для оперирования его значениями. Конкретные реализации АТД называются структурами данных.

AlexVR ★★★★★
() автор топика
Ответ на: комментарий от UVV

А кто этот void* потом будет удалять? И каким способом? А нужно ли его удалять? То что предложено по ссылке не панацея, и не имеет отношения к АТД.

То что лучше рассмотреть другой объект для примера, я уже понял. Но не надо мешать всё в одну кучу.

Для примера

/* FEOF example */
#include <stdio.h>

int main()
{
   FILE * pFile;
   char buffer [100];

   pFile = fopen ("myfile.txt" , "r");
   if (pFile == NULL) perror ("Error opening file");
   else
   {
     while ( ! feof (pFile) )
     {
       if ( fgets (buffer , 100 , pFile) == NULL ) break;
       fputs (buffer , stdout);
     }
     fclose (pFile);
   }
   return 0;
}

Откомпилировать и запустить его можно много где. И для программиста совершенно не важно как выглядит FILE и как реализованы fopen, fclose, fgets, ... Важно, что определён интерфейс для FILE через набор функций.

AlexVR ★★★★★
() автор топика

1) В destroy() и clone() не хватает const-ов в аргументах

2) Что делает ваша функция pop(), если стек пуст? И что она возвращает в таком случае? Тот же вопрос про top()

3) Что будет, если под пустой стек выделено памяти на 1000 элементов, а мы попытаемся сделать push() 1001 раз?

4) Вообще говоря, в зависимости от того, как у вас устроен стек, функции pop() и push() должны принимать одинарный или двойной указатель на стек. Покажите реализацию, если можно.

Pershin
()
Ответ на: комментарий от Pershin

1) В destroy() и clone() не хватает const-ов в аргументах

Про clone() это мой косяк, пора прекращать писать слайды ночью. А вот как лучше:

void    stack_destroy   (Stack **stack);
или
void    stack_destroy   (Stack *stack);
Мне ещё не понятно.

2) Что делает ваша функция pop(), если стек пуст? И что она возвращает в таком случае? Тот же вопрос про top()

Пусть будет UB.

3) Что будет, если под пустой стек выделено памяти на 1000 элементов, а мы попытаемся сделать push() 1001 раз?

Судя по коду, библиотека сама должна решить этот вопрос ;)

4) Вообще говоря, в зависимости от того, как у вас устроен стек, функции pop() и push() должны принимать одинарный или двойной указатель на стек. Покажите реализацию, если можно.

В том и смысл, что для пользователя не должно быть важным как реализовано (используются массивы с заполнением сверху вниз или снизу вверх, или применяются списки, или ещё как-то). Именно поэтому, полное описание структуры расположено в `stack.c` и скрыто от пользователя.

Прошу воспринимать эти три кусочка кода, как три слайда реализации «Абстрактного типа данных» на Си

AlexVR ★★★★★
() автор топика

void stack_destroy (Stack **stack);

Зачем указатель на указатель? Занулять? Так обычно не делают, free как пример.

PS Всё нормально с примером, у людей тут каша в голове.

anonymous
()
Ответ на: комментарий от AlexVR

пример нормальный, заодно можно показать на этом примере эволюцию от ADT до ООП, на примере какого-нибудь Модула-2/Модула-3/Оберона-2 до Eiffel :

инкапсуляция:

* модули Oberon-2 довольно прозрачно переводятся в/из Си: экспортированные глобальные переменные/процедуры модуля (со звёздочкой, неэкспортированные по умолчанию без звёздочки) в оберонах становятся в Си экспортированными по умолчанию, неэкспортированные со static

* через static функции Си (неэкспортированные наружу из модуля) можно изобразить приватные методы (или переменные, через геттеры/сеттеры/ридонли только геттеры). вот с protected уже сложнее

полиморфизм: возможность присвоить указателю на **Stack указатель *Stack, или *Stack-у присвоить void*. здесь правда нужно ограничить полиморфное присваивание предку только потомков, что в голом С сделать затруднительно (например, смотри в GObject какими костылями с макросами там всё обмазано ради типобезопасности).

* в Eiffel можно описать контрактами, предусловиями/постусловиями это вот самое UB в Си

* в Eiffel классы составляют решётку типов. то есть, есть дефолтный класс ANY (Object с рефлексией, общий предок всех классов) и класс NONE (по определению класс, который не может иметь потомков, и экземпляр которого нельзя создать. причём Void (NULL ссылка в Си) имеет тип этого класса NONE, то есть система типов для классов полна, замкнута (частично упорядочена, то есть составляет объект типа «математическая структура», «решётка»)

(из книг Б. Мейера) Eiffel: класс — это АТД, снабжённый некоторой, возможно частичной реализацией

и т.п. про «достаточно полный АТД», «непротиворечивый АТД», «корректный АТД», WFF (правильно построенный) АТД

из книг Б. Мейера (например, Бертран Мейер «Основы объектно-ориентированного программирования» или про ОО конструирование или более свежей «Почувствуй класс с Eiffel») в общем понятна суть «метода Eiffel» : ввести некоторый «исполняемый псевдокод» для определения семантики ООП, наиболее широкой (reusability, seemlesness, reversibility) и на DSL поверх задавать контракты, инварианты, пред- и постусловия как некоторый програмно проверяемый CTFE компилятором метаязык.

причём он там конкретные правила определяет, для обеспечения этой reusability.

а определять virtual метод или нет — будет компилятор. определять делать свойство с 0 параметрами функцией или переменной, делать реализацию абстрактной или конкретной (частично абстрактный, частично конкретный класс, причём можно перекрыть в потомках конкретный абстрактным, затем снова конкретным, снова абстрактным; тоже со свойством как функция/метод или переменная) --- должен программист, причём в клиенте модуля-типа (классе-потомке).

а в сервере модуле (классе-предке) — НИЧЕГО не должно меняться (Fragile Base Class problem, C++, аууу???).

reusability во все поля, же ну.

anonymous
()
Ответ на: комментарий от anonymous

причём что компиляторы Oberon-2, что Eiffel умеют транслироваться в Си (и/или, генерировать обёртки к Си библиотекам). что тоже намекает на прямую связь между АТД и ООП, модульностью и компонентностью (ООП-стостью).

anonymous
()
Ответ на: комментарий от anonymous
Автор: Мейер Бертран
Название: Основы объектно-ориентированного программирования
Содержание
Лекция 1. Качество ПО
Лекция 2. Критерии объектной ориентации
Лекция 3. Модульность
Лекция 4. Подходы к повторному использованию
Лекция 5. К объектной технологии
>>> Лекция 6. Абстрактные типы данных (АТД) <<<
Лекция 7. Статические структуры: классы
Лекция 8. Динамические структуры: объекты
Лекция 9. Управление памятью
Лекция 10. Универсализация
Лекция 11. Проектирование по контракту: построение надежного ПО
Лекция 12. Когда контракт нарушается: обработка исключений
Лекция 13. Поддерживающие механизмы
Лекция 14. Введение в наследование
Лекция 15. Множественное наследование
Лекция 16. Техника
anonymous
()
Ответ на: комментарий от anonymous
Лекция 16. Техника наследования
Лекция 17. Типизация
Лекция 18. Глобальные объекты и константы
anonymous
()
Ответ на: комментарий от anonymous

Мейера в общем полезно почитать на предмет того, как из наиболее общих принципов «повторной используемости» и общих пожеланий он последовательно вводит правила и методы и выводит этот «исполняемый псевдокод» ООП, который постепенно превращается в язык Eiffel.

конструирует его последовательно, как DSL.

Клеменс Шипёрски в книге «Компонентно-ориентированное программирование (дальнейшее развитие ООП)» делает то же самое с модульным программированием, Oberon-2, Blackbox Component Pascal. и выводит ООП из требований КОП и компонентно-ориентированной среды.

(тут внезапно выясняется что классическое ООП в стиле С++/Simula67 «наследование, инкапсуляция, полиморфизм» — не нужно. а модульность, компонентность — нужна. её можно и на базе АТД строить, чему замечательный пример — модули в ML (экспортированные типы/сигнатуры/функторы модуля, композабельность функторов в Mirage unikernel драйверах, например)

anonymous
()
...
void    stack_destroy   (Stack *stack);
...
...
int main(void) {
  Stack *stack = stack_create();
  
  ...
  stack_destroy(&stack);
  return 0;
}

ошибка. нужно

  stack_destroy(&stack);

ты же указатель в деструктор передаёшь. а &stack это адрес указателя, а не значение указателя.

anonymous
()
Ответ на: комментарий от anonymous

однородности ради можно и

Stack*  stack_create    (void);
заменить на
void stack_create(Stack*);

в духе UFCS в D (Uniform Function call Syntax):

foo(s,a,b,c,...) = s.foo(a,b,c,...)

чтобы не гадать, какого типа первый параметр.

anonymous
()
Ответ на: комментарий от anonymous

тут, тащем-та и видно, что система типов в голом Си сосёт: const ещё мы можем указать, а вот immutable с транзитивностью константности — фиг; полиморфного потомка — фиг; контракты в виде предусловий, постусловий метода и инварианта класса — фигушки; присоединённую (attached) ссылку с проверкой корректности вызовов, с полиморфностью — фиг; generic type для контейнера — тоже фиг;

в общем, всё то что делает «компилятор в ООП», указать в CTFE в «голом си» почти что и нельзя.

то есть это такие соглашения, с натяжкой.

anonymous
()
Ответ на: комментарий от anonymous

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

anonymous
()
Ответ на: комментарий от AlexVR

компонентно-ориентированную среду же можно выстроить и без ООП, на «голых» АТД.

если язык достаточно мощный и позволяет создавать типы. например, через лямбду вернуть функцию-генератор (конструктор типа), деструктор и т.п.

например, читай книжку про автокод Эль-76 в Эльбрусах, технологию Т.И.П.:

5.3.3 Технологический инструментальный пакет (ТИП). Обзор ТИП-технологии.

Основным инструментальным средством рассматриваемой технологии программирования является ТИП - технологический инструментальный пакет. ТИП определяет интерфейс объекта (совокупность операций над ним) и содержит в инкапсулированном виде его конкретное представление и реализацию операций. Новизна этого понятия, по сравнению с обычным понятием пакета (package), состоит в том, что горизонтальное и вертикальное слоение интерфейса, его форма и использование регламентированы. Интерфейс ТИПа должен содержать следующие вертикальные слои (группы модулей), соответствующие описанным выше основным классам операций над объектами: интерфейс доступа интерфейс генерации интерфейс модификации интерфейс вывода Интерфейс должен иметь следующие горизонтальные слои: уровень представления (0) – уровень работы в терминах элементов конкретного представления объекта уровень определения (1) – уровень работы в терминах атрибутов объекта, указанных в его определении концептуальный уровень (2) – в терминах абстр концепции, моделируемой данным объектом

это, для справки, 1976 год. алголоподобный Эль-76 с лямбдами и модулями.

anonymous
()
Ответ на: комментарий от AlexVR

Да и сам написал main(void), пора закреплять привычку.

Зачем?

anonymous
()
Ответ на: комментарий от anonymous

Вот поэтому, когда я начинаю писать слайды, у меня уходит немереное количество времени на чтение близко стоящих тем. А заканчивается тем, что успеваю за год подготовить слайды только для нескольких лекций. А остальное: «доска+мел». Диким спасением является то, что основными инструментами являются LaTeX+beamer, markdown+pandoc и Mercurial. И есть возможность, год от года дополнять ранее написанное.

AlexVR ★★★★★
() автор топика

учебный пример по Abstract data type на примере Си.

вот где-то примерно (учитывая состояния тяпницы) так..

#define push(stack,x) *stack++=x;
#define pop(stack,x) x=*stack--;
а вот stack_create() stack_destroy() .. это уже C++ головного мозга :-) в С всё просто..только то без чего не обойтись.

MKuznetsov ★★★★★
()
Ответ на: комментарий от MKuznetsov

а вот stack_create() stack_destroy() .. это уже C++ головного мозга

А теперь представь, что есть большой код в котором: триангуляция Делоне и т.п. А на этапе оптимизации point_create, edge_create, triangle_create переводятся с malloc на Memory pool.

AlexVR ★★★★★
() автор топика
Ответ на: комментарий от AlexVR

А теперь представь, что есть большой код в котором

в С не представляют и не фантазируют. Есть задача - её (и именно конкретно её) решают. Этап оптимизации (когда уже работают «триангуляция Делоне и т.п.») наступает после того как решение получено. Когда выделены узкие места и обоснован «перевод с malloc на pool» :-)

ps/ кстати, а с чего вы взяли malloc ? в моём сообщении о нём ни слова..где размещать объекты-сущности в С решает программист

MKuznetsov ★★★★★
()
Последнее исправление: MKuznetsov (всего исправлений: 1)
Ответ на: комментарий от MKuznetsov

в С всё просто..только то без чего не обойтись.

Вот прикинь, АТД без конструктора и деструктора и не обходятся. А C++ тут вообще ни при чем.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.