2014 dxdy logo

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

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




На страницу 1, 2, 3  След.
 
 Как лучше реализовать функцию pop()
Сообщение01.05.2014, 15:18 
Аватара пользователя
Здравствуйте! Возникла необходимость реализации стека в учебной программе. Нужен совет, как правильнее реализовывать функцию 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 
Зачем что-то выдумывать? Используйте стандартное решение для таких ситуаций:
Используется синтаксис 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 
Аватара пользователя
warlock66613
Я как раз стандартные способы и хотел узнать. Ваш вариант весьма неплох, я так тоже делал в других ситуациях. Но он не позволяет использовать pop(2) напрямую в выражениях (только окольным путём с помощью запятой), а также требует дополнительную переменную для результата (первое вытекает из второго, в принципе). Про errno почитаю, спасибо.

 
 
 
 Re: Как лучше реализовать функцию pop()
Сообщение01.05.2014, 16:50 
Legioner93 в сообщении #857657 писал(а):
Но он не позволяет использовать pop(2) напрямую в выражениях (только окольным путём с помощью запятой)
Так вам же всё равно так или иначе надо проверить, не произошло ли ошибки, прежде чем использовать результат дальше в выражении.

 
 
 
 Re: Как лучше реализовать функцию pop()
Сообщение01.05.2014, 17:33 
Аватара пользователя
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 
Не используйте define для хранения константных значений. Используйте константы или enum'ы.

 
 
 
 Re: Как лучше реализовать функцию pop()
Сообщение01.05.2014, 17:56 
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 
Аватара пользователя
EtCetera в сообщении #857681 писал(а):
Не используйте define для хранения константных значений.

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


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

 
 
 
 Re: Как лучше реализовать функцию pop()
Сообщение01.05.2014, 18:14 
Макросы $\text{---}$ это внеязыковая штуковина, они не "чувствуют" код программы, воспринимая его просто как текст. Поэтому если в тринадесятом #include будет макрос #define error_code 1, а потом Вы объявите случайно переменную error_code, то ошибку будете искать долго, компилятор не сможет Вам что-то подсказать. Макросы $\text{---}$ довольно мощная штука, но не стоит ими злоупотреблять по пустякам и/или использовать не по назначению. С ними надо быть очень аккуратными, прежде всего, стараться делать #undef сразу после использования.

 
 
 
 Re: Как лучше реализовать функцию pop()
Сообщение01.05.2014, 18:59 
Аватара пользователя
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 
Одна из великолепнейших, имхо, идей ++ — именно exceptions. И ещё автоматический вызов деструкторов. Как раз для этого сделано, всё прочее — ерунда. Я как-то делал что-то похожее для Цэ, на longjumpах. Стек, по сути дела, деструкторов, там же какие-то метки уровней, в начале и конце каждой функи вызовы служебных функций — начальная ставит метку в стек, конечная вызывает деструкторы до метки и снимает метку. longjump как-то это тоже отслеживает, так что при выходе сразу из нескольких процедур можно тоже приавильно поступить.
Сейчас, имхо, незачем себя насиловать Цэ — кроме разве сильно специальных случаев.

 
 
 
 Re: Как лучше реализовать функцию pop()
Сообщение01.05.2014, 20:00 
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 
Аватара пользователя
Ну он жалуется на переопределение 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 
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 
Аватара пользователя
EtCetera в сообщении #857760 писал(а):
Быстро Вы поймете, в чем дело, если будете иметь дело с более-менее крупной программой, где в один файл (с учетом вложенностей) может подключаться сотни и тысячи заголовочных файлов?

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

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

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

 
 
 [ Сообщений: 32 ]  На страницу 1, 2, 3  След.


Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group