2014 dxdy logo

Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




Начать новую тему Ответить на тему На страницу 1, 2, 3  След.
 
 Как лучше реализовать функцию pop()
Сообщение01.05.2014, 15:18 
Заслуженный участник
Аватара пользователя


28/07/09
1178
Здравствуйте! Возникла необходимость реализации стека в учебной программе. Нужен совет, как правильнее реализовывать функцию pop(1). А именно, интересует проблема обработки исключения "когда стек пустой".
Язык pure C.

Написал несколько вариантов, все работают, но ни один мне не нравится окончательно.
1)
Используется синтаксис C
typedef enum {EMPTY, POPPED} pop_t;
pop_t pop(stack **x);
 

Самый первый вариант. Функция не возвращает сам элемент стека, а только выкидывает его. Т.е. для чтения элемента стека нужно напрямую обращаться к структуре => снова возникает проблема пустого стека => бредовый вариант. Ну или делать отдельную функцию для чтения.
2)
Используется синтаксис C
double pop(stack **x)
{
stack *temp=*x;
if(!*x) fprintf(stderr, "Trying to pop from an empty stack!\n"), exit(EXIT_FAILURE); (или другие варианты, например abort())
...
return var;

В принципе неплохой вариант, но мне не нравится, что он подразумевает только один вариант решения проблемы пустого стека на все случаи жизни - тупо закрывать программу. Между тем, такая ошибка может возникнуть как в результате программной ошибки, так и в результате неправильных входных данных - это нужно отдельно обрабатывать. В общем, если по-русски, не хочу, чтобы исключения обрабатывала функция, а хочу делать это отдельно каждый раз сам. А она пусть только статус возвращает.
3)
Используется синтаксис C
typedef struct
{
double var;
enum {EMPTY, POPPED} status;
} pop_t;
pop_t pop(stack **x);

Т.е. функция как бы "кортежем" возвращает сразу и само значения из стека и статус (нормально ли досталось, или стек был пустой). На мой взгляд самый хороший вариант, т.к. даёт нам сразу и само значение, и позволяет обработать вариант EMPTY каждый раз по-своему.
Но такой подход требует больше памяти на возвращаемое значение и, самое главное, что мне очень не нравится - требует каждый раз при работе со стеком заводить специальную переменную типа pop_t, что очень некрасиво на мой взгляд.
4) Внешние переменные. Не нравится, т.к. функция становится завязанной на какие-то внешние переменные, перестаёт быть "чёрным ящиком". Наверняка в теории ООП для этого есть специальное слово даже, но лень гуглить.

 Профиль  
                  
 
 Re: Как лучше реализовать функцию pop()
Сообщение01.05.2014, 16:12 
Заслуженный участник


02/08/11
6892
Зачем что-то выдумывать? Используйте стандартное решение для таких ситуаций:
Используется синтаксис C
int pop(stack **x, double *result);
Возвращаем 0 если всё хорошо и -1 если плохо, вот и всё. Если функция может фейлится по разным причинам, то выставляем глобальную переменную errno (это особая глобальная переменная, её можно использовать) в более-менее подходящее значение (список в статье по ссылке). Дополнительно можно продублировать ошибку кодом возврата (return -errno;).

-- 01.05.2014, 17:17 --

Legioner93 в сообщении #857622 писал(а):
Наверняка в теории ООП для этого есть специальное слово даже, но лень гуглить.
Нарушение инкапсуляции, сильная связанность.

 Профиль  
                  
 
 Re: Как лучше реализовать функцию pop()
Сообщение01.05.2014, 16:38 
Заслуженный участник
Аватара пользователя


28/07/09
1178
warlock66613
Я как раз стандартные способы и хотел узнать. Ваш вариант весьма неплох, я так тоже делал в других ситуациях. Но он не позволяет использовать pop(2) напрямую в выражениях (только окольным путём с помощью запятой), а также требует дополнительную переменную для результата (первое вытекает из второго, в принципе). Про errno почитаю, спасибо.

 Профиль  
                  
 
 Re: Как лучше реализовать функцию pop()
Сообщение01.05.2014, 16:50 
Заслуженный участник


02/08/11
6892
Legioner93 в сообщении #857657 писал(а):
Но он не позволяет использовать pop(2) напрямую в выражениях (только окольным путём с помощью запятой)
Так вам же всё равно так или иначе надо проверить, не произошло ли ошибки, прежде чем использовать результат дальше в выражении.

 Профиль  
                  
 
 Re: Как лучше реализовать функцию pop()
Сообщение01.05.2014, 17:33 
Заслуженный участник
Аватара пользователя


28/07/09
1178
warlock66613
Да, похоже, что вы правы! Второй мой вариант кода позволяет писать что-нибудь типа printf("%f", pop(&x)), но тогда придётся отказаться от спецификации ошибки по контексту...

Про errno почитал, очень интересно, но в данном случае это даже излишне, т.к. у меня функция фейлится всё-таки по одной единственной причине (передан указатель на NULL), просто я хочу поступать по-разному в зависимости от контекста вызова.

Можно конечно проверять это условие прям при вызове, но это снова нарушение инкапсуляции...

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

-- Чт май 01, 2014 18:37:46 --

Просто числа не предлагать, т.к. это magic numbers:)

 Профиль  
                  
 
 Re: Как лучше реализовать функцию pop()
Сообщение01.05.2014, 17:39 
Заслуженный участник


28/04/09
1933
Не используйте define для хранения константных значений. Используйте константы или enum'ы.

 Профиль  
                  
 
 Re: Как лучше реализовать функцию pop()
Сообщение01.05.2014, 17:56 
Заслуженный участник


02/08/11
6892
Legioner93 в сообщении #857678 писал(а):
enum или define?
enum всегда (в любой ситуации) лучше, чем define.
Legioner93 в сообщении #857678 писал(а):
Просто числа не предлагать, т.к. это magic numbers:)
Именно так. Но тот же printf и многие другие стандартные функции (а также системные вызовы linux, например) используют -1 как код ошибки. То есть в данном случае -1 является хорошо узнаваемой константой, не магической. Правда проверять лучше всё-таки не == -1, а < 0 (есть функции, которые возвращают разные отрицательные чичла при разных ошибках, так что проверяя на < 0 не надо понить какие функции возвращают всегда -1, а какие не только -1).

 Профиль  
                  
 
 Re: Как лучше реализовать функцию pop()
Сообщение01.05.2014, 18:04 
Заслуженный участник
Аватара пользователя


28/07/09
1178
EtCetera в сообщении #857681 писал(а):
Не используйте define для хранения константных значений.

warlock66613 в сообщении #857691 писал(а):
enum всегда (в любой ситуации) лучше, чем define


Почему? Чем чревато? В Кернигане-Ритчи используется define для подобных вещей, в т.ч. для кодов возврата.

 Профиль  
                  
 
 Re: Как лучше реализовать функцию pop()
Сообщение01.05.2014, 18:14 
Заслуженный участник


28/04/09
1933
Макросы $\text{---}$ это внеязыковая штуковина, они не "чувствуют" код программы, воспринимая его просто как текст. Поэтому если в тринадесятом #include будет макрос #define error_code 1, а потом Вы объявите случайно переменную error_code, то ошибку будете искать долго, компилятор не сможет Вам что-то подсказать. Макросы $\text{---}$ довольно мощная штука, но не стоит ими злоупотреблять по пустякам и/или использовать не по назначению. С ними надо быть очень аккуратными, прежде всего, стараться делать #undef сразу после использования.

 Профиль  
                  
 
 Re: Как лучше реализовать функцию pop()
Сообщение01.05.2014, 18:59 
Заслуженный участник
Аватара пользователя


28/07/09
1178
EtCetera в сообщении #857699 писал(а):
Поэтому если в тринадесятом #include будет макрос #define error_code 1, а потом Вы объявите случайно переменную error_code, то ошибку будете искать долго, компилятор не сможет Вам что-то подсказать.

Чтобы использовать переменную error_code, её нужно сначала объявить. Но ни один компилятор не съест строчку
Код:
int 1;
которая получится после "#define error_code 1" из
Код:
int error_code;

Или я неправильно понял ваш пример? Поясните.

Кстати, насколько я знаю, константы из #define принято обозначать прописными буквами, при таком подходе ваша ошибка вообще исключена.

 Профиль  
                  
 
 Re: Как лучше реализовать функцию pop()
Сообщение01.05.2014, 19:10 
Заслуженный участник


16/02/13
4112
Владивосток
Одна из великолепнейших, имхо, идей ++ — именно exceptions. И ещё автоматический вызов деструкторов. Как раз для этого сделано, всё прочее — ерунда. Я как-то делал что-то похожее для Цэ, на longjumpах. Стек, по сути дела, деструкторов, там же какие-то метки уровней, в начале и конце каждой функи вызовы служебных функций — начальная ставит метку в стек, конечная вызывает деструкторы до метки и снимает метку. longjump как-то это тоже отслеживает, так что при выходе сразу из нескольких процедур можно тоже приавильно поступить.
Сейчас, имхо, незачем себя насиловать Цэ — кроме разве сильно специальных случаев.

 Профиль  
                  
 
 Re: Как лучше реализовать функцию pop()
Сообщение01.05.2014, 20:00 
Заслуженный участник


28/04/09
1933
Legioner93 в сообщении #857722 писал(а):
Или я неправильно понял ваш пример? Поясните.
Правильно. Не съест. Но почему? Быстро ли Вы поймете?
Legioner93 в сообщении #857722 писал(а):
Кстати, насколько я знаю, константы из #define принято обозначать прописными буквами, при таком подходе ваша ошибка вообще исключена.
Ну, мало ли что принято. Кроме того, человек не всегда себя контролирует. И не всегда пишет код в одиночку.

Попытайтесь понять, к примеру, почему такой простой код не компилируется:
код: [ скачать ] [ спрятать ]
Используется синтаксис C
#include <windows.h>


int GetCurrentTime()
{
    return 2014;
}

int max(int a, int b)
{
    return a > b ? a : b;
}

int main(void)
{
    int a = 1;
    int b = 2;
    int m;
    int t;
   
    m = max(a, b);
    t = GetCurrentTime();
}

 Профиль  
                  
 
 Re: Как лучше реализовать функцию pop()
Сообщение01.05.2014, 20:14 
Заслуженный участник
Аватара пользователя


28/07/09
1178
Ну он жалуется на переопределение GetTickCount, видимо где-то в windows.h (я даже не знаю, что это) стоит что-то вроде
Код:
"#define GetCurrentTime ((GetTickCount(сейчас))/10000)


-- Чт май 01, 2014 21:18:01 --

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

-- Чт май 01, 2014 21:19:53 --

10000 это у меня в gcc в time.h стоит CLOCKS_PER_SEC, хотя точно уже не помню, давно не пользовался.

 Профиль  
                  
 
 Re: Как лучше реализовать функцию pop()
Сообщение01.05.2014, 20:30 
Заслуженный участник


28/04/09
1933
Legioner93 в сообщении #857746 писал(а):
Ну он жалуется на переопределение GetTickCount, видимо где-то в windows.h (я даже не знаю, что это) стоит что-то вроде
Код:
"#define GetCurrentTime ((GetTickCount(сейчас))/10000)
Ну да. Что-то вроде. Быстро Вы поймете, в чем дело, если будете иметь дело с более-менее крупной программой, где в один файл (с учетом вложенностей) могут подключаться сотни и тысячи заголовочных файлов?
Legioner93 в сообщении #857746 писал(а):
Насчёт максимума не знаю, код ошибки ни о чем не говорит, но по контексту нашей беседы есть догадка: возможно это тоже какая-то стандартная функция-макрос, сделанная дефайном через тернарное условие.
Угу. Стандартная. Для того же windows.h. Как Вам сообщение об ошибке? Понятное?
Legioner93 в сообщении #857746 писал(а):
windows.h (я даже не знаю, что это)
Это "главный" заголовочный файл WinAPI.

 Профиль  
                  
 
 Re: Как лучше реализовать функцию pop()
Сообщение01.05.2014, 20:39 
Заслуженный участник
Аватара пользователя


28/07/09
1178
EtCetera в сообщении #857760 писал(а):
Быстро Вы поймете, в чем дело, если будете иметь дело с более-менее крупной программой, где в один файл (с учетом вложенностей) может подключаться сотни и тысячи заголовочных файлов?

Не знаю, честно. Пока не занимался такими программами. Но вы меня очень заинтересовали этой темой: можно ли как-то побороть этот ад вложенных хедеров? Т.е. какая-нибудь ограниченная область действия макросов. Вот чтобы я подключил файл A в файле B, поработал с функциями из A в B, потом подключил файл B в файле C и мог пользоваться в С функциями из B, а из A - не мог?

-- Чт май 01, 2014 21:40:56 --

Ведь такая структура выглядела бы очень логичной. Мне не нужны функции из A в C - только из B. А если понадобятся непосредственно из A - подключу отдельно.

 Профиль  
                  
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 32 ]  На страницу 1, 2, 3  След.

Модераторы: Karan, Toucan, PAV, maxal, Супермодераторы



Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group